mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2025-01-24 19:25:52 +00:00
Merge branch 'staging' into fastlane
This commit is contained in:
commit
e7ad498b71
381 changed files with 21075 additions and 7844 deletions
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
|
||||
|
|
|
@ -1,16 +1,3 @@
|
|||
buildscript {
|
||||
ext.kotlin_version = '1.8.0'
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.3.1'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
|
@ -18,12 +5,12 @@ allprojects {
|
|||
}
|
||||
}
|
||||
|
||||
rootProject.buildDir = '../build'
|
||||
rootProject.buildDir = "../build"
|
||||
subprojects {
|
||||
project.buildDir = "${rootProject.buildDir}/${project.name}"
|
||||
}
|
||||
subprojects {
|
||||
project.evaluationDependsOn(':app')
|
||||
project.evaluationDependsOn(":app")
|
||||
}
|
||||
|
||||
tasks.register("clean", Delete) {
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
org.gradle.jvmargs=-Xmx1536M
|
||||
android.enableR8=true
|
||||
org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
android.enableJetifier=true
|
|
@ -1,6 +1,5 @@
|
|||
#Fri Jun 23 08:50:38 CEST 2017
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip
|
||||
|
|
|
@ -1,11 +1,25 @@
|
|||
include ':app'
|
||||
pluginManagement {
|
||||
def flutterSdkPath = {
|
||||
def properties = new Properties()
|
||||
file("local.properties").withInputStream { properties.load(it) }
|
||||
def flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
|
||||
return flutterSdkPath
|
||||
}()
|
||||
|
||||
def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
|
||||
def properties = new Properties()
|
||||
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
|
||||
|
||||
assert localPropertiesFile.exists()
|
||||
localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
|
||||
def flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
|
||||
apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
|
||||
plugins {
|
||||
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
||||
id "com.android.application" version '8.6.0' apply false
|
||||
id "org.jetbrains.kotlin.android" version "1.8.22" apply false
|
||||
}
|
||||
|
||||
include ":app"
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
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 Width: | Height: | 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 Width: | Height: | 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 Width: | Height: | 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 Width: | Height: | Size: 2.2 MiB |
|
@ -1 +1 @@
|
|||
Subproject commit 19c76409e55f1bfed58855eb767574604376edb6
|
||||
Subproject commit 0bb1b1ced6e0d3c66e383698f89825754c692986
|
|
@ -1 +1 @@
|
|||
Subproject commit b654bf4488357c8a104900e11f9468d54a39f22b
|
||||
Subproject commit 5b08645a5b5d30955f4bde2a624ff89ef516e452
|
|
@ -1 +0,0 @@
|
|||
Subproject commit 982f5ab19fe0dd3dd3f6be2c46f8dff13d49027c
|
|
@ -1 +1 @@
|
|||
Subproject commit d539de2348bdbb87bac341dcaa6a0755f21d48e2
|
||||
Subproject commit 2451deab817b456ad93d5579c0d0687cb681392a
|
|
@ -13,12 +13,12 @@ Here you will find instructions on how to install the necessary tools for buildi
|
|||
The following instructions are for building and running on a Linux host. Alternatively, see the [Mac](#mac-host) and/or [Windows](#windows-host) section. This entire section (except for the Android Studio section) needs to be completed in WSL if building on a Windows host.
|
||||
|
||||
### Flutter
|
||||
Install Flutter 3.22.1 by [following their guide](https://docs.flutter.dev/get-started/install/linux/desktop?tab=download#install-the-flutter-sdk). You can also clone https://github.com/flutter/flutter, check out the `3.22.1` tag, and add its `flutter/bin` folder to your PATH as in
|
||||
Install Flutter 3.24.3 by [following their guide](https://docs.flutter.dev/get-started/install/linux/desktop?tab=download#install-the-flutter-sdk). You can also clone https://github.com/flutter/flutter, check out the `3.24.3` tag, and add its `flutter/bin` folder to your PATH as in
|
||||
```sh
|
||||
FLUTTER_DIR="$HOME/development/flutter"
|
||||
git clone https://github.com/flutter/flutter.git "$FLUTTER_DIR"
|
||||
cd "$FLUTTER_DIR"
|
||||
git checkout 3.22.1
|
||||
git checkout 3.24.3
|
||||
echo 'export PATH="$PATH:'"$FLUTTER_DIR"'/bin"' >> "$HOME/.profile"
|
||||
source "$HOME/.profile"
|
||||
flutter precache
|
||||
|
@ -53,14 +53,33 @@ 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
|
||||
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
|
||||
```
|
||||
|
||||
Install [Rust](https://www.rust-lang.org/tools/install) with command:
|
||||
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:
|
||||
```
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||
source ~/.bashrc
|
||||
rustup install 1.67.1 1.72.0 1.73.0
|
||||
source ~/.bashrc
|
||||
rustup install 1.67.1 1.71.0 1.72.0 1.73.0
|
||||
rustup default 1.67.1
|
||||
cargo install cargo-ndk --version 2.12.7 --locked
|
||||
```
|
||||
|
@ -77,6 +96,7 @@ sudo apt-get install clang cmake ninja-build pkg-config libgtk-3-dev liblzma-dev
|
|||
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
|
||||
```
|
||||
|
||||
### Clone the repository and initialize submodules
|
||||
After installing the prerequisites listed above, download the code and init the submodules
|
||||
```
|
||||
git clone https://github.com/cypherstack/stack_wallet.git
|
||||
|
@ -92,17 +112,15 @@ cd scripts/linux
|
|||
cd ../..
|
||||
```
|
||||
|
||||
### Build coinlib
|
||||
Coinlib's native secp256k1 library must be built prior to running Stack Wallet. It can be built from within the root `stack_wallet` folder on a...
|
||||
- Linux host for Linux targets: `dart run coinlib:build_linux`, or
|
||||
### Build secp256k1
|
||||
Coinlib requires a secp256k1 library to be built prior to running Stack Wallet. It can be built from within the root `stack_wallet` folder on a...
|
||||
- Linux host for Linux targets: `dart run coinlib:build_linux` (requires [Docker](https://docs.docker.com/engine/install/ubuntu/) or [`podman`](https://podman.io/docs/installation))
|
||||
- Linux host for Windows targets: `dart run coinlib:build_windows_crosscompile`
|
||||
- Windows host: `dart run coinlib:build_windows`
|
||||
- WSL2 host: `dart run coinlib:build_wsl`
|
||||
- macOS host: `dart run coinlib:build_macos`
|
||||
|
||||
To build coinlib on Linux, you will need `docker` (see [installation instructions](https://docs.docker.com/engine/install/ubuntu/)) or [`podman`](https://podman.io/docs/installation) (`sudo apt-get -y install podman`)
|
||||
|
||||
For Windows targets, you can use a `secp256k1.dll` produced by any of the three middle options if the first attempt doesn't succeed.
|
||||
or by using `scripts/linux/build_secp256k1.sh` or `scripts/windows/build_secp256k1.bat`.
|
||||
|
||||
### Run prebuild script
|
||||
|
||||
|
@ -117,7 +135,7 @@ or manually by creating the files referenced in that script with the specified c
|
|||
|
||||
### Build plugins
|
||||
#### Build script: `build_app.sh`
|
||||
The `build_app.sh` script is use to build applications Stack Wallet. View the script's help message with `./build_app.sh -h` for more information on its usage.
|
||||
The `build_app.sh` script is used to build the Stack Wallet and its family of applications. View the script's help message with `./build_app.sh -h` for more information on its usage.
|
||||
|
||||
Options:
|
||||
|
||||
|
@ -144,7 +162,7 @@ cd scripts
|
|||
cd scripts
|
||||
./build_app.sh -a stack_wallet -p linux
|
||||
```
|
||||
|
||||
<!--
|
||||
##### Remove system packages (may be needed for building flutter_libmonero)
|
||||
[`flutter_libmonero`](https://github.com/cypherstack/flutter_libmonero) may have issues building due to conflicts with system packages: if so, follow this section.
|
||||
|
||||
|
@ -156,13 +174,19 @@ for example to find which pre-installed packages you may need to remove with `su
|
|||
```
|
||||
sudo apt-get remove '^libboost.*-dev.*'
|
||||
```
|
||||
<!-- TODO: configure compiler to prefer built over system libraries. Should already use them? -->
|
||||
TODO: configure compiler to prefer built over system libraries. Should already use them? -->
|
||||
|
||||
#### Building plugins and configure for Windows
|
||||
Install dependencies like MXE:
|
||||
```
|
||||
cd scripts
|
||||
cd scripts/windows
|
||||
./deps.sh
|
||||
./build_app.sh -a stack_wallet -p windows
|
||||
```
|
||||
|
||||
and use `scripts/build_app.sh` to build plugins:
|
||||
```
|
||||
cd ..
|
||||
./build_app.sh -a stack_wallet -p windows -v 2.1.0 -b 210
|
||||
```
|
||||
|
||||
### Running
|
||||
|
@ -209,7 +233,7 @@ Download and install [Rust](https://www.rust-lang.org/tools/install). [Rustup](
|
|||
```
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||
source ~/.bashrc
|
||||
rustup install 1.67.1 1.72.0 1.73.0
|
||||
rustup install 1.67.1 1.71.0 1.72.0 1.73.0
|
||||
rustup default 1.67.1
|
||||
cargo install cargo-ndk --version 2.12.7 --locked
|
||||
cargo install cbindgen cargo-lipo
|
||||
|
@ -219,7 +243,7 @@ rustup target add aarch64-apple-ios aarch64-apple-darwin
|
|||
Optionally download [Android Studio](https://developer.android.com/studio) as an IDE and activate its Dart and Flutter plugins. VS Code may work as an alternative, but this is not recommended.
|
||||
|
||||
### Flutter
|
||||
Install [Flutter](https://docs.flutter.dev/get-started/install) 3.22.1 on your Mac host by following [these instructions](https://docs.flutter.dev/get-started/install/macos). Run `flutter doctor` in a terminal to confirm its installation.
|
||||
Install [Flutter](https://docs.flutter.dev/get-started/install) 3.24.3 on your Mac host by following [these instructions](https://docs.flutter.dev/get-started/install/macos). Run `flutter doctor` in a terminal to confirm its installation.
|
||||
|
||||
### Build plugins and configure
|
||||
#### Building plugins for iOS
|
||||
|
@ -272,16 +296,11 @@ Install the following libraries:
|
|||
sudo apt-get install libgtk2.0-dev
|
||||
```
|
||||
|
||||
You will also need to install MXE on the WSL2 Ubuntu 20.04 host and can do so by running `stack_wallet/scripts/windows/deps.sh`:
|
||||
```
|
||||
./stack_wallet/scripts/windows/deps.sh
|
||||
```
|
||||
|
||||
The WSL2 host may optionally be navigated to the `stack_wallet` repository on the Windows host in order to build the plugins in-place and skip the next section in which you copy the `dll`s from WSL2 to Windows. Then build windows `dll` libraries by running the following script on the WSL2 Ubuntu 20.04 host:
|
||||
|
||||
- `stack_wallet/scripts/windows/build_all.sh`
|
||||
|
||||
Copy the resulting `dll`s to their respective positions on the Windows host:
|
||||
If the DLLs were built on the WSL filesystem instead of on Windows, copy the resulting `dll`s to their respective positions on the Windows host:
|
||||
|
||||
- `stack_wallet/crypto_plugins/flutter_libepiccash/scripts/windows/build/libepic_cash_wallet.dll`
|
||||
- `stack_wallet/crypto_plugins/flutter_liblelantus/scripts/windows/build/libmobileliblelantus.dll`
|
||||
|
@ -294,13 +313,13 @@ Copy the resulting `dll`s to their respective positions on the Windows host:
|
|||
Frostdart will be built by the Windows host later.
|
||||
|
||||
### Install Flutter on Windows host
|
||||
Install Flutter 3.22.1 on your Windows host (not in WSL2) by [following their guide](https://docs.flutter.dev/get-started/install/windows/desktop?tab=download#install-the-flutter-sdk) or by cloning https://github.com/flutter/flutter, checking out the `3.22.1` tag, and adding its `flutter/bin` folder to your PATH as in
|
||||
Install Flutter 3.24.3 on your Windows host (not in WSL2) by [following their guide](https://docs.flutter.dev/get-started/install/windows/desktop?tab=download#install-the-flutter-sdk) or by cloning https://github.com/flutter/flutter, checking out the `3.24.3` tag, and adding its `flutter/bin` folder to your PATH as in
|
||||
```bat
|
||||
@echo off
|
||||
set "FLUTTER_DIR=%USERPROFILE%\development\flutter"
|
||||
git clone https://github.com/flutter/flutter.git "%FLUTTER_DIR%"
|
||||
cd /d "%FLUTTER_DIR%"
|
||||
git checkout 3.22.1
|
||||
git checkout 3.24.3
|
||||
setx PATH "%PATH%;%FLUTTER_DIR%\bin"
|
||||
echo Flutter setup completed. Please restart your command prompt.
|
||||
```
|
||||
|
@ -310,7 +329,7 @@ Run `flutter doctor` in PowerShell to confirm its installation.
|
|||
### Rust
|
||||
Install [Rust](https://www.rust-lang.org/tools/install) on the Windows host (not in WSL2). Download the installer from [rustup.rs](https://rustup.rs), make sure it works on the commandline (you may need to open a new terminal), and install the following versions:
|
||||
```
|
||||
rustup install 1.67.1 1.72.0 1.73.0
|
||||
rustup install 1.67.1 1.71.0 1.72.0 1.73.0
|
||||
rustup default 1.67.1
|
||||
cargo install cargo-ndk --version 2.12.7 --locked
|
||||
```
|
||||
|
|
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>
|
|
@ -3,12 +3,14 @@ PODS:
|
|||
- Flutter
|
||||
- MTBBarcodeScanner
|
||||
- SwiftProtobuf
|
||||
- coinlib_flutter (0.3.2):
|
||||
- coinlib_flutter (0.5.0):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- 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):
|
||||
|
@ -83,18 +84,21 @@ PODS:
|
|||
- SDWebImage/Core (5.13.2)
|
||||
- share_plus (0.0.1):
|
||||
- Flutter
|
||||
- sqlite3 (3.46.0):
|
||||
- sqlite3/common (= 3.46.0)
|
||||
- sqlite3/common (3.46.0)
|
||||
- sqlite3/fts5 (3.46.0):
|
||||
- "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/perf-threadsafe (3.46.0):
|
||||
- "sqlite3/fts5 (3.46.0+1)":
|
||||
- sqlite3/common
|
||||
- sqlite3/rtree (3.46.0):
|
||||
- "sqlite3/perf-threadsafe (3.46.0+1)":
|
||||
- sqlite3/common
|
||||
- "sqlite3/rtree (3.46.0+1)":
|
||||
- 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: 6abec900d67762a6e7ccfd567a3cd3ae00bbee35
|
||||
coinlib_flutter: 9275e8255ef67d3da33beb6e117d09ced4f46eb5
|
||||
connectivity_plus: 07c49e96d7fc92bc9920617b83238c4d178b446a
|
||||
device_info_plus: 7545d84d8d1b896cb16a4ff98c19f07ec4b298ea
|
||||
devicelocale: b22617f40038496deffba44747101255cee005b0
|
||||
cs_monero_flutter_libs: 43cda3474c2bc907f2b2b5bb26fd89cb864fcfc6
|
||||
device_info_plus: 97af1d7e84681a90d0693e63169a5d50e0839a0d
|
||||
devicelocale: 35ba84dc7f45f527c3001535d8c8d104edd5d926
|
||||
DKImagePickerController: b512c28220a2b8ac7419f21c491fc8534b7601ac
|
||||
DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179
|
||||
file_picker: ce3938a0df3cc1ef404671531facef740d03f920
|
||||
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: 13825b8a9334a850581300559b8839134b124670
|
||||
isar_flutter_libs: b69f437aeab9c521821c3f376198c4371fa21073
|
||||
integration_test: 252f60fa39af5e17c3aa9899d35d908a0721b573
|
||||
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
|
||||
sqlite3: 154b084339ede06960a5b3c8160066adc9176b7d
|
||||
sqlite3_flutter_libs: 0d611efdf6d1c9297d5ab03dab21b75aeebdae31
|
||||
share_plus: c3fef564749587fc939ef86ffb283ceac0baf9f5
|
||||
sqlite3: 292c3e1bfe89f64e51ea7fc7dab9182a017c8630
|
||||
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,7 +1,7 @@
|
|||
import UIKit
|
||||
import Flutter
|
||||
|
||||
@UIApplicationMain
|
||||
@main
|
||||
@objc class AppDelegate: FlutterAppDelegate {
|
||||
override func application(
|
||||
_ application: UIApplication,
|
||||
|
|
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>
|
|
@ -27,6 +27,8 @@ abstract class AppConfig {
|
|||
|
||||
static List<CryptoCurrency> get coins => _supportedCoins;
|
||||
|
||||
static ({String from, String to}) get swapDefaults => _swapDefaults;
|
||||
|
||||
static bool get isSingleCoinApp => coins.length == 1;
|
||||
|
||||
static CryptoCurrency? getCryptoCurrencyFor(String coinIdentifier) {
|
||||
|
|
|
@ -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([
|
||||
|
|
|
@ -9,6 +9,7 @@ import 'package:sqlite3/sqlite3.dart';
|
|||
import 'package:uuid/uuid.dart';
|
||||
|
||||
import '../../electrumx_rpc/electrumx_client.dart';
|
||||
import '../../models/electrumx_response/spark_models.dart';
|
||||
import '../../utilities/extensions/extensions.dart';
|
||||
import '../../utilities/logger.dart';
|
||||
import '../../utilities/stack_file_system.dart';
|
||||
|
@ -30,7 +31,7 @@ void _debugLog(Object? object) {
|
|||
}
|
||||
|
||||
abstract class _FiroCache {
|
||||
static const int _setCacheVersion = 1;
|
||||
static const int _setCacheVersion = 2;
|
||||
static const int _tagsCacheVersion = 2;
|
||||
|
||||
static final networks = [
|
||||
|
@ -134,7 +135,7 @@ abstract class _FiroCache {
|
|||
blockHash TEXT NOT NULL,
|
||||
setHash TEXT NOT NULL,
|
||||
groupId INTEGER NOT NULL,
|
||||
timestampUTC INTEGER NOT NULL,
|
||||
size INTEGER NOT NULL,
|
||||
UNIQUE (blockHash, setHash, groupId)
|
||||
);
|
||||
|
||||
|
@ -143,7 +144,8 @@ abstract class _FiroCache {
|
|||
serialized TEXT NOT NULL,
|
||||
txHash TEXT NOT NULL,
|
||||
context TEXT NOT NULL,
|
||||
UNIQUE(serialized, txHash, context)
|
||||
groupId INTEGER NOT NULL,
|
||||
UNIQUE(serialized, txHash, context, groupId)
|
||||
);
|
||||
|
||||
CREATE TABLE SparkSetCoins (
|
||||
|
|
|
@ -6,6 +6,8 @@ typedef LTagPair = ({String tag, String txid});
|
|||
/// background isolate and [FiroCacheCoordinator] should manage that isolate
|
||||
abstract class FiroCacheCoordinator {
|
||||
static final Map<CryptoCurrencyNetwork, _FiroCacheWorker> _workers = {};
|
||||
static final Map<CryptoCurrencyNetwork, Mutex> _tagLocks = {};
|
||||
static final Map<CryptoCurrencyNetwork, Mutex> _setLocks = {};
|
||||
|
||||
static bool _init = false;
|
||||
static Future<void> init() async {
|
||||
|
@ -15,6 +17,8 @@ abstract class FiroCacheCoordinator {
|
|||
_init = true;
|
||||
await _FiroCache.init();
|
||||
for (final network in _FiroCache.networks) {
|
||||
_tagLocks[network] = Mutex();
|
||||
_setLocks[network] = Mutex();
|
||||
_workers[network] = await _FiroCacheWorker.spawn(network);
|
||||
}
|
||||
}
|
||||
|
@ -31,11 +35,23 @@ abstract class FiroCacheCoordinator {
|
|||
final usedTagsCacheFile = File(
|
||||
"${dir.path}/${_FiroCache.sparkUsedTagsCacheFileName(network)}",
|
||||
);
|
||||
final int bytes =
|
||||
((await setCacheFile.exists()) ? await setCacheFile.length() : 0) +
|
||||
((await usedTagsCacheFile.exists())
|
||||
? await usedTagsCacheFile.length()
|
||||
: 0);
|
||||
|
||||
final setSize =
|
||||
(await setCacheFile.exists()) ? await setCacheFile.length() : 0;
|
||||
final tagsSize = (await usedTagsCacheFile.exists())
|
||||
? await usedTagsCacheFile.length()
|
||||
: 0;
|
||||
|
||||
Logging.instance.log(
|
||||
"Spark cache used tags size: $tagsSize",
|
||||
level: LogLevel.Debug,
|
||||
);
|
||||
Logging.instance.log(
|
||||
"Spark cache anon set size: $setSize",
|
||||
level: LogLevel.Debug,
|
||||
);
|
||||
|
||||
final int bytes = tagsSize + setSize;
|
||||
|
||||
if (bytes < 1024) {
|
||||
return '$bytes B';
|
||||
|
@ -55,43 +71,96 @@ abstract class FiroCacheCoordinator {
|
|||
ElectrumXClient client,
|
||||
CryptoCurrencyNetwork network,
|
||||
) async {
|
||||
final count = await FiroCacheCoordinator.getUsedCoinTagsCount(network);
|
||||
final unhashedTags = await client.getSparkUnhashedUsedCoinsTagsWithTxHashes(
|
||||
startNumber: count,
|
||||
);
|
||||
if (unhashedTags.isNotEmpty) {
|
||||
await _workers[network]!.runTask(
|
||||
FCTask(
|
||||
func: FCFuncName._updateSparkUsedTagsWith,
|
||||
data: unhashedTags,
|
||||
),
|
||||
await _tagLocks[network]!.protect(() async {
|
||||
final count = await FiroCacheCoordinator.getUsedCoinTagsCount(network);
|
||||
final unhashedTags =
|
||||
await client.getSparkUnhashedUsedCoinsTagsWithTxHashes(
|
||||
startNumber: count,
|
||||
);
|
||||
}
|
||||
if (unhashedTags.isNotEmpty) {
|
||||
await _workers[network]!.runTask(
|
||||
FCTask(
|
||||
func: FCFuncName._updateSparkUsedTagsWith,
|
||||
data: unhashedTags,
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static Future<void> runFetchAndUpdateSparkAnonSetCacheForGroupId(
|
||||
int groupId,
|
||||
ElectrumXClient client,
|
||||
CryptoCurrencyNetwork network,
|
||||
void Function(int countFetched, int totalCount)? progressUpdated,
|
||||
) async {
|
||||
final blockhashResult =
|
||||
await FiroCacheCoordinator.getLatestSetInfoForGroupId(
|
||||
groupId,
|
||||
network,
|
||||
);
|
||||
final blockHash = blockhashResult?.blockHash ?? "";
|
||||
await _setLocks[network]!.protect(() async {
|
||||
const sectorSize =
|
||||
1500; // chosen as a somewhat decent value. Could be changed in the future if wanted/needed
|
||||
final prevMeta = await FiroCacheCoordinator.getLatestSetInfoForGroupId(
|
||||
groupId,
|
||||
network,
|
||||
);
|
||||
|
||||
final json = await client.getSparkAnonymitySet(
|
||||
coinGroupId: groupId.toString(),
|
||||
startBlockHash: blockHash.toHexReversedFromBase64,
|
||||
);
|
||||
final prevSize = prevMeta?.size ?? 0;
|
||||
|
||||
await _workers[network]!.runTask(
|
||||
FCTask(
|
||||
func: FCFuncName._updateSparkAnonSetCoinsWith,
|
||||
data: (groupId, json),
|
||||
),
|
||||
);
|
||||
final meta = await client.getSparkAnonymitySetMeta(
|
||||
coinGroupId: groupId,
|
||||
);
|
||||
|
||||
progressUpdated?.call(prevSize, meta.size);
|
||||
|
||||
if (prevMeta?.blockHash == meta.blockHash) {
|
||||
Logging.instance.log(
|
||||
"prevMeta?.blockHash == meta.blockHash",
|
||||
level: LogLevel.Debug,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
final numberOfCoinsToFetch = meta.size - prevSize;
|
||||
|
||||
final fullSectorCount = numberOfCoinsToFetch ~/ sectorSize;
|
||||
final remainder = numberOfCoinsToFetch % sectorSize;
|
||||
|
||||
final List<dynamic> coins = [];
|
||||
|
||||
for (int i = 0; i < fullSectorCount; i++) {
|
||||
final start = (i * sectorSize);
|
||||
final data = await client.getSparkAnonymitySetBySector(
|
||||
coinGroupId: groupId,
|
||||
latestBlock: meta.blockHash,
|
||||
startIndex: start,
|
||||
endIndex: start + sectorSize,
|
||||
);
|
||||
progressUpdated?.call(start + sectorSize, numberOfCoinsToFetch);
|
||||
|
||||
coins.addAll(data);
|
||||
}
|
||||
|
||||
if (remainder > 0) {
|
||||
final data = await client.getSparkAnonymitySetBySector(
|
||||
coinGroupId: groupId,
|
||||
latestBlock: meta.blockHash,
|
||||
startIndex: numberOfCoinsToFetch - remainder,
|
||||
endIndex: numberOfCoinsToFetch,
|
||||
);
|
||||
progressUpdated?.call(numberOfCoinsToFetch, numberOfCoinsToFetch);
|
||||
|
||||
coins.addAll(data);
|
||||
}
|
||||
|
||||
final result = coins
|
||||
.map((e) => RawSparkCoin.fromRPCResponse(e as List, groupId))
|
||||
.toList();
|
||||
|
||||
await _workers[network]!.runTask(
|
||||
FCTask(
|
||||
func: FCFuncName._updateSparkAnonSetCoinsWith,
|
||||
data: (meta, result),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
|
@ -165,28 +234,29 @@ abstract class FiroCacheCoordinator {
|
|||
);
|
||||
}
|
||||
|
||||
static Future<
|
||||
List<
|
||||
({
|
||||
String serialized,
|
||||
String txHash,
|
||||
String context,
|
||||
})>> getSetCoinsForGroupId(
|
||||
static Future<List<RawSparkCoin>> getSetCoinsForGroupId(
|
||||
int groupId, {
|
||||
int? newerThanTimeStamp,
|
||||
String? afterBlockHash,
|
||||
required CryptoCurrencyNetwork network,
|
||||
}) async {
|
||||
final resultSet = await _Reader._getSetCoinsForGroupId(
|
||||
groupId,
|
||||
db: _FiroCache.setCacheDB(network),
|
||||
newerThanTimeStamp: newerThanTimeStamp,
|
||||
);
|
||||
final resultSet = afterBlockHash == null
|
||||
? await _Reader._getSetCoinsForGroupId(
|
||||
groupId,
|
||||
db: _FiroCache.setCacheDB(network),
|
||||
)
|
||||
: await _Reader._getSetCoinsForGroupIdAndBlockHash(
|
||||
groupId,
|
||||
afterBlockHash,
|
||||
db: _FiroCache.setCacheDB(network),
|
||||
);
|
||||
|
||||
return resultSet
|
||||
.map(
|
||||
(row) => (
|
||||
(row) => RawSparkCoin(
|
||||
serialized: row["serialized"] as String,
|
||||
txHash: row["txHash"] as String,
|
||||
context: row["context"] as String,
|
||||
groupId: groupId,
|
||||
),
|
||||
)
|
||||
.toList()
|
||||
|
@ -194,12 +264,7 @@ abstract class FiroCacheCoordinator {
|
|||
.toList();
|
||||
}
|
||||
|
||||
static Future<
|
||||
({
|
||||
String blockHash,
|
||||
String setHash,
|
||||
int timestampUTC,
|
||||
})?> getLatestSetInfoForGroupId(
|
||||
static Future<SparkAnonymitySetMeta?> getLatestSetInfoForGroupId(
|
||||
int groupId,
|
||||
CryptoCurrencyNetwork network,
|
||||
) async {
|
||||
|
@ -212,10 +277,11 @@ abstract class FiroCacheCoordinator {
|
|||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
return SparkAnonymitySetMeta(
|
||||
coinGroupId: groupId,
|
||||
blockHash: result.first["blockHash"] as String,
|
||||
setHash: result.first["setHash"] as String,
|
||||
timestampUTC: result.first["timestampUTC"] as int,
|
||||
size: result.first["size"] as int,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -8,21 +8,15 @@ abstract class _Reader {
|
|||
static Future<ResultSet> _getSetCoinsForGroupId(
|
||||
int groupId, {
|
||||
required Database db,
|
||||
int? newerThanTimeStamp,
|
||||
}) async {
|
||||
String query = """
|
||||
SELECT sc.serialized, sc.txHash, sc.context
|
||||
final query = """
|
||||
SELECT sc.serialized, sc.txHash, sc.context, sc.groupId
|
||||
FROM SparkSet AS ss
|
||||
JOIN SparkSetCoins AS ssc ON ss.id = ssc.setId
|
||||
JOIN SparkCoin AS sc ON ssc.coinId = sc.id
|
||||
WHERE ss.groupId = $groupId
|
||||
WHERE ss.groupId = $groupId;
|
||||
""";
|
||||
|
||||
if (newerThanTimeStamp != null) {
|
||||
query += " AND ss.timestampUTC"
|
||||
" > $newerThanTimeStamp";
|
||||
}
|
||||
|
||||
return db.select("$query;");
|
||||
}
|
||||
|
||||
|
@ -31,16 +25,45 @@ abstract class _Reader {
|
|||
required Database db,
|
||||
}) async {
|
||||
final query = """
|
||||
SELECT ss.blockHash, ss.setHash, ss.timestampUTC
|
||||
SELECT ss.blockHash, ss.setHash, ss.size
|
||||
FROM SparkSet ss
|
||||
WHERE ss.groupId = $groupId
|
||||
ORDER BY ss.timestampUTC DESC
|
||||
ORDER BY ss.size DESC
|
||||
LIMIT 1;
|
||||
""";
|
||||
|
||||
return db.select("$query;");
|
||||
}
|
||||
|
||||
static Future<ResultSet> _getSetCoinsForGroupIdAndBlockHash(
|
||||
int groupId,
|
||||
String blockHash, {
|
||||
required Database db,
|
||||
}) async {
|
||||
const query = """
|
||||
WITH TargetBlock AS (
|
||||
SELECT id
|
||||
FROM SparkSet
|
||||
WHERE blockHash = ?
|
||||
),
|
||||
TargetSets AS (
|
||||
SELECT id AS setId
|
||||
FROM SparkSet
|
||||
WHERE groupId = ? AND id > (SELECT id FROM TargetBlock)
|
||||
)
|
||||
SELECT
|
||||
SparkCoin.serialized,
|
||||
SparkCoin.txHash,
|
||||
SparkCoin.context,
|
||||
SparkCoin.groupId
|
||||
FROM SparkSetCoins
|
||||
JOIN SparkCoin ON SparkSetCoins.coinId = SparkCoin.id
|
||||
WHERE SparkSetCoins.setId IN (SELECT setId FROM TargetSets);
|
||||
""";
|
||||
|
||||
return db.select("$query;", [blockHash, groupId]);
|
||||
}
|
||||
|
||||
static Future<bool> _checkSetInfoForGroupIdExists(
|
||||
int groupId, {
|
||||
required Database db,
|
||||
|
|
|
@ -48,7 +48,11 @@ class _FiroCacheWorker {
|
|||
try {
|
||||
await Isolate.spawn(
|
||||
_startWorkerIsolate,
|
||||
(initPort.sendPort, setCacheFilePath, usedTagsCacheFilePath),
|
||||
(
|
||||
initPort.sendPort,
|
||||
setCacheFilePath,
|
||||
usedTagsCacheFilePath,
|
||||
),
|
||||
);
|
||||
} catch (_) {
|
||||
initPort.close();
|
||||
|
@ -90,7 +94,8 @@ class _FiroCacheWorker {
|
|||
final FCResult result;
|
||||
switch (task.func) {
|
||||
case FCFuncName._updateSparkAnonSetCoinsWith:
|
||||
final data = task.data as (int, Map<String, dynamic>);
|
||||
final data =
|
||||
task.data as (SparkAnonymitySetMeta, List<RawSparkCoin>);
|
||||
result = _updateSparkAnonSetCoinsWith(
|
||||
setCacheDb,
|
||||
data.$2,
|
||||
|
|
|
@ -19,8 +19,8 @@ FCResult _updateSparkUsedTagsWith(
|
|||
) {
|
||||
// hash the tags here since this function is called in a background isolate
|
||||
final hashedTags = LibSpark.hashTags(
|
||||
base64Tags: tags.map((e) => e[0] as String),
|
||||
);
|
||||
base64Tags: tags.map((e) => e[0] as String).toSet(),
|
||||
).toList();
|
||||
|
||||
if (hashedTags.isEmpty) {
|
||||
// nothing to add, return early
|
||||
|
@ -52,29 +52,13 @@ FCResult _updateSparkUsedTagsWith(
|
|||
// ================== write to spark anon set cache ==========================
|
||||
|
||||
/// update the sqlite cache
|
||||
/// Expected json format:
|
||||
/// {
|
||||
/// "blockHash": "someBlockHash",
|
||||
/// "setHash": "someSetHash",
|
||||
/// "coins": [
|
||||
/// ["serliazed1", "hash1", "context1"],
|
||||
/// ["serliazed2", "hash2", "context2"],
|
||||
/// ...
|
||||
/// ["serliazed3", "hash3", "context3"],
|
||||
/// ["serliazed4", "hash4", "context4"],
|
||||
/// ],
|
||||
/// }
|
||||
///
|
||||
/// returns true if successful, otherwise false
|
||||
FCResult _updateSparkAnonSetCoinsWith(
|
||||
Database db,
|
||||
Map<String, dynamic> json,
|
||||
int groupId,
|
||||
final List<RawSparkCoin> coinsRaw,
|
||||
SparkAnonymitySetMeta meta,
|
||||
) {
|
||||
final blockHash = json["blockHash"] as String;
|
||||
final setHash = json["setHash"] as String;
|
||||
final coinsRaw = json["coins"] as List;
|
||||
|
||||
if (coinsRaw.isEmpty) {
|
||||
// no coins to actually insert
|
||||
return FCResult(success: true);
|
||||
|
@ -87,9 +71,9 @@ FCResult _updateSparkAnonSetCoinsWith(
|
|||
WHERE blockHash = ? AND setHash = ? AND groupId = ?;
|
||||
""",
|
||||
[
|
||||
blockHash,
|
||||
setHash,
|
||||
groupId,
|
||||
meta.blockHash,
|
||||
meta.setHash,
|
||||
meta.coinGroupId,
|
||||
],
|
||||
);
|
||||
|
||||
|
@ -98,59 +82,28 @@ FCResult _updateSparkAnonSetCoinsWith(
|
|||
return FCResult(success: true);
|
||||
}
|
||||
|
||||
final coins = coinsRaw
|
||||
.map(
|
||||
(e) => [
|
||||
e[0] as String,
|
||||
e[1] as String,
|
||||
e[2] as String,
|
||||
],
|
||||
)
|
||||
.toList()
|
||||
.reversed;
|
||||
|
||||
final timestamp = DateTime.now().toUtc().millisecondsSinceEpoch ~/ 1000;
|
||||
final coins = coinsRaw.reversed;
|
||||
|
||||
db.execute("BEGIN;");
|
||||
try {
|
||||
db.execute(
|
||||
"""
|
||||
INSERT INTO SparkSet (blockHash, setHash, groupId, timestampUTC)
|
||||
INSERT INTO SparkSet (blockHash, setHash, groupId, size)
|
||||
VALUES (?, ?, ?, ?);
|
||||
""",
|
||||
[blockHash, setHash, groupId, timestamp],
|
||||
[meta.blockHash, meta.setHash, meta.coinGroupId, meta.size],
|
||||
);
|
||||
final setId = db.lastInsertRowId;
|
||||
|
||||
for (final coin in coins) {
|
||||
int coinId;
|
||||
try {
|
||||
// try to insert and get row id
|
||||
db.execute(
|
||||
"""
|
||||
INSERT INTO SparkCoin (serialized, txHash, context)
|
||||
VALUES (?, ?, ?);
|
||||
db.execute(
|
||||
"""
|
||||
INSERT INTO SparkCoin (serialized, txHash, context, groupId)
|
||||
VALUES (?, ?, ?, ?);
|
||||
""",
|
||||
coin,
|
||||
);
|
||||
coinId = db.lastInsertRowId;
|
||||
} on SqliteException catch (e) {
|
||||
// if there already is a matching coin in the db
|
||||
// just grab its row id
|
||||
if (e.extendedResultCode == 2067) {
|
||||
final result = db.select(
|
||||
"""
|
||||
SELECT id
|
||||
FROM SparkCoin
|
||||
WHERE serialized = ? AND txHash = ? AND context = ?;
|
||||
""",
|
||||
coin,
|
||||
);
|
||||
coinId = result.first["id"] as int;
|
||||
} else {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
[coin.serialized, coin.txHash, coin.context, coin.groupId],
|
||||
);
|
||||
final coinId = db.lastInsertRowId;
|
||||
|
||||
// finally add the row id to the newly added set
|
||||
db.execute(
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
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 {
|
||||
|
@ -8,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 = {};
|
||||
|
@ -22,28 +27,53 @@ 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>();
|
||||
_subscriptions[key] = client.subscribeHeaders().listen((event) {
|
||||
_heights[key] = event.height;
|
||||
_subscriptions[key] = client.subscribeHeaders().listen(
|
||||
(event) {
|
||||
_heights[key] = event.height;
|
||||
|
||||
if (!_heightCompleters[key]!.isCompleted) {
|
||||
_heightCompleters[key]!.complete(event.height);
|
||||
}
|
||||
});
|
||||
if (!_heightCompleters[key]!.isCompleted) {
|
||||
_heightCompleters[key]!.complete(event.height);
|
||||
}
|
||||
},
|
||||
onError: (Object err, StackTrace s) => Logging.instance.log(
|
||||
"ClientManager listen: $err\n$s",
|
||||
level: LogLevel.Error,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<int> getChainHeightFor(CryptoCurrency cryptoCurrency) async {
|
||||
|
@ -60,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);
|
||||
|
@ -72,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 {
|
||||
|
@ -91,6 +135,7 @@ class ClientManager {
|
|||
_heightCompleters.clear();
|
||||
_heights.clear();
|
||||
_subscriptions.clear();
|
||||
_mapNet.clear();
|
||||
_map.clear();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ import 'package:mutex/mutex.dart';
|
|||
import 'package:stream_channel/stream_channel.dart';
|
||||
|
||||
import '../exceptions/electrumx/no_such_transaction.dart';
|
||||
import '../models/electrumx_response/spark_models.dart';
|
||||
import '../services/event_bus/events/global/tor_connection_status_changed_event.dart';
|
||||
import '../services/event_bus/events/global/tor_status_changed_event.dart';
|
||||
import '../services/event_bus/global_event_bus.dart';
|
||||
|
@ -29,19 +30,17 @@ 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';
|
||||
|
||||
typedef SparkMempoolData = ({
|
||||
String txid,
|
||||
List<String> serialContext,
|
||||
List<String> lTags,
|
||||
List<String> coins,
|
||||
});
|
||||
|
||||
class WifiOnlyException implements Exception {}
|
||||
|
||||
class TorOnlyException implements Exception {}
|
||||
|
||||
class ClearnetOnlyException implements Exception {}
|
||||
|
||||
class ElectrumXNode {
|
||||
ElectrumXNode({
|
||||
required this.address,
|
||||
|
@ -49,12 +48,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 +66,8 @@ class ElectrumXNode {
|
|||
name: node.name,
|
||||
id: node.id,
|
||||
useSSL: node.useSSL,
|
||||
torEnabled: node.torEnabled,
|
||||
clearnetEnabled: node.clearnetEnabled,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -74,6 +79,7 @@ class ElectrumXNode {
|
|||
|
||||
class ElectrumXClient {
|
||||
final CryptoCurrency cryptoCurrency;
|
||||
final TorPlainNetworkOption netType;
|
||||
|
||||
String get host => _host;
|
||||
late String _host;
|
||||
|
@ -90,12 +96,13 @@ class ElectrumXClient {
|
|||
ElectrumClient? getElectrumAdapter() =>
|
||||
ClientManager.sharedInstance.getClient(
|
||||
cryptoCurrency: cryptoCurrency,
|
||||
netType: netType,
|
||||
);
|
||||
|
||||
late Prefs _prefs;
|
||||
late TorService _torService;
|
||||
|
||||
List<ElectrumXNode>? failovers;
|
||||
late final List<ElectrumXNode> _failovers;
|
||||
int currentFailoverIndex = -1;
|
||||
|
||||
final Duration connectionTimeoutForSpecialCaseJsonRPCClients;
|
||||
|
@ -119,6 +126,7 @@ class ElectrumXClient {
|
|||
required int port,
|
||||
required bool useSSL,
|
||||
required Prefs prefs,
|
||||
required this.netType,
|
||||
required List<ElectrumXNode> failovers,
|
||||
required this.cryptoCurrency,
|
||||
this.connectionTimeoutForSpecialCaseJsonRPCClients =
|
||||
|
@ -131,6 +139,7 @@ class ElectrumXClient {
|
|||
_host = host;
|
||||
_port = port;
|
||||
_useSSL = useSSL;
|
||||
_failovers = failovers;
|
||||
|
||||
final bus = globalEventBusForTesting ?? GlobalEventBus.instance;
|
||||
|
||||
|
@ -168,6 +177,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 +203,10 @@ class ElectrumXClient {
|
|||
failovers: failovers,
|
||||
globalEventBusForTesting: globalEventBusForTesting,
|
||||
cryptoCurrency: cryptoCurrency,
|
||||
netType: TorPlainNetworkOption.fromNodeData(
|
||||
node.torEnabled,
|
||||
node.clearnetEnabled,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -236,6 +250,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.
|
||||
|
@ -253,9 +279,11 @@ class ElectrumXClient {
|
|||
usePort = port;
|
||||
useUseSSL = useSSL;
|
||||
} else {
|
||||
useHost = failovers![currentFailoverIndex].address;
|
||||
usePort = failovers![currentFailoverIndex].port;
|
||||
useUseSSL = failovers![currentFailoverIndex].useSSL;
|
||||
_electrumAdapterChannel = null;
|
||||
await ClientManager.sharedInstance.remove(cryptoCurrency: cryptoCurrency);
|
||||
useHost = _failovers[currentFailoverIndex].address;
|
||||
usePort = _failovers[currentFailoverIndex].port;
|
||||
useUseSSL = _failovers[currentFailoverIndex].useSSL;
|
||||
}
|
||||
|
||||
_electrumAdapterChannel ??= await electrum_adapter.connect(
|
||||
|
@ -288,9 +316,10 @@ class ElectrumXClient {
|
|||
);
|
||||
}
|
||||
|
||||
ClientManager.sharedInstance.addClient(
|
||||
await ClientManager.sharedInstance.addClient(
|
||||
newClient,
|
||||
cryptoCurrency: cryptoCurrency,
|
||||
netType: netType,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -352,6 +381,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) {
|
||||
|
@ -366,7 +399,12 @@ class ElectrumXClient {
|
|||
rethrow;
|
||||
}
|
||||
} catch (e) {
|
||||
if (failovers != null && currentFailoverIndex < failovers!.length - 1) {
|
||||
final errorMessage = e.toString();
|
||||
Logging.instance.log("$host $e", level: LogLevel.Debug);
|
||||
if (errorMessage.contains("JSON-RPC error")) {
|
||||
currentFailoverIndex = _failovers.length;
|
||||
}
|
||||
if (currentFailoverIndex < _failovers.length - 1) {
|
||||
currentFailoverIndex++;
|
||||
return request(
|
||||
command: command,
|
||||
|
@ -442,6 +480,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) {
|
||||
|
@ -455,7 +497,7 @@ class ElectrumXClient {
|
|||
rethrow;
|
||||
}
|
||||
} catch (e) {
|
||||
if (failovers != null && currentFailoverIndex < failovers!.length - 1) {
|
||||
if (currentFailoverIndex < _failovers.length - 1) {
|
||||
currentFailoverIndex++;
|
||||
return batchRequest(
|
||||
command: command,
|
||||
|
@ -488,9 +530,17 @@ class ElectrumXClient {
|
|||
return await request(
|
||||
requestID: requestID,
|
||||
command: 'server.ping',
|
||||
requestTimeout: const Duration(seconds: 2),
|
||||
requestTimeout: const Duration(seconds: 30),
|
||||
retries: retryCount,
|
||||
).timeout(const Duration(seconds: 2)) as bool;
|
||||
).timeout(
|
||||
const Duration(seconds: 30),
|
||||
onTimeout: () {
|
||||
Logging.instance.log(
|
||||
"ElectrumxClient.ping timed out with retryCount=$retryCount, host=$_host",
|
||||
level: LogLevel.Debug,
|
||||
);
|
||||
},
|
||||
) as bool;
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
|
@ -986,29 +1036,30 @@ class ElectrumXClient {
|
|||
/// "b476ed2b374bb081ea51d111f68f0136252521214e213d119b8dc67b92f5a390",
|
||||
/// ]
|
||||
/// }
|
||||
Future<List<Map<String, dynamic>>> getSparkMintMetaData({
|
||||
String? requestID,
|
||||
required List<String> sparkCoinHashes,
|
||||
}) async {
|
||||
try {
|
||||
Logging.instance.log(
|
||||
"attempting to fetch spark.getsparkmintmetadata...",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
await checkElectrumAdapter();
|
||||
final List<dynamic> response =
|
||||
await (getElectrumAdapter() as FiroElectrumClient)
|
||||
.getSparkMintMetaData(sparkCoinHashes: sparkCoinHashes);
|
||||
Logging.instance.log(
|
||||
"Fetching spark.getsparkmintmetadata finished",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
return List<Map<String, dynamic>>.from(response);
|
||||
} catch (e) {
|
||||
Logging.instance.log(e, level: LogLevel.Error);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
/// NOT USED?
|
||||
// Future<List<Map<String, dynamic>>> getSparkMintMetaData({
|
||||
// String? requestID,
|
||||
// required List<String> sparkCoinHashes,
|
||||
// }) async {
|
||||
// try {
|
||||
// Logging.instance.log(
|
||||
// "attempting to fetch spark.getsparkmintmetadata...",
|
||||
// level: LogLevel.Info,
|
||||
// );
|
||||
// await checkElectrumAdapter();
|
||||
// final List<dynamic> response =
|
||||
// await (getElectrumAdapter() as FiroElectrumClient)
|
||||
// .getSparkMintMetaData(sparkCoinHashes: sparkCoinHashes);
|
||||
// Logging.instance.log(
|
||||
// "Fetching spark.getsparkmintmetadata finished",
|
||||
// level: LogLevel.Info,
|
||||
// );
|
||||
// return List<Map<String, dynamic>>.from(response);
|
||||
// } catch (e) {
|
||||
// Logging.instance.log(e, level: LogLevel.Error);
|
||||
// rethrow;
|
||||
// }
|
||||
// }
|
||||
|
||||
/// Returns the latest Spark set id
|
||||
///
|
||||
|
@ -1084,7 +1135,7 @@ class ElectrumXClient {
|
|||
final List<SparkMempoolData> result = [];
|
||||
for (final entry in map.entries) {
|
||||
result.add(
|
||||
(
|
||||
SparkMempoolData(
|
||||
txid: entry.key,
|
||||
serialContext:
|
||||
List<String>.from(entry.value["serial_context"] as List),
|
||||
|
@ -1140,6 +1191,98 @@ class ElectrumXClient {
|
|||
rethrow;
|
||||
}
|
||||
}
|
||||
// ======== New Paginated Endpoints ==========================================
|
||||
|
||||
Future<SparkAnonymitySetMeta> getSparkAnonymitySetMeta({
|
||||
String? requestID,
|
||||
required int coinGroupId,
|
||||
}) async {
|
||||
try {
|
||||
const command = "spark.getsparkanonymitysetmeta";
|
||||
Logging.instance.log(
|
||||
"[${getElectrumAdapter()?.host}] => attempting to fetch $command...",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
|
||||
final start = DateTime.now();
|
||||
final response = await request(
|
||||
requestID: requestID,
|
||||
command: command,
|
||||
args: [
|
||||
"$coinGroupId",
|
||||
],
|
||||
);
|
||||
|
||||
final map = Map<String, dynamic>.from(response as Map);
|
||||
|
||||
final result = SparkAnonymitySetMeta(
|
||||
coinGroupId: coinGroupId,
|
||||
blockHash: map["blockHash"] as String,
|
||||
setHash: map["setHash"] as String,
|
||||
size: map["size"] as int,
|
||||
);
|
||||
|
||||
Logging.instance.log(
|
||||
"Finished ElectrumXClient.getSparkAnonymitySetMeta("
|
||||
"requestID=$requestID, "
|
||||
"coinGroupId=$coinGroupId"
|
||||
"). Set meta=$result, "
|
||||
"Duration=${DateTime.now().difference(start)}",
|
||||
level: LogLevel.Debug,
|
||||
);
|
||||
|
||||
return result;
|
||||
} catch (e) {
|
||||
Logging.instance.log(e, level: LogLevel.Error);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<dynamic>> getSparkAnonymitySetBySector({
|
||||
String? requestID,
|
||||
required int coinGroupId,
|
||||
required String latestBlock,
|
||||
required int startIndex, // inclusive
|
||||
required int endIndex, // exclusive
|
||||
}) async {
|
||||
try {
|
||||
const command =
|
||||
"spark.getsparkanonymitysetsector"; // TODO verify this will be correct
|
||||
final start = DateTime.now();
|
||||
final response = await request(
|
||||
requestID: requestID,
|
||||
command: command,
|
||||
args: [
|
||||
"$coinGroupId",
|
||||
latestBlock,
|
||||
"$startIndex",
|
||||
"$endIndex",
|
||||
],
|
||||
);
|
||||
|
||||
final map = Map<String, dynamic>.from(response as Map);
|
||||
|
||||
final result = map["coins"] as List;
|
||||
|
||||
Logging.instance.log(
|
||||
"Finished ElectrumXClient.getSparkAnonymitySetBySector("
|
||||
"requestID=$requestID, "
|
||||
"coinGroupId=$coinGroupId, "
|
||||
"latestBlock=$latestBlock, "
|
||||
"startIndex=$startIndex, "
|
||||
"endIndex=$endIndex"
|
||||
"). # of coins=${result.length}, "
|
||||
"Duration=${DateTime.now().difference(start)}",
|
||||
level: LogLevel.Debug,
|
||||
);
|
||||
|
||||
return result;
|
||||
} catch (e) {
|
||||
Logging.instance.log(e, level: LogLevel.Error);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
|
||||
Future<bool> isMasterNodeCollateral({
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
class NodeTorMismatchConfigException implements Exception {
|
||||
final String message;
|
||||
|
||||
NodeTorMismatchConfigException({required this.message});
|
||||
|
||||
@override
|
||||
String toString() => message;
|
||||
}
|
|
@ -13,15 +13,11 @@ 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_libmonero/monero/monero.dart';
|
||||
import 'package:flutter_libmonero/wownero/wownero.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
|
@ -94,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;
|
||||
|
@ -172,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,
|
||||
|
@ -237,12 +226,9 @@ void main(List<String> args) async {
|
|||
}
|
||||
}
|
||||
|
||||
monero.onStartup();
|
||||
wownero.onStartup();
|
||||
|
||||
// SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual,
|
||||
// overlays: [SystemUiOverlay.bottom]);
|
||||
await NotificationApi.init();
|
||||
unawaited(NotificationApi.init());
|
||||
|
||||
await loadCoinlibFuture;
|
||||
|
||||
|
@ -378,7 +364,8 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
|
|||
// TODO: this should probably run unawaited. Keep commented out for now as proper community nodes ui hasn't been implemented yet
|
||||
// unawaited(_nodeService.updateCommunityNodes());
|
||||
|
||||
if (AppConfig.hasFeature(AppFeature.swap)) {
|
||||
if (AppConfig.hasFeature(AppFeature.swap) &&
|
||||
ref.read(prefsChangeNotifierProvider).enableExchange) {
|
||||
await ExchangeDataLoadingService.instance.initDB();
|
||||
// run without awaiting
|
||||
if (ref.read(prefsChangeNotifierProvider).externalCalls &&
|
||||
|
|
98
lib/models/electrumx_response/spark_models.dart
Normal file
98
lib/models/electrumx_response/spark_models.dart
Normal file
|
@ -0,0 +1,98 @@
|
|||
class SparkMempoolData {
|
||||
final String txid;
|
||||
final List<String> serialContext;
|
||||
final List<String> lTags;
|
||||
final List<String> coins;
|
||||
|
||||
SparkMempoolData({
|
||||
required this.txid,
|
||||
required this.serialContext,
|
||||
required this.lTags,
|
||||
required this.coins,
|
||||
});
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return "SparkMempoolData{"
|
||||
"txid: $txid, "
|
||||
"serialContext: $serialContext, "
|
||||
"lTags: $lTags, "
|
||||
"coins: $coins"
|
||||
"}";
|
||||
}
|
||||
}
|
||||
|
||||
class SparkAnonymitySetMeta {
|
||||
final int coinGroupId;
|
||||
final String blockHash;
|
||||
final String setHash;
|
||||
final int size;
|
||||
|
||||
SparkAnonymitySetMeta({
|
||||
required this.coinGroupId,
|
||||
required this.blockHash,
|
||||
required this.setHash,
|
||||
required this.size,
|
||||
});
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return "SparkAnonymitySetMeta{"
|
||||
"coinGroupId: $coinGroupId, "
|
||||
"blockHash: $blockHash, "
|
||||
"setHash: $setHash, "
|
||||
"size: $size"
|
||||
"}";
|
||||
}
|
||||
}
|
||||
|
||||
class RawSparkCoin {
|
||||
final String serialized;
|
||||
final String txHash;
|
||||
final String context;
|
||||
final int groupId;
|
||||
|
||||
RawSparkCoin({
|
||||
required this.serialized,
|
||||
required this.txHash,
|
||||
required this.context,
|
||||
required this.groupId,
|
||||
});
|
||||
|
||||
static RawSparkCoin fromRPCResponse(List<dynamic> data, int groupId) {
|
||||
try {
|
||||
if (data.length != 3) throw Exception();
|
||||
return RawSparkCoin(
|
||||
serialized: data[0] as String,
|
||||
txHash: data[1] as String,
|
||||
context: data[2] as String,
|
||||
groupId: groupId,
|
||||
);
|
||||
} catch (_) {
|
||||
throw Exception("Invalid coin data: $data");
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
if (other is! RawSparkCoin) return false;
|
||||
return serialized == other.serialized &&
|
||||
txHash == other.txHash &&
|
||||
groupId == other.groupId &&
|
||||
context == other.context;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(serialized, txHash, context);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return "SparkAnonymitySetMeta{"
|
||||
"serialized: $serialized, "
|
||||
"txHash: $txHash, "
|
||||
"context: $context, "
|
||||
"groupId: $groupId"
|
||||
"}";
|
||||
}
|
||||
}
|
|
@ -7,7 +7,7 @@ part of 'currency.dart';
|
|||
// **************************************************************************
|
||||
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types
|
||||
|
||||
extension GetCurrencyCollection on Isar {
|
||||
IsarCollection<Currency> get currencies => this.collection();
|
||||
|
@ -135,7 +135,7 @@ const CurrencySchema = CollectionSchema(
|
|||
getId: _currencyGetId,
|
||||
getLinks: _currencyGetLinks,
|
||||
attach: _currencyAttach,
|
||||
version: '3.0.5',
|
||||
version: '3.1.8',
|
||||
);
|
||||
|
||||
int _currencyEstimateSize(
|
||||
|
|
|
@ -7,7 +7,7 @@ part of 'pair.dart';
|
|||
// **************************************************************************
|
||||
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types
|
||||
|
||||
extension GetPairCollection on Isar {
|
||||
IsarCollection<Pair> get pairs => this.collection();
|
||||
|
@ -92,7 +92,7 @@ const PairSchema = CollectionSchema(
|
|||
getId: _pairGetId,
|
||||
getLinks: _pairGetLinks,
|
||||
attach: _pairAttach,
|
||||
version: '3.0.5',
|
||||
version: '3.1.8',
|
||||
);
|
||||
|
||||
int _pairEstimateSize(
|
||||
|
|
|
@ -7,7 +7,7 @@ part of 'address_label.dart';
|
|||
// **************************************************************************
|
||||
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types
|
||||
|
||||
extension GetAddressLabelCollection on Isar {
|
||||
IsarCollection<AddressLabel> get addressLabels => this.collection();
|
||||
|
@ -81,7 +81,7 @@ const AddressLabelSchema = CollectionSchema(
|
|||
getId: _addressLabelGetId,
|
||||
getLinks: _addressLabelGetLinks,
|
||||
attach: _addressLabelAttach,
|
||||
version: '3.0.5',
|
||||
version: '3.1.8',
|
||||
);
|
||||
|
||||
int _addressLabelEstimateSize(
|
||||
|
|
|
@ -7,7 +7,7 @@ part of 'block_explorer.dart';
|
|||
// **************************************************************************
|
||||
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types
|
||||
|
||||
extension GetTransactionBlockExplorerCollection on Isar {
|
||||
IsarCollection<TransactionBlockExplorer> get transactionBlockExplorers =>
|
||||
|
@ -54,7 +54,7 @@ const TransactionBlockExplorerSchema = CollectionSchema(
|
|||
getId: _transactionBlockExplorerGetId,
|
||||
getLinks: _transactionBlockExplorerGetLinks,
|
||||
attach: _transactionBlockExplorerAttach,
|
||||
version: '3.0.5',
|
||||
version: '3.1.8',
|
||||
);
|
||||
|
||||
int _transactionBlockExplorerEstimateSize(
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:isar/isar.dart';
|
||||
|
||||
import '../../../../exceptions/address/address_exception.dart';
|
||||
import 'crypto_currency_address.dart';
|
||||
import 'transaction.dart';
|
||||
|
@ -27,6 +28,7 @@ class Address extends CryptoCurrencyAddress {
|
|||
required this.derivationPath,
|
||||
required this.type,
|
||||
required this.subType,
|
||||
this.zSafeFrost,
|
||||
this.otherData,
|
||||
});
|
||||
|
||||
|
@ -55,6 +57,8 @@ class Address extends CryptoCurrencyAddress {
|
|||
|
||||
final transactions = IsarLinks<Transaction>();
|
||||
|
||||
late final bool? zSafeFrost;
|
||||
|
||||
int derivationChain() {
|
||||
if (subType == AddressSubType.receiving) {
|
||||
return 0; // 0 for receiving (external)
|
||||
|
@ -80,6 +84,7 @@ class Address extends CryptoCurrencyAddress {
|
|||
AddressType? type,
|
||||
AddressSubType? subType,
|
||||
DerivationPath? derivationPath,
|
||||
bool? zSafeFrost,
|
||||
String? otherData,
|
||||
}) {
|
||||
return Address(
|
||||
|
@ -90,6 +95,7 @@ class Address extends CryptoCurrencyAddress {
|
|||
type: type ?? this.type,
|
||||
subType: subType ?? this.subType,
|
||||
derivationPath: derivationPath ?? this.derivationPath,
|
||||
zSafeFrost: zSafeFrost ?? this.zSafeFrost,
|
||||
otherData: otherData ?? this.otherData,
|
||||
);
|
||||
}
|
||||
|
@ -105,6 +111,7 @@ class Address extends CryptoCurrencyAddress {
|
|||
"subType: ${subType.name}, "
|
||||
"transactionsLength: ${transactions.length} "
|
||||
"derivationPath: $derivationPath, "
|
||||
"zSafeFrost: $zSafeFrost, "
|
||||
"otherData: $otherData, "
|
||||
"}";
|
||||
|
||||
|
@ -117,6 +124,7 @@ class Address extends CryptoCurrencyAddress {
|
|||
"type": type.name,
|
||||
"subType": subType.name,
|
||||
"derivationPath": derivationPath?.value,
|
||||
"zSafeFrost": zSafeFrost,
|
||||
"otherData": otherData,
|
||||
};
|
||||
return jsonEncode(result);
|
||||
|
@ -143,6 +151,7 @@ class Address extends CryptoCurrencyAddress {
|
|||
derivationPath: derivationPath,
|
||||
type: AddressType.values.byName(json["type"] as String),
|
||||
subType: AddressSubType.values.byName(json["subType"] as String),
|
||||
zSafeFrost: json["zSafeFrost"] as bool?,
|
||||
otherData: json["otherData"] as String?,
|
||||
);
|
||||
}
|
||||
|
@ -165,7 +174,8 @@ enum AddressType {
|
|||
tezos,
|
||||
frostMS,
|
||||
p2tr,
|
||||
solana;
|
||||
solana,
|
||||
cardanoShelley;
|
||||
|
||||
String get readableName {
|
||||
switch (this) {
|
||||
|
@ -201,6 +211,8 @@ enum AddressType {
|
|||
return "Solana";
|
||||
case AddressType.p2tr:
|
||||
return "P2TR (taproot)";
|
||||
case AddressType.cardanoShelley:
|
||||
return "Cardano Shelley";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ part of 'address.dart';
|
|||
// **************************************************************************
|
||||
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types
|
||||
|
||||
extension GetAddressCollection on Isar {
|
||||
IsarCollection<Address> get addresses => this.collection();
|
||||
|
@ -59,6 +59,11 @@ const AddressSchema = CollectionSchema(
|
|||
id: 7,
|
||||
name: r'walletId',
|
||||
type: IsarType.string,
|
||||
),
|
||||
r'zSafeFrost': PropertySchema(
|
||||
id: 8,
|
||||
name: r'zSafeFrost',
|
||||
type: IsarType.bool,
|
||||
)
|
||||
},
|
||||
estimateSize: _addressEstimateSize,
|
||||
|
@ -124,7 +129,7 @@ const AddressSchema = CollectionSchema(
|
|||
getId: _addressGetId,
|
||||
getLinks: _addressGetLinks,
|
||||
attach: _addressAttach,
|
||||
version: '3.0.5',
|
||||
version: '3.1.8',
|
||||
);
|
||||
|
||||
int _addressEstimateSize(
|
||||
|
@ -172,6 +177,7 @@ void _addressSerialize(
|
|||
writer.writeByte(offsets[5], object.type.index);
|
||||
writer.writeString(offsets[6], object.value);
|
||||
writer.writeString(offsets[7], object.walletId);
|
||||
writer.writeBool(offsets[8], object.zSafeFrost);
|
||||
}
|
||||
|
||||
Address _addressDeserialize(
|
||||
|
@ -195,6 +201,7 @@ Address _addressDeserialize(
|
|||
AddressType.p2pkh,
|
||||
value: reader.readString(offsets[6]),
|
||||
walletId: reader.readString(offsets[7]),
|
||||
zSafeFrost: reader.readBoolOrNull(offsets[8]),
|
||||
);
|
||||
object.id = id;
|
||||
return object;
|
||||
|
@ -229,6 +236,8 @@ P _addressDeserializeProp<P>(
|
|||
return (reader.readString(offset)) as P;
|
||||
case 7:
|
||||
return (reader.readString(offset)) as P;
|
||||
case 8:
|
||||
return (reader.readBoolOrNull(offset)) as P;
|
||||
default:
|
||||
throw IsarError('Unknown property with id $propertyId');
|
||||
}
|
||||
|
@ -269,6 +278,7 @@ const _AddresstypeEnumValueMap = {
|
|||
'frostMS': 13,
|
||||
'p2tr': 14,
|
||||
'solana': 15,
|
||||
'cardanoShelley': 16,
|
||||
};
|
||||
const _AddresstypeValueEnumMap = {
|
||||
0: AddressType.p2pkh,
|
||||
|
@ -287,6 +297,7 @@ const _AddresstypeValueEnumMap = {
|
|||
13: AddressType.frostMS,
|
||||
14: AddressType.p2tr,
|
||||
15: AddressType.solana,
|
||||
16: AddressType.cardanoShelley,
|
||||
};
|
||||
|
||||
Id _addressGetId(Address object) {
|
||||
|
@ -1474,6 +1485,32 @@ extension AddressQueryFilter
|
|||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Address, Address, QAfterFilterCondition> zSafeFrostIsNull() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(const FilterCondition.isNull(
|
||||
property: r'zSafeFrost',
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Address, Address, QAfterFilterCondition> zSafeFrostIsNotNull() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(const FilterCondition.isNotNull(
|
||||
property: r'zSafeFrost',
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Address, Address, QAfterFilterCondition> zSafeFrostEqualTo(
|
||||
bool? value) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.equalTo(
|
||||
property: r'zSafeFrost',
|
||||
value: value,
|
||||
));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
extension AddressQueryObject
|
||||
|
@ -1621,6 +1658,18 @@ extension AddressQuerySortBy on QueryBuilder<Address, Address, QSortBy> {
|
|||
return query.addSortBy(r'walletId', Sort.desc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Address, Address, QAfterSortBy> sortByZSafeFrost() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'zSafeFrost', Sort.asc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Address, Address, QAfterSortBy> sortByZSafeFrostDesc() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'zSafeFrost', Sort.desc);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
extension AddressQuerySortThenBy
|
||||
|
@ -1708,6 +1757,18 @@ extension AddressQuerySortThenBy
|
|||
return query.addSortBy(r'walletId', Sort.desc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Address, Address, QAfterSortBy> thenByZSafeFrost() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'zSafeFrost', Sort.asc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Address, Address, QAfterSortBy> thenByZSafeFrostDesc() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'zSafeFrost', Sort.desc);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
extension AddressQueryWhereDistinct
|
||||
|
@ -1756,6 +1817,12 @@ extension AddressQueryWhereDistinct
|
|||
return query.addDistinctBy(r'walletId', caseSensitive: caseSensitive);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Address, Address, QDistinct> distinctByZSafeFrost() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addDistinctBy(r'zSafeFrost');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
extension AddressQueryProperty
|
||||
|
@ -1814,6 +1881,12 @@ extension AddressQueryProperty
|
|||
return query.addPropertyName(r'walletId');
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Address, bool?, QQueryOperations> zSafeFrostProperty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addPropertyName(r'zSafeFrost');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// **************************************************************************
|
||||
|
@ -1821,7 +1894,7 @@ extension AddressQueryProperty
|
|||
// **************************************************************************
|
||||
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types
|
||||
|
||||
const DerivationPathSchema = Schema(
|
||||
name: r'DerivationPath',
|
||||
|
|
|
@ -7,7 +7,7 @@ part of 'input.dart';
|
|||
// **************************************************************************
|
||||
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types
|
||||
|
||||
const InputSchema = Schema(
|
||||
name: r'Input',
|
||||
|
|
|
@ -7,7 +7,7 @@ part of 'output.dart';
|
|||
// **************************************************************************
|
||||
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types
|
||||
|
||||
const OutputSchema = Schema(
|
||||
name: r'Output',
|
||||
|
|
|
@ -7,7 +7,7 @@ part of 'transaction.dart';
|
|||
// **************************************************************************
|
||||
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types
|
||||
|
||||
extension GetTransactionCollection on Isar {
|
||||
IsarCollection<Transaction> get transactions => this.collection();
|
||||
|
@ -171,7 +171,7 @@ const TransactionSchema = CollectionSchema(
|
|||
getId: _transactionGetId,
|
||||
getLinks: _transactionGetLinks,
|
||||
attach: _transactionAttach,
|
||||
version: '3.0.5',
|
||||
version: '3.1.8',
|
||||
);
|
||||
|
||||
int _transactionEstimateSize(
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
*
|
||||
*/
|
||||
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:isar/isar.dart';
|
||||
|
@ -79,9 +80,28 @@ 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
|
||||
String? get keyImage {
|
||||
if (otherData == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
final map = jsonDecode(otherData!) as Map;
|
||||
return map["keyImage"] as String;
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
UTXO copyWith({
|
||||
|
|
|
@ -7,7 +7,7 @@ part of 'utxo.dart';
|
|||
// **************************************************************************
|
||||
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types
|
||||
|
||||
extension GetUTXOCollection on Isar {
|
||||
IsarCollection<UTXO> get utxos => this.collection();
|
||||
|
@ -149,7 +149,7 @@ const UTXOSchema = CollectionSchema(
|
|||
getId: _uTXOGetId,
|
||||
getLinks: _uTXOGetLinks,
|
||||
attach: _uTXOAttach,
|
||||
version: '3.0.5',
|
||||
version: '3.1.8',
|
||||
);
|
||||
|
||||
int _uTXOEstimateSize(
|
||||
|
|
|
@ -7,7 +7,7 @@ part of 'input_v2.dart';
|
|||
// **************************************************************************
|
||||
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types
|
||||
|
||||
const OutpointV2Schema = Schema(
|
||||
name: r'OutpointV2',
|
||||
|
@ -330,7 +330,7 @@ extension OutpointV2QueryObject
|
|||
on QueryBuilder<OutpointV2, OutpointV2, QFilterCondition> {}
|
||||
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types
|
||||
|
||||
const InputV2Schema = Schema(
|
||||
name: r'InputV2',
|
||||
|
|
|
@ -7,7 +7,7 @@ part of 'output_v2.dart';
|
|||
// **************************************************************************
|
||||
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types
|
||||
|
||||
const OutputV2Schema = Schema(
|
||||
name: r'OutputV2',
|
||||
|
|
|
@ -87,7 +87,10 @@ class TransactionV2 {
|
|||
);
|
||||
}
|
||||
|
||||
@ignore
|
||||
int? get size => _getFromOtherData(key: TxV2OdKeys.size) as int?;
|
||||
|
||||
@ignore
|
||||
int? get vSize => _getFromOtherData(key: TxV2OdKeys.vSize) as int?;
|
||||
|
||||
bool get isEpiccashTransaction =>
|
||||
|
@ -109,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}) {
|
||||
|
@ -121,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
|
||||
|
@ -131,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);
|
||||
|
@ -148,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);
|
||||
|
@ -188,18 +216,40 @@ 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)}"
|
||||
"/"
|
||||
"${(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";
|
||||
return "Anonymizing ${prettyConfirms()}";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -211,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) {
|
||||
|
@ -219,11 +269,11 @@ class TransactionV2 {
|
|||
} else if ((numberOfMessages ?? 0) > 1) {
|
||||
return "Receiving (waiting for confirmations)"; // TODO test if the sender still has to open again after the receiver has 2 messages present, ie. sender->receiver->sender->node (yes) vs. sender->receiver->node (no)
|
||||
} else {
|
||||
return "Receiving";
|
||||
return "Receiving ${prettyConfirms()}";
|
||||
}
|
||||
}
|
||||
} else if (type == TransactionType.outgoing) {
|
||||
if (isConfirmed(currentChainHeight, minConfirms)) {
|
||||
if (isConfirmed(currentChainHeight, minConfirms, minCoinbaseConfirms)) {
|
||||
return "Sent (confirmed)";
|
||||
} else {
|
||||
if (numberOfMessages == 1) {
|
||||
|
@ -231,7 +281,7 @@ class TransactionV2 {
|
|||
} else if ((numberOfMessages ?? 0) > 1) {
|
||||
return "Sending (waiting for confirmations)";
|
||||
} else {
|
||||
return "Sending";
|
||||
return "Sending ${prettyConfirms()}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -241,19 +291,23 @@ class TransactionV2 {
|
|||
// if (_transaction.isMinting) {
|
||||
// return "Minting";
|
||||
// } else
|
||||
if (isConfirmed(currentChainHeight, minConfirms)) {
|
||||
if (isConfirmed(currentChainHeight, minConfirms, minCoinbaseConfirms)) {
|
||||
return "Received";
|
||||
} else {
|
||||
return "Receiving";
|
||||
return "Receiving ${prettyConfirms()}";
|
||||
}
|
||||
} else if (type == TransactionType.outgoing) {
|
||||
if (isConfirmed(currentChainHeight, minConfirms)) {
|
||||
if (isConfirmed(currentChainHeight, minConfirms, minCoinbaseConfirms)) {
|
||||
return "Sent";
|
||||
} else {
|
||||
return "Sending";
|
||||
return "Sending ${prettyConfirms()}";
|
||||
}
|
||||
} else if (type == TransactionType.sentToSelf) {
|
||||
return "Sent to self";
|
||||
if (isConfirmed(currentChainHeight, minConfirms, minCoinbaseConfirms)) {
|
||||
return "Sent to self";
|
||||
} else {
|
||||
return "Sent to self ${prettyConfirms()}";
|
||||
}
|
||||
} else {
|
||||
return type.name;
|
||||
}
|
||||
|
@ -267,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'
|
||||
|
@ -297,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";
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ part of 'transaction_v2.dart';
|
|||
// **************************************************************************
|
||||
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types
|
||||
|
||||
extension GetTransactionV2Collection on Isar {
|
||||
IsarCollection<TransactionV2> get transactionV2s => this.collection();
|
||||
|
@ -177,7 +177,7 @@ const TransactionV2Schema = CollectionSchema(
|
|||
getId: _transactionV2GetId,
|
||||
getLinks: _transactionV2GetLinks,
|
||||
attach: _transactionV2Attach,
|
||||
version: '3.0.5',
|
||||
version: '3.1.8',
|
||||
);
|
||||
|
||||
int _transactionV2EstimateSize(
|
||||
|
|
|
@ -7,7 +7,7 @@ part of 'contact_entry.dart';
|
|||
// **************************************************************************
|
||||
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types
|
||||
|
||||
extension GetContactEntryCollection on Isar {
|
||||
IsarCollection<ContactEntry> get contactEntrys => this.collection();
|
||||
|
@ -69,7 +69,7 @@ const ContactEntrySchema = CollectionSchema(
|
|||
getId: _contactEntryGetId,
|
||||
getLinks: _contactEntryGetLinks,
|
||||
attach: _contactEntryAttach,
|
||||
version: '3.0.5',
|
||||
version: '3.1.8',
|
||||
);
|
||||
|
||||
int _contactEntryEstimateSize(
|
||||
|
@ -1142,7 +1142,7 @@ extension ContactEntryQueryProperty
|
|||
// **************************************************************************
|
||||
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types
|
||||
|
||||
const ContactAddressEntrySchema = Schema(
|
||||
name: r'ContactAddressEntry',
|
||||
|
|
|
@ -7,7 +7,7 @@ part of 'encrypted_string_value.dart';
|
|||
// **************************************************************************
|
||||
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types
|
||||
|
||||
extension GetEncryptedStringValueCollection on Isar {
|
||||
IsarCollection<EncryptedStringValue> get encryptedStringValues =>
|
||||
|
@ -54,7 +54,7 @@ const EncryptedStringValueSchema = CollectionSchema(
|
|||
getId: _encryptedStringValueGetId,
|
||||
getLinks: _encryptedStringValueGetLinks,
|
||||
attach: _encryptedStringValueAttach,
|
||||
version: '3.0.5',
|
||||
version: '3.1.8',
|
||||
);
|
||||
|
||||
int _encryptedStringValueEstimateSize(
|
||||
|
|
|
@ -7,7 +7,7 @@ part of 'eth_contract.dart';
|
|||
// **************************************************************************
|
||||
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types
|
||||
|
||||
extension GetEthContractCollection on Isar {
|
||||
IsarCollection<EthContract> get ethContracts => this.collection();
|
||||
|
@ -74,7 +74,7 @@ const EthContractSchema = CollectionSchema(
|
|||
getId: _ethContractGetId,
|
||||
getLinks: _ethContractGetLinks,
|
||||
attach: _ethContractAttach,
|
||||
version: '3.0.5',
|
||||
version: '3.1.8',
|
||||
);
|
||||
|
||||
int _ethContractEstimateSize(
|
||||
|
|
|
@ -7,7 +7,7 @@ part of 'lelantus_coin.dart';
|
|||
// **************************************************************************
|
||||
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types
|
||||
|
||||
extension GetLelantusCoinCollection on Isar {
|
||||
IsarCollection<LelantusCoin> get lelantusCoins => this.collection();
|
||||
|
@ -101,7 +101,7 @@ const LelantusCoinSchema = CollectionSchema(
|
|||
getId: _lelantusCoinGetId,
|
||||
getLinks: _lelantusCoinGetLinks,
|
||||
attach: _lelantusCoinAttach,
|
||||
version: '3.0.5',
|
||||
version: '3.1.8',
|
||||
);
|
||||
|
||||
int _lelantusCoinEstimateSize(
|
||||
|
|
|
@ -7,7 +7,7 @@ part of 'log.dart';
|
|||
// **************************************************************************
|
||||
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types
|
||||
|
||||
extension GetLogCollection on Isar {
|
||||
IsarCollection<Log> get logs => this.collection();
|
||||
|
@ -59,7 +59,7 @@ const LogSchema = CollectionSchema(
|
|||
getId: _logGetId,
|
||||
getLinks: _logGetLinks,
|
||||
attach: _logAttach,
|
||||
version: '3.0.5',
|
||||
version: '3.1.8',
|
||||
);
|
||||
|
||||
int _logEstimateSize(
|
||||
|
|
35
lib/models/isar/models/sent_to_address.dart
Normal file
35
lib/models/isar/models/sent_to_address.dart
Normal file
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* This file is part of Stack Wallet.
|
||||
*
|
||||
* Copyright (c) 2023 Cypher Stack
|
||||
* All Rights Reserved.
|
||||
* The code is distributed under GPLv3 license, see LICENSE file for details.
|
||||
* Generated by Cypher Stack on 2023-05-26
|
||||
*
|
||||
*/
|
||||
|
||||
import 'package:isar/isar.dart';
|
||||
|
||||
part 'sent_to_address.g.dart';
|
||||
|
||||
@Collection()
|
||||
class SentToAddress {
|
||||
SentToAddress({
|
||||
required this.walletId,
|
||||
required this.txid,
|
||||
required this.value,
|
||||
this.label = "",
|
||||
});
|
||||
|
||||
Id id = Isar.autoIncrement;
|
||||
|
||||
@Index()
|
||||
late final String walletId;
|
||||
|
||||
@Index(unique: true, composite: [CompositeIndex("walletId")])
|
||||
late final String txid;
|
||||
|
||||
late final String value;
|
||||
|
||||
late final String label;
|
||||
}
|
1248
lib/models/isar/models/sent_to_address.g.dart
Normal file
1248
lib/models/isar/models/sent_to_address.g.dart
Normal file
File diff suppressed because it is too large
Load diff
|
@ -7,7 +7,7 @@ part of 'transaction_note.dart';
|
|||
// **************************************************************************
|
||||
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types
|
||||
|
||||
extension GetTransactionNoteCollection on Isar {
|
||||
IsarCollection<TransactionNote> get transactionNotes => this.collection();
|
||||
|
@ -76,7 +76,7 @@ const TransactionNoteSchema = CollectionSchema(
|
|||
getId: _transactionNoteGetId,
|
||||
getLinks: _transactionNoteGetLinks,
|
||||
attach: _transactionNoteAttach,
|
||||
version: '3.0.5',
|
||||
version: '3.1.8',
|
||||
);
|
||||
|
||||
int _transactionNoteEstimateSize(
|
||||
|
|
|
@ -7,7 +7,7 @@ part of 'ordinal.dart';
|
|||
// **************************************************************************
|
||||
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types
|
||||
|
||||
extension GetOrdinalCollection on Isar {
|
||||
IsarCollection<Ordinal> get ordinals => this.collection();
|
||||
|
@ -83,7 +83,7 @@ const OrdinalSchema = CollectionSchema(
|
|||
getId: _ordinalGetId,
|
||||
getLinks: _ordinalGetLinks,
|
||||
attach: _ordinalAttach,
|
||||
version: '3.0.5',
|
||||
version: '3.1.8',
|
||||
);
|
||||
|
||||
int _ordinalEstimateSize(
|
||||
|
|
|
@ -7,7 +7,7 @@ part of 'stack_theme.dart';
|
|||
// **************************************************************************
|
||||
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types
|
||||
|
||||
extension GetStackThemeCollection on Isar {
|
||||
IsarCollection<StackTheme> get stackThemes => this.collection();
|
||||
|
@ -860,7 +860,7 @@ const StackThemeSchema = CollectionSchema(
|
|||
getId: _stackThemeGetId,
|
||||
getLinks: _stackThemeGetLinks,
|
||||
attach: _stackThemeAttach,
|
||||
version: '3.0.5',
|
||||
version: '3.1.8',
|
||||
);
|
||||
|
||||
int _stackThemeEstimateSize(
|
||||
|
@ -18012,7 +18012,7 @@ extension StackThemeQueryProperty
|
|||
// **************************************************************************
|
||||
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types
|
||||
|
||||
const ThemeAssetsSchema = Schema(
|
||||
name: r'ThemeAssets',
|
||||
|
@ -25833,7 +25833,7 @@ extension ThemeAssetsQueryObject
|
|||
on QueryBuilder<ThemeAssets, ThemeAssets, QFilterCondition> {}
|
||||
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types
|
||||
|
||||
const ThemeAssetsV2Schema = Schema(
|
||||
name: r'ThemeAssetsV2',
|
||||
|
@ -29441,7 +29441,7 @@ extension ThemeAssetsV2QueryObject
|
|||
on QueryBuilder<ThemeAssetsV2, ThemeAssetsV2, QFilterCondition> {}
|
||||
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types
|
||||
|
||||
const ThemeAssetsV3Schema = Schema(
|
||||
name: r'ThemeAssetsV3',
|
||||
|
|
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,
|
||||
});
|
||||
|
@ -58,12 +65,14 @@ class NodeModel {
|
|||
int? port,
|
||||
String? name,
|
||||
bool? useSSL,
|
||||
String? loginName,
|
||||
required String? loginName,
|
||||
bool? enabled,
|
||||
String? coinName,
|
||||
bool? isFailover,
|
||||
bool? isDown,
|
||||
bool? trusted,
|
||||
required bool? trusted,
|
||||
bool? torEnabled,
|
||||
bool? clearnetEnabled,
|
||||
}) {
|
||||
return NodeModel(
|
||||
host: host ?? this.host,
|
||||
|
@ -71,12 +80,14 @@ class NodeModel {
|
|||
name: name ?? this.name,
|
||||
id: id,
|
||||
useSSL: useSSL ?? this.useSSL,
|
||||
loginName: loginName ?? this.loginName,
|
||||
loginName: loginName,
|
||||
enabled: enabled ?? this.enabled,
|
||||
coinName: coinName ?? this.coinName,
|
||||
isFailover: isFailover ?? this.isFailover,
|
||||
isDown: isDown ?? this.isDown,
|
||||
trusted: trusted ?? this.trusted,
|
||||
trusted: 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
|
||||
|
|
|
@ -4,6 +4,7 @@ import 'dart:io';
|
|||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:socks5_proxy/socks_client.dart';
|
||||
|
||||
import '../utilities/logger.dart';
|
||||
|
||||
// WIP wrapper layer
|
||||
|
@ -118,6 +119,10 @@ class HTTP {
|
|||
onDone: () => completer.complete(
|
||||
Uint8List.fromList(bytes),
|
||||
),
|
||||
onError: (Object err, StackTrace s) => Logging.instance.log(
|
||||
"Http wrapper layer listen: $err\n$s",
|
||||
level: LogLevel.Error,
|
||||
),
|
||||
);
|
||||
return completer.future;
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
|
@ -69,6 +68,7 @@ class _AddWalletViewState extends ConsumerState<AddWalletView> {
|
|||
...AppConfig.coins.where((e) => e.network == CryptoCurrencyNetwork.main),
|
||||
];
|
||||
final List<AddWalletListEntity> coinEntities = [];
|
||||
final List<AddWalletListEntity> coinTestnetEntities = [];
|
||||
final List<EthTokenEntity> tokenEntities = [];
|
||||
|
||||
final bool isDesktop = Util.isDesktop;
|
||||
|
@ -130,16 +130,11 @@ 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)));
|
||||
|
||||
if (ref.read(prefsChangeNotifierProvider).showTestNetCoins) {
|
||||
coinEntities.addAll(_coinsTestnet.map((e) => CoinEntity(e)));
|
||||
coinTestnetEntities.addAll(_coinsTestnet.map((e) => CoinEntity(e)));
|
||||
}
|
||||
|
||||
if (AppConfig.coins.whereType<Ethereum>().isNotEmpty) {
|
||||
|
@ -286,6 +281,14 @@ class _AddWalletViewState extends ConsumerState<AddWalletView> {
|
|||
initialState: ExpandableState.expanded,
|
||||
animationDurationMultiplier: 0.5,
|
||||
),
|
||||
if (coinTestnetEntities.isNotEmpty)
|
||||
ExpandingSubListItem(
|
||||
title: "Testnet",
|
||||
entities:
|
||||
filter(_searchTerm, coinTestnetEntities),
|
||||
initialState: ExpandableState.expanded,
|
||||
animationDurationMultiplier: 0.5,
|
||||
),
|
||||
if (tokenEntities.isNotEmpty)
|
||||
ExpandingSubListItem(
|
||||
title: "Tokens",
|
||||
|
@ -419,6 +422,13 @@ class _AddWalletViewState extends ConsumerState<AddWalletView> {
|
|||
entities: filter(_searchTerm, coinEntities),
|
||||
initialState: ExpandableState.expanded,
|
||||
),
|
||||
if (coinTestnetEntities.isNotEmpty)
|
||||
ExpandingSubListItem(
|
||||
title: "Testnet",
|
||||
entities:
|
||||
filter(_searchTerm, coinTestnetEntities),
|
||||
initialState: ExpandableState.expanded,
|
||||
),
|
||||
if (tokenEntities.isNotEmpty)
|
||||
ExpandingSubListItem(
|
||||
title: "Tokens",
|
||||
|
|
|
@ -208,7 +208,7 @@ class _FrostReshareStep2abdState extends ConsumerState<FrostReshareStep2abd> {
|
|||
label: "Continue",
|
||||
enabled: _userVerifyContinue &&
|
||||
(amOutgoingParticipant ||
|
||||
!fieldIsEmptyFlags.reduce((v, e) => v |= e)),
|
||||
!fieldIsEmptyFlags.fold(false, (v, e) => v || e)),
|
||||
onPressed: _onPressed,
|
||||
),
|
||||
],
|
||||
|
|
|
@ -238,7 +238,7 @@ class _FrostReshareStep4State extends ConsumerState<FrostReshareStep4> {
|
|||
label: amOutgoingParticipant ? "Done" : "Complete",
|
||||
enabled: (amNewParticipant || _userVerifyContinue) &&
|
||||
(amOutgoingParticipant ||
|
||||
!fieldIsEmptyFlags.reduce((v, e) => v |= e)),
|
||||
!fieldIsEmptyFlags.fold(false, (v, e) => v || e)),
|
||||
onPressed: _onPressed,
|
||||
),
|
||||
],
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:barcode_scan2/barcode_scan2.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
@ -32,6 +33,7 @@ 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/desktop/qr_code_scanner_dialog.dart';
|
||||
import '../../../../widgets/frost_mascot.dart';
|
||||
import '../../../../widgets/icon_widgets/clipboard_icon.dart';
|
||||
import '../../../../widgets/icon_widgets/qrcode_icon.dart';
|
||||
|
@ -207,6 +209,52 @@ class _RestoreFrostMsWalletViewState
|
|||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> scanQr() async {
|
||||
try {
|
||||
if (Platform.isAndroid || Platform.isIOS) {
|
||||
if (FocusScope.of(context).hasFocus) {
|
||||
FocusScope.of(context).unfocus();
|
||||
await Future<void>.delayed(
|
||||
const Duration(milliseconds: 75),
|
||||
);
|
||||
}
|
||||
|
||||
final qrResult = await BarcodeScanner.scan();
|
||||
|
||||
configFieldController.text = qrResult.rawContent;
|
||||
|
||||
setState(() {
|
||||
_configEmpty = configFieldController.text.isEmpty;
|
||||
});
|
||||
} else {
|
||||
// Platform.isLinux, Platform.isWindows, or Platform.isMacOS.
|
||||
final qrResult = await showDialog<String>(
|
||||
context: context,
|
||||
builder: (context) => const QrCodeScannerDialog(),
|
||||
);
|
||||
|
||||
if (qrResult == null) {
|
||||
Logging.instance.log(
|
||||
"Qr scanning cancelled",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
} else {
|
||||
// TODO [prio=low]: Validate QR code data.
|
||||
configFieldController.text = qrResult;
|
||||
|
||||
setState(() {
|
||||
_configEmpty = configFieldController.text.isEmpty;
|
||||
});
|
||||
}
|
||||
}
|
||||
} on PlatformException catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"Failed to get camera permissions while trying to scan qr code: $e\n$s",
|
||||
level: LogLevel.Warning,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ConditionalParent(
|
||||
|
@ -351,31 +399,7 @@ class _RestoreFrostMsWalletViewState
|
|||
semanticsLabel:
|
||||
"Scan QR Button. Opens Camera For Scanning QR Code.",
|
||||
key: const Key("frConfigScanQrButtonKey"),
|
||||
onTap: () async {
|
||||
try {
|
||||
if (FocusScope.of(context).hasFocus) {
|
||||
FocusScope.of(context).unfocus();
|
||||
await Future<void>.delayed(
|
||||
const Duration(milliseconds: 75),
|
||||
);
|
||||
}
|
||||
|
||||
final qrResult = await BarcodeScanner.scan();
|
||||
|
||||
configFieldController.text =
|
||||
qrResult.rawContent;
|
||||
|
||||
setState(() {
|
||||
_configEmpty =
|
||||
configFieldController.text.isEmpty;
|
||||
});
|
||||
} on PlatformException catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"Failed to get camera permissions while trying to scan qr code: $e\n$s",
|
||||
level: LogLevel.Warning,
|
||||
);
|
||||
}
|
||||
},
|
||||
onTap: scanQr,
|
||||
child: const QrCodeIcon(),
|
||||
),
|
||||
],
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -26,18 +26,20 @@ import '../../../services/transaction_notification_tracker.dart';
|
|||
import '../../../themes/stack_colors.dart';
|
||||
import '../../../utilities/assets.dart';
|
||||
import '../../../utilities/logger.dart';
|
||||
import '../../../utilities/show_loading.dart';
|
||||
import '../../../utilities/text_styles.dart';
|
||||
import '../../../utilities/util.dart';
|
||||
import '../../../wallets/crypto_currency/crypto_currency.dart';
|
||||
import '../../../wallets/isar/models/wallet_info.dart';
|
||||
import '../../../wallets/wallet/intermediate/lib_monero_wallet.dart';
|
||||
import '../../../wallets/wallet/wallet.dart';
|
||||
import '../../../wallets/wallet/wallet_mixin_interfaces/mnemonic_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/loading_indicator.dart';
|
||||
import '../../../widgets/rounded_container.dart';
|
||||
import '../../../widgets/rounded_white_container.dart';
|
||||
import '../../../widgets/stack_dialog.dart';
|
||||
import '../new_wallet_options/new_wallet_options_view.dart';
|
||||
import '../new_wallet_recovery_phrase_view/new_wallet_recovery_phrase_view.dart';
|
||||
import 'recovery_phrase_explanation_dialog.dart';
|
||||
|
@ -65,6 +67,221 @@ class _NewWalletRecoveryPhraseWarningViewState
|
|||
late final String walletName;
|
||||
late final bool isDesktop;
|
||||
|
||||
Future<void> _initNewWallet() async {
|
||||
Exception? ex;
|
||||
final result = await showLoading(
|
||||
whileFuture: _initNewFuture(),
|
||||
context: context,
|
||||
message: "Generating...",
|
||||
onException: (e) => ex = e,
|
||||
);
|
||||
|
||||
// on failure show error message
|
||||
if (result == null) {
|
||||
if (mounted) {
|
||||
await showDialog<void>(
|
||||
context: context,
|
||||
builder: (_) => StackOkDialog(
|
||||
title: "Create Wallet Error",
|
||||
message: ex?.toString() ?? "Unknown error",
|
||||
maxWidth: 600,
|
||||
),
|
||||
);
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
if (mounted) {
|
||||
final nav = Navigator.of(context);
|
||||
unawaited(
|
||||
nav.pushNamed(
|
||||
NewWalletRecoveryPhraseView.routeName,
|
||||
arguments: Tuple2(
|
||||
result.$1,
|
||||
result.$2,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<(Wallet, List<String>)> _initNewFuture() async {
|
||||
try {
|
||||
String? otherDataJsonString;
|
||||
if (widget.coin is Tezos) {
|
||||
otherDataJsonString = jsonEncode({
|
||||
WalletInfoKeys.tezosDerivationPath:
|
||||
Tezos.standardDerivationPath.value,
|
||||
});
|
||||
// }//todo: probably not needed (broken anyways)
|
||||
// else if (widget.coin is Epiccash) {
|
||||
// final int secondsSinceEpoch =
|
||||
// DateTime.now().millisecondsSinceEpoch ~/ 1000;
|
||||
// const int epicCashFirstBlock = 1565370278;
|
||||
// const double overestimateSecondsPerBlock = 61;
|
||||
// int chosenSeconds = secondsSinceEpoch - epicCashFirstBlock;
|
||||
// int approximateHeight = chosenSeconds ~/ overestimateSecondsPerBlock;
|
||||
// /
|
||||
// // debugPrint(
|
||||
// // "approximate height: $approximateHeight chosen_seconds: $chosenSeconds");
|
||||
// height = approximateHeight;
|
||||
// if (height < 0) {
|
||||
// height = 0;
|
||||
// }
|
||||
//
|
||||
// otherDataJsonString = jsonEncode(
|
||||
// {
|
||||
// WalletInfoKeys.epiccashData: jsonEncode(
|
||||
// ExtraEpiccashWalletInfo(
|
||||
// receivingIndex: 0,
|
||||
// changeIndex: 0,
|
||||
// slatesToAddresses: {},
|
||||
// slatesToCommits: {},
|
||||
// lastScannedBlock: epicCashFirstBlock,
|
||||
// restoreHeight: height,
|
||||
// creationHeight: height,
|
||||
// ).toMap(),
|
||||
// ),
|
||||
// },
|
||||
// );
|
||||
} else if (widget.coin is Firo) {
|
||||
otherDataJsonString = jsonEncode(
|
||||
{
|
||||
WalletInfoKeys.lelantusCoinIsarRescanRequired: false,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
final info = WalletInfo.createNew(
|
||||
coin: widget.coin,
|
||||
name: widget.walletName,
|
||||
otherDataJsonString: otherDataJsonString,
|
||||
);
|
||||
|
||||
var node = ref
|
||||
.read(
|
||||
nodeServiceChangeNotifierProvider,
|
||||
)
|
||||
.getPrimaryNodeFor(
|
||||
currency: coin,
|
||||
);
|
||||
|
||||
if (node == null) {
|
||||
node = coin.defaultNode;
|
||||
await ref
|
||||
.read(
|
||||
nodeServiceChangeNotifierProvider,
|
||||
)
|
||||
.setPrimaryNodeFor(
|
||||
coin: coin,
|
||||
node: node,
|
||||
);
|
||||
}
|
||||
|
||||
final txTracker = TransactionNotificationTracker(
|
||||
walletId: info.walletId,
|
||||
);
|
||||
|
||||
String? mnemonicPassphrase;
|
||||
String? mnemonic;
|
||||
String? privateKey;
|
||||
|
||||
// set some sane default
|
||||
int wordCount = info.coin.defaultSeedPhraseLength;
|
||||
|
||||
// TODO: Refactor these to generate each coin in their respective classes
|
||||
// This code should not be in a random view page file
|
||||
if (coin is Monero || coin is Wownero) {
|
||||
// currently a special case due to the
|
||||
// xmr/wow libraries handling their
|
||||
// own mnemonic generation
|
||||
wordCount = ref.read(pNewWalletOptions)?.mnemonicWordsCount ??
|
||||
info.coin.defaultSeedPhraseLength;
|
||||
} else if (wordCount > 0) {
|
||||
if (ref
|
||||
.read(
|
||||
pNewWalletOptions.state,
|
||||
)
|
||||
.state !=
|
||||
null) {
|
||||
if (coin.hasMnemonicPassphraseSupport) {
|
||||
mnemonicPassphrase = ref
|
||||
.read(
|
||||
pNewWalletOptions.state,
|
||||
)
|
||||
.state!
|
||||
.mnemonicPassphrase;
|
||||
} else {
|
||||
// this may not be epiccash specific?
|
||||
if (coin is Epiccash) {
|
||||
mnemonicPassphrase = "";
|
||||
}
|
||||
}
|
||||
|
||||
wordCount = ref
|
||||
.read(
|
||||
pNewWalletOptions.state,
|
||||
)
|
||||
.state!
|
||||
.mnemonicWordsCount;
|
||||
} else {
|
||||
mnemonicPassphrase = "";
|
||||
}
|
||||
|
||||
if (wordCount < 12 || 24 < wordCount || wordCount % 3 != 0) {
|
||||
throw Exception(
|
||||
"Invalid word count",
|
||||
);
|
||||
}
|
||||
|
||||
final strength = (wordCount ~/ 3) * 32;
|
||||
|
||||
mnemonic = bip39.generateMnemonic(
|
||||
strength: strength,
|
||||
);
|
||||
}
|
||||
|
||||
final wallet = await Wallet.create(
|
||||
walletInfo: info,
|
||||
mainDB: ref.read(mainDBProvider),
|
||||
secureStorageInterface: ref.read(secureStoreProvider),
|
||||
nodeService: ref.read(
|
||||
nodeServiceChangeNotifierProvider,
|
||||
),
|
||||
prefs: ref.read(
|
||||
prefsChangeNotifierProvider,
|
||||
),
|
||||
mnemonicPassphrase: mnemonicPassphrase,
|
||||
mnemonic: mnemonic,
|
||||
privateKey: privateKey,
|
||||
);
|
||||
|
||||
if (wallet is LibMoneroWallet) {
|
||||
await wallet.init(wordCount: wordCount);
|
||||
} else {
|
||||
await wallet.init();
|
||||
}
|
||||
|
||||
// set checkbox back to unchecked to annoy users to agree again :P
|
||||
ref
|
||||
.read(
|
||||
checkBoxStateProvider.state,
|
||||
)
|
||||
.state = false;
|
||||
|
||||
final fetchedMnemonic =
|
||||
await (wallet as MnemonicInterface).getMnemonicAsWords();
|
||||
|
||||
return (wallet, fetchedMnemonic);
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"$e\n$s",
|
||||
level: LogLevel.Fatal,
|
||||
);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
coin = widget.coin;
|
||||
|
@ -454,215 +671,7 @@ class _NewWalletRecoveryPhraseWarningViewState
|
|||
onPressed: ref
|
||||
.read(checkBoxStateProvider.state)
|
||||
.state
|
||||
? () async {
|
||||
try {
|
||||
unawaited(
|
||||
showDialog<dynamic>(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
useSafeArea: true,
|
||||
builder: (ctx) {
|
||||
return const Center(
|
||||
child: LoadingIndicator(
|
||||
width: 50,
|
||||
height: 50,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
String? otherDataJsonString;
|
||||
if (widget.coin is Tezos) {
|
||||
otherDataJsonString = jsonEncode({
|
||||
WalletInfoKeys
|
||||
.tezosDerivationPath:
|
||||
Tezos.standardDerivationPath
|
||||
.value,
|
||||
});
|
||||
// }//todo: probably not needed (broken anyways)
|
||||
// else if (widget.coin is Epiccash) {
|
||||
// final int secondsSinceEpoch =
|
||||
// DateTime.now().millisecondsSinceEpoch ~/ 1000;
|
||||
// const int epicCashFirstBlock = 1565370278;
|
||||
// const double overestimateSecondsPerBlock = 61;
|
||||
// int chosenSeconds = secondsSinceEpoch - epicCashFirstBlock;
|
||||
// int approximateHeight = chosenSeconds ~/ overestimateSecondsPerBlock;
|
||||
// /
|
||||
// // debugPrint(
|
||||
// // "approximate height: $approximateHeight chosen_seconds: $chosenSeconds");
|
||||
// height = approximateHeight;
|
||||
// if (height < 0) {
|
||||
// height = 0;
|
||||
// }
|
||||
//
|
||||
// otherDataJsonString = jsonEncode(
|
||||
// {
|
||||
// WalletInfoKeys.epiccashData: jsonEncode(
|
||||
// ExtraEpiccashWalletInfo(
|
||||
// receivingIndex: 0,
|
||||
// changeIndex: 0,
|
||||
// slatesToAddresses: {},
|
||||
// slatesToCommits: {},
|
||||
// lastScannedBlock: epicCashFirstBlock,
|
||||
// restoreHeight: height,
|
||||
// creationHeight: height,
|
||||
// ).toMap(),
|
||||
// ),
|
||||
// },
|
||||
// );
|
||||
} else if (widget.coin is Firo) {
|
||||
otherDataJsonString = jsonEncode(
|
||||
{
|
||||
WalletInfoKeys
|
||||
.lelantusCoinIsarRescanRequired:
|
||||
false,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
final info = WalletInfo.createNew(
|
||||
coin: widget.coin,
|
||||
name: widget.walletName,
|
||||
otherDataJsonString:
|
||||
otherDataJsonString,
|
||||
);
|
||||
|
||||
var node = ref
|
||||
.read(
|
||||
nodeServiceChangeNotifierProvider,
|
||||
)
|
||||
.getPrimaryNodeFor(
|
||||
currency: coin,
|
||||
);
|
||||
|
||||
if (node == null) {
|
||||
node = coin.defaultNode;
|
||||
await ref
|
||||
.read(
|
||||
nodeServiceChangeNotifierProvider,
|
||||
)
|
||||
.setPrimaryNodeFor(
|
||||
coin: coin,
|
||||
node: node,
|
||||
);
|
||||
}
|
||||
|
||||
final txTracker =
|
||||
TransactionNotificationTracker(
|
||||
walletId: info.walletId,
|
||||
);
|
||||
|
||||
int? wordCount;
|
||||
String? mnemonicPassphrase;
|
||||
String? mnemonic;
|
||||
String? privateKey;
|
||||
|
||||
wordCount = info
|
||||
.coin.defaultSeedPhraseLength;
|
||||
|
||||
if (coin is Monero ||
|
||||
coin is Wownero) {
|
||||
// currently a special case due to the
|
||||
// xmr/wow libraries handling their
|
||||
// own mnemonic generation
|
||||
} else if (wordCount > 0) {
|
||||
if (ref
|
||||
.read(
|
||||
pNewWalletOptions.state,
|
||||
)
|
||||
.state !=
|
||||
null) {
|
||||
if (coin
|
||||
.hasMnemonicPassphraseSupport) {
|
||||
mnemonicPassphrase = ref
|
||||
.read(
|
||||
pNewWalletOptions.state,
|
||||
)
|
||||
.state!
|
||||
.mnemonicPassphrase;
|
||||
} else {}
|
||||
|
||||
wordCount = ref
|
||||
.read(
|
||||
pNewWalletOptions.state,
|
||||
)
|
||||
.state!
|
||||
.mnemonicWordsCount;
|
||||
} else {
|
||||
mnemonicPassphrase = "";
|
||||
}
|
||||
|
||||
if (wordCount < 12 ||
|
||||
24 < wordCount ||
|
||||
wordCount % 3 != 0) {
|
||||
throw Exception(
|
||||
"Invalid word count",
|
||||
);
|
||||
}
|
||||
|
||||
final strength =
|
||||
(wordCount ~/ 3) * 32;
|
||||
|
||||
mnemonic = bip39.generateMnemonic(
|
||||
strength: strength,
|
||||
);
|
||||
}
|
||||
|
||||
final wallet = await Wallet.create(
|
||||
walletInfo: info,
|
||||
mainDB: ref.read(mainDBProvider),
|
||||
secureStorageInterface:
|
||||
ref.read(secureStoreProvider),
|
||||
nodeService: ref.read(
|
||||
nodeServiceChangeNotifierProvider,
|
||||
),
|
||||
prefs: ref.read(
|
||||
prefsChangeNotifierProvider,
|
||||
),
|
||||
mnemonicPassphrase:
|
||||
mnemonicPassphrase,
|
||||
mnemonic: mnemonic,
|
||||
privateKey: privateKey,
|
||||
);
|
||||
|
||||
await wallet.init();
|
||||
|
||||
// pop progress dialog
|
||||
if (context.mounted) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
// set checkbox back to unchecked to annoy users to agree again :P
|
||||
ref
|
||||
.read(
|
||||
checkBoxStateProvider.state,
|
||||
)
|
||||
.state = false;
|
||||
|
||||
if (context.mounted) {
|
||||
final nav = Navigator.of(context);
|
||||
unawaited(
|
||||
nav.pushNamed(
|
||||
NewWalletRecoveryPhraseView
|
||||
.routeName,
|
||||
arguments: Tuple2(
|
||||
wallet,
|
||||
await (wallet
|
||||
as MnemonicInterface)
|
||||
.getMnemonicAsWords(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"$e\n$s",
|
||||
level: LogLevel.Fatal,
|
||||
);
|
||||
// TODO: handle gracefully
|
||||
// any network/socket exception here will break new wallet creation
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
? _initNewWallet
|
||||
: null,
|
||||
style: ref
|
||||
.read(checkBoxStateProvider.state)
|
||||
|
|
|
@ -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,13 +16,14 @@ 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/wakelock.dart';
|
||||
import 'package:wakelock_plus/wakelock_plus.dart';
|
||||
|
||||
import '../../../notifications/show_flush_bar.dart';
|
||||
import '../../../pages_desktop_specific/desktop_home_view.dart';
|
||||
|
@ -47,6 +48,7 @@ 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/intermediate/lib_monero_wallet.dart';
|
||||
import '../../../wallets/wallet/supporting/epiccash_wallet_info_extension.dart';
|
||||
import '../../../wallets/wallet/wallet.dart';
|
||||
import '../../../widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
|
@ -86,7 +88,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 +183,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 +191,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 +221,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;
|
||||
}
|
||||
|
@ -282,7 +290,7 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
|
|||
),
|
||||
);
|
||||
} else {
|
||||
if (!Platform.isLinux) await Wakelock.enable();
|
||||
if (!Platform.isLinux) await WakelockPlus.enable();
|
||||
|
||||
final info = WalletInfo.createNew(
|
||||
coin: widget.coin,
|
||||
|
@ -362,6 +370,10 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
|
|||
|
||||
await wallet.recover(isRescan: false);
|
||||
|
||||
if (wallet is LibMoneroWallet) {
|
||||
await wallet.exit();
|
||||
}
|
||||
|
||||
// check if state is still active before continuing
|
||||
if (mounted) {
|
||||
await wallet.info.setMnemonicVerified(
|
||||
|
@ -426,12 +438,12 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
|
|||
}
|
||||
|
||||
if (!Platform.isLinux && !isDesktop) {
|
||||
await Wakelock.disable();
|
||||
await WakelockPlus.disable();
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
if (!Platform.isLinux && !isDesktop) {
|
||||
await Wakelock.disable();
|
||||
await WakelockPlus.disable();
|
||||
}
|
||||
|
||||
// if (e is HiveError &&
|
||||
|
@ -463,7 +475,7 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
|
|||
}
|
||||
|
||||
if (!Platform.isLinux && !isDesktop) {
|
||||
await Wakelock.disable();
|
||||
await WakelockPlus.disable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -648,16 +660,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,19 +9,18 @@
|
|||
*/
|
||||
|
||||
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 '../add_token_view/edit_wallet_tokens_view.dart';
|
||||
import '../new_wallet_options/new_wallet_options_view.dart';
|
||||
import '../new_wallet_recovery_phrase_view/new_wallet_recovery_phrase_view.dart';
|
||||
import '../select_wallet_for_token_view.dart';
|
||||
import 'sub_widgets/word_table.dart';
|
||||
import 'verify_mnemonic_passphrase_dialog.dart';
|
||||
import '../../home_view/home_view.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';
|
||||
|
@ -30,15 +29,32 @@ 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 'package:tuple/tuple.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';
|
||||
import '../new_wallet_recovery_phrase_view/new_wallet_recovery_phrase_view.dart';
|
||||
import '../select_wallet_for_token_view.dart';
|
||||
import 'sub_widgets/word_table.dart';
|
||||
import 'verify_mnemonic_passphrase_dialog.dart';
|
||||
|
||||
final createSpecialEthWalletRoutingFlag = StateProvider((ref) => false);
|
||||
|
||||
|
@ -63,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,
|
||||
|
@ -112,9 +107,161 @@ 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.state).state != null) {
|
||||
if (ref.read(pNewWalletOptions) != null &&
|
||||
ref.read(pNewWalletOptions)!.mnemonicPassphrase.isNotEmpty) {
|
||||
final passphraseVerified = await _verifyMnemonicPassphrase();
|
||||
|
||||
if (!passphraseVerified) {
|
||||
|
@ -122,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);
|
||||
|
@ -140,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) {
|
||||
|
@ -154,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,
|
||||
|
@ -177,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,
|
||||
|
@ -267,7 +459,7 @@ class _VerifyRecoveryPhraseViewState
|
|||
|
||||
Future<void> delete() async {
|
||||
await ref.read(pWallets).deleteWallet(
|
||||
_wallet.info,
|
||||
widget.wallet.info,
|
||||
ref.read(secureStoreProvider),
|
||||
);
|
||||
}
|
||||
|
@ -297,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()
|
||||
|
@ -75,6 +131,14 @@ class _NewContactAddressEntryFormState
|
|||
addressLabelFocusNode = FocusNode();
|
||||
addressFocusNode = FocusNode();
|
||||
coins = [...AppConfig.coins];
|
||||
|
||||
if (AppConfig.isSingleCoinApp) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (mounted) {
|
||||
ref.read(addressEntryDataProvider(widget.id)).coin = coins.first;
|
||||
}
|
||||
});
|
||||
}
|
||||
super.initState();
|
||||
}
|
||||
|
||||
|
@ -109,7 +173,7 @@ class _NewContactAddressEntryFormState
|
|||
|
||||
return Column(
|
||||
children: [
|
||||
if (isDesktop)
|
||||
if (isDesktop && !AppConfig.isSingleCoinApp)
|
||||
DropdownButtonHideUnderline(
|
||||
child: DropdownButton2<CryptoCurrency>(
|
||||
hint: Text(
|
||||
|
@ -188,7 +252,7 @@ class _NewContactAddressEntryFormState
|
|||
],
|
||||
),
|
||||
),
|
||||
if (!isDesktop)
|
||||
if (!isDesktop && !AppConfig.isSingleCoinApp)
|
||||
TextField(
|
||||
autocorrect: Util.isDesktop ? false : true,
|
||||
enableSuggestions: Util.isDesktop ? false : true,
|
||||
|
@ -280,9 +344,10 @@ class _NewContactAddressEntryFormState
|
|||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
if (!AppConfig.isSingleCoinApp)
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
|
@ -395,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(
|
||||
|
|
|
@ -154,6 +154,8 @@ class _BuyFormState extends ConsumerState<BuyForm> {
|
|||
}
|
||||
}
|
||||
|
||||
String _receivingAddressValidationErrorString = "";
|
||||
|
||||
void selectCrypto() async {
|
||||
if (ref.read(simplexProvider).supportedCryptos.isEmpty) {
|
||||
bool shouldPop = false;
|
||||
|
@ -711,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();
|
||||
|
@ -1218,6 +1274,15 @@ class _BuyFormState extends ConsumerState<BuyForm> {
|
|||
_address = newValue;
|
||||
setState(() {
|
||||
_addressToggleFlag = newValue.isNotEmpty;
|
||||
|
||||
// TODO [prio=low]: Validate address.
|
||||
if (newValue.startsWith("bc1p")) {
|
||||
// Display an error message or handle invalid address
|
||||
_receivingAddressValidationErrorString =
|
||||
"Taproot addresses are not allowed.";
|
||||
} else {
|
||||
_receivingAddressValidationErrorString = "";
|
||||
}
|
||||
});
|
||||
},
|
||||
focusNode: _receiveAddressFocusNode,
|
||||
|
@ -1364,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(),
|
||||
),
|
||||
],
|
||||
|
@ -1430,6 +1439,14 @@ class _BuyFormState extends ConsumerState<BuyForm> {
|
|||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: isDesktop ? 10 : 4,
|
||||
),
|
||||
if (_receivingAddressValidationErrorString.isNotEmpty)
|
||||
Text(
|
||||
_receivingAddressValidationErrorString,
|
||||
style: STextStyles.errorSmall(context),
|
||||
),
|
||||
SizedBox(
|
||||
height: isDesktop ? 20 : 12,
|
||||
),
|
||||
|
|
|
@ -12,7 +12,7 @@ import 'dart:async';
|
|||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:wakelock/wakelock.dart';
|
||||
import 'package:wakelock_plus/wakelock_plus.dart';
|
||||
|
||||
import '../../pages_desktop_specific/cashfusion/sub_widgets/fusion_progress.dart';
|
||||
import '../../providers/cash_fusion/fusion_progress_ui_state_provider.dart';
|
||||
|
@ -85,7 +85,7 @@ class _FusionProgressViewState extends ConsumerState<FusionProgressView> {
|
|||
message: "Stopping fusion",
|
||||
);
|
||||
|
||||
await Wakelock.disable();
|
||||
await WakelockPlus.disable();
|
||||
|
||||
return true;
|
||||
} else {
|
||||
|
@ -101,7 +101,7 @@ class _FusionProgressViewState extends ConsumerState<FusionProgressView> {
|
|||
|
||||
@override
|
||||
void dispose() {
|
||||
Wakelock.disable();
|
||||
WakelockPlus.disable();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
@ -117,7 +117,7 @@ class _FusionProgressViewState extends ConsumerState<FusionProgressView> {
|
|||
.watch(fusionProgressUIStateProvider(widget.walletId))
|
||||
.fusionRoundsCompleted;
|
||||
|
||||
Wakelock.enable();
|
||||
WakelockPlus.enable();
|
||||
|
||||
return WillPopScope(
|
||||
onWillPop: () async {
|
||||
|
|
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,
|
||||
|
|
|
@ -13,9 +13,9 @@ import 'dart:async';
|
|||
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 '../wallet_view/transaction_views/transaction_details_view.dart';
|
||||
import '../../providers/global/wallets_provider.dart';
|
||||
import '../../themes/stack_colors.dart';
|
||||
import '../../utilities/amount/amount.dart';
|
||||
|
@ -33,6 +33,7 @@ import '../../widgets/desktop/desktop_dialog_close_button.dart';
|
|||
import '../../widgets/desktop/secondary_button.dart';
|
||||
import '../../widgets/icon_widgets/utxo_status_icon.dart';
|
||||
import '../../widgets/rounded_container.dart';
|
||||
import '../wallet_view/transaction_views/transaction_details_view.dart';
|
||||
|
||||
class UtxoDetailsView extends ConsumerStatefulWidget {
|
||||
const UtxoDetailsView({
|
||||
|
@ -97,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(),
|
||||
),
|
||||
],
|
||||
|
|
|
@ -28,6 +28,7 @@ import '../../../utilities/assets.dart';
|
|||
import '../../../utilities/clipboard_interface.dart';
|
||||
import '../../../utilities/constants.dart';
|
||||
import '../../../utilities/enums/fee_rate_type_enum.dart';
|
||||
import '../../../utilities/logger.dart';
|
||||
import '../../../utilities/text_styles.dart';
|
||||
import '../../../wallets/crypto_currency/crypto_currency.dart';
|
||||
import '../../../wallets/isar/providers/wallet_info_provider.dart';
|
||||
|
@ -315,7 +316,8 @@ class _Step4ViewState extends ConsumerState<Step4View> {
|
|||
);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
} catch (e, s) {
|
||||
Logging.instance.log("$e\n$s", level: LogLevel.Error);
|
||||
if (mounted && !wasCancelled) {
|
||||
// pop building dialog
|
||||
Navigator.of(context).pop();
|
||||
|
|
|
@ -28,12 +28,14 @@ import '../../utilities/amount/amount_formatter.dart';
|
|||
import '../../utilities/assets.dart';
|
||||
import '../../utilities/constants.dart';
|
||||
import '../../utilities/enums/fee_rate_type_enum.dart';
|
||||
import '../../utilities/logger.dart';
|
||||
import '../../utilities/text_styles.dart';
|
||||
import '../../utilities/util.dart';
|
||||
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/intermediate/lib_monero_wallet.dart';
|
||||
import '../../widgets/background.dart';
|
||||
import '../../widgets/conditional_parent.dart';
|
||||
import '../../widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
|
@ -271,6 +273,15 @@ class _SendFromCardState extends ConsumerState<SendFromCard> {
|
|||
),
|
||||
);
|
||||
|
||||
// Currently CwBasedInterface wallets (xmr/wow) shouldn't even have
|
||||
// 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 LibMoneroWallet) {
|
||||
await wallet.init();
|
||||
await wallet.open();
|
||||
}
|
||||
|
||||
final time = Future<dynamic>.delayed(
|
||||
const Duration(
|
||||
milliseconds: 2500,
|
||||
|
@ -375,7 +386,8 @@ class _SendFromCardState extends ConsumerState<SendFromCard> {
|
|||
);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
} catch (e, s) {
|
||||
Logging.instance.log("$e\n$s", level: LogLevel.Error);
|
||||
if (mounted) {
|
||||
// pop building dialog
|
||||
Navigator.of(context).pop();
|
||||
|
|
|
@ -190,6 +190,15 @@ class _TradeDetailsViewState extends ConsumerState<TradeDetailsView> {
|
|||
|
||||
final isDesktop = Util.isDesktop;
|
||||
|
||||
final showSendFromStackButton = !hasTx &&
|
||||
!["xmr", "monero", "wow", "wownero"]
|
||||
.contains(trade.payInCurrency.toLowerCase()) &&
|
||||
AppConfig.isStackCoin(trade.payInCurrency) &&
|
||||
(trade.status == "New" ||
|
||||
trade.status == "new" ||
|
||||
trade.status == "waiting" ||
|
||||
trade.status == "Waiting");
|
||||
|
||||
return ConditionalParent(
|
||||
condition: !isDesktop,
|
||||
builder: (child) => Background(
|
||||
|
@ -248,21 +257,11 @@ class _TradeDetailsViewState extends ConsumerState<TradeDetailsView> {
|
|||
children: children,
|
||||
),
|
||||
),
|
||||
if (!hasTx &&
|
||||
AppConfig.isStackCoin(trade.payInCurrency) &&
|
||||
(trade.status == "New" ||
|
||||
trade.status == "new" ||
|
||||
trade.status == "waiting" ||
|
||||
trade.status == "Waiting"))
|
||||
if (showSendFromStackButton)
|
||||
const SizedBox(
|
||||
height: 32,
|
||||
),
|
||||
if (!hasTx &&
|
||||
AppConfig.isStackCoin(trade.payInCurrency) &&
|
||||
(trade.status == "New" ||
|
||||
trade.status == "new" ||
|
||||
trade.status == "waiting" ||
|
||||
trade.status == "Waiting"))
|
||||
if (showSendFromStackButton)
|
||||
SecondaryButton(
|
||||
label: "Send from ${AppConfig.prefix}",
|
||||
buttonHeight: ButtonHeight.l,
|
||||
|
@ -1371,13 +1370,7 @@ class _TradeDetailsViewState extends ConsumerState<TradeDetailsView> {
|
|||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
if (!isDesktop &&
|
||||
!hasTx &&
|
||||
AppConfig.isStackCoin(trade.payInCurrency) &&
|
||||
(trade.status == "New" ||
|
||||
trade.status == "new" ||
|
||||
trade.status == "waiting" ||
|
||||
trade.status == "Waiting"))
|
||||
if (!isDesktop && showSendFromStackButton)
|
||||
SecondaryButton(
|
||||
label: "Send from ${AppConfig.prefix}",
|
||||
onPressed: () {
|
||||
|
|
|
@ -17,6 +17,7 @@ import 'package:flutter_svg/svg.dart';
|
|||
|
||||
import '../../app_config.dart';
|
||||
import '../../providers/global/notifications_provider.dart';
|
||||
import '../../providers/global/prefs_provider.dart';
|
||||
import '../../providers/ui/home_view_index_provider.dart';
|
||||
import '../../providers/ui/unread_notifications_provider.dart';
|
||||
import '../../services/event_bus/events/global/tor_connection_status_changed_event.dart';
|
||||
|
@ -172,6 +173,20 @@ class _HomeViewState extends ConsumerState<HomeView> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
debugPrint("BUILD: $runtimeType");
|
||||
|
||||
// dirty hack
|
||||
ref.listen(
|
||||
prefsChangeNotifierProvider.select((value) => value.enableExchange),
|
||||
(prev, next) {
|
||||
if (next == false &&
|
||||
mounted &&
|
||||
ref.read(homeViewPageIndexStateProvider) != 0) {
|
||||
WidgetsBinding.instance.addPostFrameCallback(
|
||||
(_) => ref.read(homeViewPageIndexStateProvider.state).state = 0,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return WillPopScope(
|
||||
onWillPop: _onWillPop,
|
||||
child: Background(
|
||||
|
@ -345,7 +360,8 @@ class _HomeViewState extends ConsumerState<HomeView> {
|
|||
),
|
||||
body: Column(
|
||||
children: [
|
||||
if (_children.length > 1)
|
||||
if (_children.length > 1 &&
|
||||
ref.watch(prefsChangeNotifierProvider).enableExchange)
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context)
|
||||
|
|
|
@ -44,8 +44,6 @@ class _HomeViewButtonBarState extends ConsumerState<HomeViewButtonBar> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
//todo: check if print needed
|
||||
// debugPrint("BUILD: HomeViewButtonBar");
|
||||
final selectedIndex = ref.watch(homeViewPageIndexStateProvider.state).state;
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
|
|
|
@ -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)
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue