Merge remote-tracking branch 'origin/staging' into ubuntu-24.04

# Conflicts:
#	crypto_plugins/flutter_libmonero
#	docs/building.md
#	pubspec.lock
#	scripts/app_config/templates/pubspec.template
#	scripts/linux/build_secp256k1.sh
#	scripts/windows/deps.sh
This commit is contained in:
julian 2024-11-20 11:47:17 -06:00
commit f41a85bd6e
267 changed files with 11427 additions and 4750 deletions

22
.gitignore vendored
View file

@ -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
View file

@ -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

View file

@ -1,16 +1,3 @@
buildscript {
ext.kotlin_version = '2.0.20'
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) {

View file

@ -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

View file

@ -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

View file

@ -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"

View 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

View 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

View 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

@ -1 +1 @@
Subproject commit 46a7da857d4113eb3998567b18ac0b33a470f4fd
Subproject commit 1d48c0a8aa394324e7c39267e5654038834aff95

@ -1 +1 @@
Subproject commit f8746dbef5c5ad5ed2dad12f615723d087083e9c
Subproject commit 8e9e20e2f90387dcc6e23833d186a84ad3ac372a

@ -1 +0,0 @@
Subproject commit 52bdb85f0a87793327a794aba477ac8b967c315f

@ -1 +1 @@
Subproject commit 2a74a97fb0f0e22a5280b22c010b710cdeec33bb
Subproject commit 2451deab817b456ad93d5579c0d0687cb681392a

View file

@ -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.24.2 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.2` 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.24.2
git checkout 3.24.3
echo 'export PATH="$PATH:'"$FLUTTER_DIR"'/bin"' >> "$HOME/.profile"
source "$HOME/.profile"
flutter precache
@ -93,6 +93,7 @@ rustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-andro
Linux desktop specific dependencies:
```
sudo apt-get install clang cmake ninja-build pkg-config libgtk-3-dev liblzma-dev meson python3-pip libgirepository1.0-dev valac xsltproc docbook-xsl
pip3 install --upgrade meson==0.64.1 markdown==3.4.1 markupsafe==2.1.1 jinja2==3.1.2 pygments==2.13.0 toml==0.10.2 typogrify==2.0.7 tomli==2.0.1
```
### Clone the repository and initialize submodules
@ -242,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.24.2 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
@ -312,13 +313,13 @@ If the DLLs were built on the WSL filesystem instead of on Windows, copy the res
Frostdart will be built by the Windows host later.
### Install Flutter on Windows host
Install Flutter 3.24.2 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.2` 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.24.2
git checkout 3.24.3
setx PATH "%PATH%;%FLUTTER_DIR%\bin"
echo Flutter setup completed. Please restart your command prompt.
```

View file

@ -1 +0,0 @@
MoneroWallet

View file

@ -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>

View file

@ -9,6 +9,8 @@ PODS:
- connectivity_plus (0.0.1):
- Flutter
- ReachabilitySwift
- cs_monero_flutter_libs (0.0.1):
- Flutter
- device_info_plus (0.0.1):
- Flutter
- devicelocale (0.0.1):
@ -50,8 +52,6 @@ PODS:
- Flutter (1.0.0)
- flutter_libepiccash (0.0.1):
- Flutter
- flutter_libmonero (0.0.1):
- Flutter
- flutter_libsparkmobile (0.0.1):
- Flutter
- flutter_local_notifications (0.0.1):
@ -67,15 +67,16 @@ PODS:
- Flutter
- lelantus (0.0.1):
- Flutter
- local_auth (0.0.1):
- local_auth_darwin (0.0.1):
- Flutter
- FlutterMacOS
- MTBBarcodeScanner (5.0.11)
- package_info_plus (0.4.5):
- Flutter
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- permission_handler_apple (9.1.1):
- permission_handler_apple (9.3.0):
- Flutter
- ReachabilitySwift (5.0.0)
- SDWebImage (5.13.2):
@ -86,6 +87,8 @@ PODS:
- "sqlite3 (3.46.0+1)":
- "sqlite3/common (= 3.46.0+1)"
- "sqlite3/common (3.46.0+1)"
- "sqlite3/dbstatvtab (3.46.0+1)":
- sqlite3/common
- "sqlite3/fts5 (3.46.0+1)":
- sqlite3/common
- "sqlite3/perf-threadsafe (3.46.0+1)":
@ -94,7 +97,8 @@ PODS:
- sqlite3/common
- sqlite3_flutter_libs (0.0.1):
- Flutter
- sqlite3 (~> 3.46.0)
- "sqlite3 (~> 3.46.0+1)"
- sqlite3/dbstatvtab
- sqlite3/fts5
- sqlite3/perf-threadsafe
- sqlite3/rtree
@ -106,19 +110,19 @@ PODS:
- Flutter
- url_launcher_ios (0.0.1):
- Flutter
- wakelock (0.0.1):
- wakelock_plus (0.0.1):
- Flutter
DEPENDENCIES:
- barcode_scan2 (from `.symlinks/plugins/barcode_scan2/ios`)
- coinlib_flutter (from `.symlinks/plugins/coinlib_flutter/darwin`)
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
- cs_monero_flutter_libs (from `.symlinks/plugins/cs_monero_flutter_libs/ios`)
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
- devicelocale (from `.symlinks/plugins/devicelocale/ios`)
- file_picker (from `.symlinks/plugins/file_picker/ios`)
- Flutter (from `Flutter`)
- flutter_libepiccash (from `.symlinks/plugins/flutter_libepiccash/ios`)
- flutter_libmonero (from `.symlinks/plugins/flutter_libmonero/ios`)
- flutter_libsparkmobile (from `.symlinks/plugins/flutter_libsparkmobile/ios`)
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
@ -127,7 +131,7 @@ DEPENDENCIES:
- integration_test (from `.symlinks/plugins/integration_test/ios`)
- isar_flutter_libs (from `.symlinks/plugins/isar_flutter_libs/ios`)
- lelantus (from `.symlinks/plugins/lelantus/ios`)
- local_auth (from `.symlinks/plugins/local_auth/ios`)
- local_auth_darwin (from `.symlinks/plugins/local_auth_darwin/darwin`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
@ -136,7 +140,7 @@ DEPENDENCIES:
- stack_wallet_backup (from `.symlinks/plugins/stack_wallet_backup/ios`)
- tor_ffi_plugin (from `.symlinks/plugins/tor_ffi_plugin/ios`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
- wakelock (from `.symlinks/plugins/wakelock/ios`)
- wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`)
SPEC REPOS:
trunk:
@ -156,6 +160,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/coinlib_flutter/darwin"
connectivity_plus:
:path: ".symlinks/plugins/connectivity_plus/ios"
cs_monero_flutter_libs:
:path: ".symlinks/plugins/cs_monero_flutter_libs/ios"
device_info_plus:
:path: ".symlinks/plugins/device_info_plus/ios"
devicelocale:
@ -166,8 +172,6 @@ EXTERNAL SOURCES:
:path: Flutter
flutter_libepiccash:
:path: ".symlinks/plugins/flutter_libepiccash/ios"
flutter_libmonero:
:path: ".symlinks/plugins/flutter_libmonero/ios"
flutter_libsparkmobile:
:path: ".symlinks/plugins/flutter_libsparkmobile/ios"
flutter_local_notifications:
@ -184,8 +188,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/isar_flutter_libs/ios"
lelantus:
:path: ".symlinks/plugins/lelantus/ios"
local_auth:
:path: ".symlinks/plugins/local_auth/ios"
local_auth_darwin:
:path: ".symlinks/plugins/local_auth_darwin/darwin"
package_info_plus:
:path: ".symlinks/plugins/package_info_plus/ios"
path_provider_foundation:
@ -202,45 +206,45 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/tor_ffi_plugin/ios"
url_launcher_ios:
:path: ".symlinks/plugins/url_launcher_ios/ios"
wakelock:
:path: ".symlinks/plugins/wakelock/ios"
wakelock_plus:
:path: ".symlinks/plugins/wakelock_plus/ios"
SPEC CHECKSUMS:
barcode_scan2: 0af2bb63c81b4565aab6cd78278e4c0fa136dbb0
coinlib_flutter: 9275e8255ef67d3da33beb6e117d09ced4f46eb5
connectivity_plus: 07c49e96d7fc92bc9920617b83238c4d178b446a
cs_monero_flutter_libs: 43cda3474c2bc907f2b2b5bb26fd89cb864fcfc6
device_info_plus: 97af1d7e84681a90d0693e63169a5d50e0839a0d
devicelocale: b22617f40038496deffba44747101255cee005b0
devicelocale: 35ba84dc7f45f527c3001535d8c8d104edd5d926
DKImagePickerController: b512c28220a2b8ac7419f21c491fc8534b7601ac
DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179
file_picker: 09aa5ec1ab24135ccd7a1621c46c84134bfd6655
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
flutter_libepiccash: 36241aa7d3126f6521529985ccb3dc5eaf7bb317
flutter_libmonero: da68a616b73dd0374a8419c684fa6b6df2c44ffe
flutter_libsparkmobile: 6373955cc3327a926d17059e7405dde2fb12f99f
flutter_local_notifications: 4cde75091f6327eb8517fa068a0a5950212d2086
flutter_native_splash: 52501b97d1c0a5f898d687f1646226c1f93c56ef
flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be
frostdart: 4c72b69ccac2f13ede744107db046a125acce597
integration_test: 252f60fa39af5e17c3aa9899d35d908a0721b573
isar_flutter_libs: b69f437aeab9c521821c3f376198c4371fa21073
isar_flutter_libs: fdf730ca925d05687f36d7f1d355e482529ed097
lelantus: 417f0221260013dfc052cae9cf4b741b6479edba
local_auth: 1740f55d7af0a2e2a8684ce225fe79d8931e808c
local_auth_darwin: 66e40372f1c29f383a314c738c7446e2f7fdadc3
MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb
package_info_plus: fd030dabf36271f146f1f3beacd48f564b0f17f7
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6
package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
SDWebImage: 72f86271a6f3139cc7e4a89220946489d4b9a866
share_plus: 599aa54e4ea31d4b4c0e9c911bcc26c55e791028
share_plus: c3fef564749587fc939ef86ffb283ceac0baf9f5
sqlite3: 292c3e1bfe89f64e51ea7fc7dab9182a017c8630
sqlite3_flutter_libs: 0d611efdf6d1c9297d5ab03dab21b75aeebdae31
sqlite3_flutter_libs: c00457ebd31e59fa6bb830380ddba24d44fbcd3b
stack_wallet_backup: 5b8563aba5d8ffbf2ce1944331ff7294a0ec7c03
SwiftProtobuf: 6ef3f0e422ef90d6605ca20b21a94f6c1324d6b3
SwiftyGif: 6c3eafd0ce693cad58bb63d2b2fb9bacb8552780
tor_ffi_plugin: d80e291b649379c8176e1be739e49be007d4ef93
url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4
wakelock: d0fc7c864128eac40eba1617cb5264d9c940b46f
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
wakelock_plus: 78ec7c5b202cab7761af8e2b2b3d0671be6c4ae1
PODFILE CHECKSUM: 57c8aed26fba39d3ec9424816221f294a07c58eb

View file

@ -1 +0,0 @@
WowneroWallet

View file

@ -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>

View file

@ -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) {

View file

@ -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([

View file

@ -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

View file

@ -1,6 +1,8 @@
import 'dart:async';
import 'package:electrum_adapter/electrum_adapter.dart';
import '../utilities/logger.dart';
import '../wallets/crypto_currency/crypto_currency.dart';
class ClientManager {
@ -37,13 +39,19 @@ class ClientManager {
}
_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 {

View file

@ -490,7 +490,15 @@ class ElectrumXClient {
command: 'server.ping',
requestTimeout: const Duration(seconds: 2),
retries: retryCount,
).timeout(const Duration(seconds: 2)) as bool;
).timeout(
const Duration(seconds: 2),
onTimeout: () {
Logging.instance.log(
"ElectrumxClient.ping timed out with retryCount=$retryCount, host=$_host",
level: LogLevel.Debug,
);
},
) as bool;
} catch (e) {
rethrow;
}

View file

@ -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,9 +226,6 @@ void main(List<String> args) async {
}
}
monero.onStartup();
wownero.onStartup();
// SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual,
// overlays: [SystemUiOverlay.bottom]);
unawaited(NotificationApi.init());

View file

@ -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(

View file

@ -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(

View file

@ -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(

View file

@ -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(

View file

@ -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";
}
}
}

View file

@ -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');
}
@ -1474,6 +1483,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 +1656,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 +1755,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 +1815,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 +1879,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 +1892,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',

View file

@ -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',

View file

@ -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',

View file

@ -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(

View file

@ -8,6 +8,7 @@
*
*/
import 'dart:convert';
import 'dart:math';
import 'package:isar/isar.dart';
@ -84,6 +85,20 @@ class UTXO {
return confirmations >= 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({
Id? id,
String? walletId,

View file

@ -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(

View file

@ -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',

View file

@ -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',

View file

@ -134,6 +134,14 @@ class TransactionV2 {
}
Amount getAmountReceivedInThisWallet({required int fractionDigits}) {
if (_isMonero()) {
if (type == TransactionType.incoming) {
return _getMoneroAmount()!;
} else {
return Amount.zeroWith(fractionDigits: fractionDigits);
}
}
final outSum = outputs
.where((e) => e.walletOwns)
.fold(BigInt.zero, (p, e) => p + e.value);
@ -151,6 +159,14 @@ class TransactionV2 {
}
Amount getAmountSentFromThisWallet({required int fractionDigits}) {
if (_isMonero()) {
if (type == TransactionType.outgoing) {
return _getMoneroAmount()!;
} else {
return Amount.zeroWith(fractionDigits: fractionDigits);
}
}
final inSum = inputs
.where((e) => e.walletOwns)
.fold(BigInt.zero, (p, e) => p + e.value);
@ -191,6 +207,21 @@ 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,
@ -307,4 +338,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";
}

View file

@ -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(

View file

@ -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',

View file

@ -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(

View file

@ -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(

View file

@ -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(

View file

@ -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(

View file

@ -7,7 +7,7 @@ part of 'sent_to_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 GetSentToAddressCollection on Isar {
IsarCollection<SentToAddress> get sentToAddress => this.collection();
@ -81,7 +81,7 @@ const SentToAddressSchema = CollectionSchema(
getId: _sentToAddressGetId,
getLinks: _sentToAddressGetLinks,
attach: _sentToAddressAttach,
version: '3.0.5',
version: '3.1.8',
);
int _sentToAddressEstimateSize(

View file

@ -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(

View file

@ -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(

View file

@ -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',

View 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,
},
),
],
});
}

View file

@ -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;
}

View file

@ -69,6 +69,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;
@ -139,7 +140,7 @@ class _AddWalletViewState extends ConsumerState<AddWalletView> {
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 +287,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 +428,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",

View file

@ -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(

View file

@ -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;

View file

@ -560,6 +560,8 @@ class _NewWalletRecoveryPhraseWarningViewState
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
@ -580,7 +582,12 @@ class _NewWalletRecoveryPhraseWarningViewState
)
.state!
.mnemonicPassphrase;
} else {}
} else {
// this may not be epiccash specific?
if (coin is Epiccash) {
mnemonicPassphrase = "";
}
}
wordCount = ref
.read(

View file

@ -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,
),
],
);
}
}

View file

@ -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,
),
],
),
),
),
),
);
},
),
),
);
}
}

View file

@ -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';
@ -86,7 +87,7 @@ class RestoreWalletView extends ConsumerStatefulWidget {
final CryptoCurrency coin;
final String mnemonicPassphrase;
final int seedWordsLength;
final DateTime restoreFromDate;
final DateTime? restoreFromDate;
final bool enableLelantusScanning;
final BarcodeScannerInterface barcodeScanner;
@ -181,7 +182,7 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
if (widget.coin is Monero) {
switch (widget.seedWordsLength) {
case 25:
return libxmr.monero.getMoneroWordList("English").contains(word);
return lib_monero.getMoneroWordList("English").contains(word);
case 16:
return Monero.sixteenWordsWordList.contains(word);
default:
@ -189,7 +190,7 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
}
}
if (widget.coin is Wownero) {
final wowneroWordList = libwow.wownero.getWowneroWordList(
final wowneroWordList = lib_monero.getWowneroWordList(
"English",
seedWordsLength: widget.seedWordsLength,
);
@ -219,29 +220,35 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
int height = 0;
String? otherDataJsonString;
if (widget.coin is Monero) {
height = libxmr.monero.getHeigthByDate(date: widget.restoreFromDate);
} else if (widget.coin is Wownero) {
height = libwow.wownero.getHeightByDate(date: widget.restoreFromDate);
if (widget.restoreFromDate != null) {
if (widget.coin is Monero) {
height = cs_monero_deprecated.getMoneroHeightByDate(
date: widget.restoreFromDate!,
);
}
if (widget.coin is Wownero) {
height = cs_monero_deprecated.getWowneroHeightByDate(
date: widget.restoreFromDate!,
);
}
if (height < 0) {
height = 0;
}
}
// todo: wait until this implemented
// else if (widget.coin is Wownero) {
// height = wownero.getHeightByDate(date: widget.restoreFromDate);
// }
// TODO: make more robust estimate of date maybe using https://explorer.epic.tech/api-index
if (widget.coin is Epiccash) {
final int secondsSinceEpoch =
widget.restoreFromDate.millisecondsSinceEpoch ~/ 1000;
const int epicCashFirstBlock = 1565370278;
const double overestimateSecondsPerBlock = 61;
final int chosenSeconds = secondsSinceEpoch - epicCashFirstBlock;
final int approximateHeight =
chosenSeconds ~/ overestimateSecondsPerBlock;
//todo: check if print needed
// debugPrint(
// "approximate height: $approximateHeight chosen_seconds: $chosenSeconds");
height = approximateHeight;
if (widget.restoreFromDate != null) {
final int secondsSinceEpoch =
widget.restoreFromDate!.millisecondsSinceEpoch ~/ 1000;
const int epicCashFirstBlock = 1565370278;
const double overestimateSecondsPerBlock = 61;
final int chosenSeconds = secondsSinceEpoch - epicCashFirstBlock;
final int approximateHeight =
chosenSeconds ~/ overestimateSecondsPerBlock;
height = approximateHeight;
}
if (height < 0) {
height = 0;
}
@ -282,7 +289,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,
@ -426,12 +433,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 +470,7 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
}
if (!Platform.isLinux && !isDesktop) {
await Wakelock.disable();
await WakelockPlus.disable();
}
}
}
@ -648,16 +655,18 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
const Duration(milliseconds: 100),
);
await showDialog<dynamic>(
context: context,
useSafeArea: false,
barrierDismissible: true,
builder: (context) {
return ConfirmRecoveryDialog(
onConfirm: attemptRestore,
);
},
);
if (mounted) {
await showDialog<dynamic>(
context: context,
useSafeArea: false,
barrierDismissible: true,
builder: (context) {
return ConfirmRecoveryDialog(
onConfirm: attemptRestore,
);
},
);
}
}
@override

View file

@ -9,13 +9,17 @@
*/
import 'dart:async';
import 'dart:convert';
import 'dart:math';
import 'package:cs_monero/src/deprecated/get_height_by_date.dart'
as cs_monero_deprecated;
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:tuple/tuple.dart';
import '../../../models/keys/view_only_wallet_data.dart';
import '../../../notifications/show_flush_bar.dart';
import '../../../pages_desktop_specific/desktop_home_view.dart';
import '../../../pages_desktop_specific/my_stack_view/exit_to_my_stack_button.dart';
@ -25,14 +29,25 @@ import '../../../providers/providers.dart';
import '../../../themes/stack_colors.dart';
import '../../../utilities/assets.dart';
import '../../../utilities/constants.dart';
import '../../../utilities/logger.dart';
import '../../../utilities/show_loading.dart';
import '../../../utilities/text_styles.dart';
import '../../../utilities/util.dart';
import '../../../wallets/crypto_currency/coins/ethereum.dart';
import '../../../wallets/crypto_currency/crypto_currency.dart';
import '../../../wallets/crypto_currency/intermediate/bip39_hd_currency.dart';
import '../../../wallets/isar/models/wallet_info.dart';
import '../../../wallets/isar/providers/wallet_info_provider.dart';
import '../../../wallets/wallet/impl/epiccash_wallet.dart';
import '../../../wallets/wallet/impl/monero_wallet.dart';
import '../../../wallets/wallet/impl/wownero_wallet.dart';
import '../../../wallets/wallet/intermediate/lib_monero_wallet.dart';
import '../../../wallets/wallet/wallet.dart';
import '../../../wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.dart';
import '../../../wallets/wallet/wallet_mixin_interfaces/view_only_option_interface.dart';
import '../../../widgets/custom_buttons/app_bar_icon_button.dart';
import '../../../widgets/desktop/desktop_app_bar.dart';
import '../../../widgets/desktop/desktop_scaffold.dart';
import '../../../widgets/stack_dialog.dart';
import '../../home_view/home_view.dart';
import '../add_token_view/edit_wallet_tokens_view.dart';
import '../new_wallet_options/new_wallet_options_view.dart';
@ -64,46 +79,25 @@ class _VerifyRecoveryPhraseViewState
extends ConsumerState<VerifyRecoveryPhraseView>
// with WidgetsBindingObserver
{
late Wallet _wallet;
late String _walletId;
late CryptoCurrency _coin;
late List<String> _mnemonic;
late final bool isDesktop;
@override
void initState() {
_wallet = widget.wallet;
_walletId = widget.wallet.walletId;
_coin = widget.wallet.cryptoCurrency;
_mnemonic = widget.mnemonic;
isDesktop = Util.isDesktop;
// WidgetsBinding.instance?.addObserver(this);
super.initState();
}
@override
dispose() {
// WidgetsBinding.instance?.removeObserver(this);
super.dispose();
}
// @override
// void didChangeAppLifecycleState(AppLifecycleState state) {
// switch (state) {
// case AppLifecycleState.inactive:
// debugPrint(
// "VerifyRecoveryPhraseView ========================= Inactive");
// break;
// case AppLifecycleState.paused:
// debugPrint("VerifyRecoveryPhraseView ========================= Paused");
// break;
// case AppLifecycleState.resumed:
// debugPrint(
// "VerifyRecoveryPhraseView ========================= Resumed");
// break;
// case AppLifecycleState.detached:
// debugPrint(
// "VerifyRecoveryPhraseView ========================= Detached");
// break;
// }
// }
Future<bool> _verifyMnemonicPassphrase() async {
final result = await showDialog<String?>(
context: context,
@ -113,6 +107,157 @@ class _VerifyRecoveryPhraseViewState
return result == "verified";
}
Future<void> _convertToViewOnly() async {
int height = 0;
final Map<String, dynamic> otherDataJson = {
WalletInfoKeys.isViewOnlyKey: true,
};
final ViewOnlyWalletType viewOnlyWalletType;
if (widget.wallet is ExtendedKeysInterface) {
if (widget.wallet.cryptoCurrency is Firo) {
otherDataJson.addAll(
{
WalletInfoKeys.lelantusCoinIsarRescanRequired: false,
WalletInfoKeys.enableLelantusScanning: false,
},
);
}
viewOnlyWalletType = ViewOnlyWalletType.xPub;
} else if (widget.wallet is LibMoneroWallet) {
if (widget.wallet.cryptoCurrency is Monero) {
height = cs_monero_deprecated.getMoneroHeightByDate(
date: DateTime.now().subtract(const Duration(days: 7)),
);
}
if (widget.wallet.cryptoCurrency is Wownero) {
height = cs_monero_deprecated.getWowneroHeightByDate(
date: DateTime.now().subtract(const Duration(days: 7)),
);
}
if (height < 0) height = 0;
viewOnlyWalletType = ViewOnlyWalletType.cryptonote;
} else {
throw Exception(
"Unsupported view only wallet type found: ${widget.wallet.runtimeType}",
);
}
otherDataJson[WalletInfoKeys.viewOnlyTypeIndexKey] =
viewOnlyWalletType.index;
final voInfo = WalletInfo.createNew(
coin: _coin,
name: widget.wallet.info.name,
restoreHeight: height,
otherDataJsonString: jsonEncode(otherDataJson),
);
final ViewOnlyWalletData viewOnlyData;
if (widget.wallet is ExtendedKeysInterface) {
final extendedKeyInfo =
await (widget.wallet as ExtendedKeysInterface).getXPubs();
final testPath = (_coin as Bip39HDCurrency).constructDerivePath(
derivePathType: (_coin as Bip39HDCurrency).defaultDerivePathType,
chain: 0,
index: 0,
);
XPub? xPub;
for (final pub in extendedKeyInfo.xpubs) {
if (testPath.startsWith(pub.path)) {
xPub = pub;
break;
}
}
if (xPub == null) {
throw Exception("Default derivation path not matched in xPubs");
}
viewOnlyData = ExtendedKeysViewOnlyWalletData(
walletId: voInfo.walletId,
xPubs: [xPub],
);
} else if (widget.wallet is LibMoneroWallet) {
final w = widget.wallet as LibMoneroWallet;
final info = await w
.hackToCreateNewViewOnlyWalletDataFromNewlyCreatedWalletThisFunctionShouldNotBeCalledUnlessYouKnowWhatYouAreDoing();
final address = info.$1;
final privateViewKey = info.$2;
await w.exit();
viewOnlyData = CryptonoteViewOnlyWalletData(
walletId: voInfo.walletId,
address: address,
privateViewKey: privateViewKey,
);
} else {
throw Exception(
"Unsupported view only wallet type found: ${widget.wallet.runtimeType}",
);
}
final voWallet = await Wallet.create(
walletInfo: voInfo,
mainDB: ref.read(mainDBProvider),
secureStorageInterface: ref.read(secureStoreProvider),
nodeService: ref.read(nodeServiceChangeNotifierProvider),
prefs: ref.read(prefsChangeNotifierProvider),
viewOnlyData: viewOnlyData,
);
try {
// TODO: extract interface with isRestore param
switch (voWallet.runtimeType) {
case const (EpiccashWallet):
await (voWallet as EpiccashWallet).init(isRestore: true);
break;
case const (MoneroWallet):
await (voWallet as MoneroWallet).init(isRestore: true);
break;
case const (WowneroWallet):
await (voWallet as WowneroWallet).init(isRestore: true);
break;
default:
await voWallet.init();
}
await voWallet.recover(isRescan: false);
// don't remove this setMnemonicVerified thing
await voWallet.info.setMnemonicVerified(
isar: ref.read(mainDBProvider).isar,
);
ref.read(pWallets).addWallet(voWallet);
await voWallet.exit();
await ref.read(pWallets).deleteWallet(
widget.wallet.info,
ref.read(secureStoreProvider),
);
} catch (e) {
await ref.read(pWallets).deleteWallet(
widget.wallet.info,
ref.read(secureStoreProvider),
);
await ref.read(pWallets).deleteWallet(
voWallet.info,
ref.read(secureStoreProvider),
);
rethrow;
}
}
Future<void> _continue(bool isMatch) async {
if (isMatch) {
if (ref.read(pNewWalletOptions) != null &&
@ -124,11 +269,11 @@ class _VerifyRecoveryPhraseViewState
}
}
await ref.read(pWalletInfo(_wallet.walletId)).setMnemonicVerified(
await ref.read(pWalletInfo(widget.wallet.walletId)).setMnemonicVerified(
isar: ref.read(mainDBProvider).isar,
);
ref.read(pWallets).addWallet(_wallet);
ref.read(pWallets).addWallet(widget.wallet);
final isCreateSpecialEthWallet =
ref.read(createSpecialEthWalletRoutingFlag);
@ -142,6 +287,51 @@ class _VerifyRecoveryPhraseViewState
.state;
}
if (mounted &&
ref.read(pNewWalletOptions)?.convertToViewOnly == true &&
widget.wallet is ViewOnlyOptionInterface) {
try {
Exception? ex;
await showLoading(
whileFuture: _convertToViewOnly(),
context: context,
message: "Converting to view only wallet",
rootNavigator: Util.isDesktop,
onException: (e) {
ex = e;
},
);
if (ex != null) {
throw ex!;
}
} catch (e, s) {
Logging.instance.log(
"$e\n$s",
level: LogLevel.Fatal,
);
if (mounted) {
await showDialog<void>(
context: context,
builder: (_) => StackOkDialog(
title: e.toString(),
desktopPopRootNavigator: Util.isDesktop,
),
);
}
if (mounted) {
Navigator.of(context).popUntil(
ModalRoute.withName(
NewWalletRecoveryPhraseView.routeName,
),
);
}
return;
}
}
if (mounted) {
if (isDesktop) {
if (isCreateSpecialEthWallet) {
@ -156,7 +346,7 @@ class _VerifyRecoveryPhraseViewState
DesktopHomeView.routeName,
),
);
if (widget.wallet.info.coin is Ethereum) {
if (_coin is Ethereum) {
unawaited(
Navigator.of(context).pushNamed(
EditWalletTokensView.routeName,
@ -179,7 +369,7 @@ class _VerifyRecoveryPhraseViewState
(route) => false,
),
);
if (widget.wallet.info.coin is Ethereum) {
if (_coin is Ethereum) {
unawaited(
Navigator.of(context).pushNamed(
EditWalletTokensView.routeName,
@ -269,7 +459,7 @@ class _VerifyRecoveryPhraseViewState
Future<void> delete() async {
await ref.read(pWallets).deleteWallet(
_wallet.info,
widget.wallet.info,
ref.read(secureStoreProvider),
);
}
@ -299,7 +489,7 @@ class _VerifyRecoveryPhraseViewState
trailing: ExitToMyStackButton(
onPressed: () async {
await delete();
if (mounted) {
if (context.mounted) {
Navigator.of(context).popUntil(
ModalRoute.withName(DesktopHomeView.routeName),
);

View file

@ -66,6 +66,62 @@ class _NewContactAddressEntryFormState
List<CryptoCurrency> coins = [];
void _onQrTapped() async {
try {
// ref
// .read(shouldShowLockscreenOnResumeStateProvider
// .state)
// .state = false;
final qrResult = await widget.barcodeScanner.scan();
// Future<void>.delayed(
// const Duration(seconds: 2),
// () => ref
// .read(
// shouldShowLockscreenOnResumeStateProvider
// .state)
// .state = true,
// );
final paymentData = AddressUtils.parsePaymentUri(
qrResult.rawContent,
logging: Logging.instance,
);
if (paymentData != null) {
addressController.text = paymentData.address;
ref.read(addressEntryDataProvider(widget.id)).address =
addressController.text.isEmpty ? null : addressController.text;
addressLabelController.text =
paymentData.label ?? addressLabelController.text;
ref.read(addressEntryDataProvider(widget.id)).addressLabel =
addressLabelController.text.isEmpty
? null
: addressLabelController.text;
// now check for non standard encoded basic address
} else if (ref.read(addressEntryDataProvider(widget.id)).coin != null) {
if (ref.read(addressEntryDataProvider(widget.id)).coin!.validateAddress(
qrResult.rawContent,
)) {
addressController.text = qrResult.rawContent;
ref.read(addressEntryDataProvider(widget.id)).address =
qrResult.rawContent;
}
}
} on PlatformException catch (e, s) {
// ref
// .read(shouldShowLockscreenOnResumeStateProvider
// .state)
// .state = true;
Logging.instance.log(
"Failed to get camera permissions to scan address qr code: $e\n$s",
level: LogLevel.Warning,
);
}
}
@override
void initState() {
addressLabelController = TextEditingController()
@ -404,71 +460,7 @@ class _NewContactAddressEntryFormState
null)
TextFieldIconButton(
key: const Key("addAddressBookEntryScanQrButtonKey"),
onTap: () async {
try {
// ref
// .read(shouldShowLockscreenOnResumeStateProvider
// .state)
// .state = false;
final qrResult = await widget.barcodeScanner.scan();
// Future<void>.delayed(
// const Duration(seconds: 2),
// () => ref
// .read(
// shouldShowLockscreenOnResumeStateProvider
// .state)
// .state = true,
// );
final results =
AddressUtils.parseUri(qrResult.rawContent);
if (results.isNotEmpty) {
addressController.text = results["address"] ?? "";
ref
.read(addressEntryDataProvider(widget.id))
.address =
addressController.text.isEmpty
? null
: addressController.text;
addressLabelController.text = results["label"] ??
addressLabelController.text;
ref
.read(addressEntryDataProvider(widget.id))
.addressLabel =
addressLabelController.text.isEmpty
? null
: addressLabelController.text;
// now check for non standard encoded basic address
} else if (ref
.read(addressEntryDataProvider(widget.id))
.coin !=
null) {
if (ref
.read(addressEntryDataProvider(widget.id))
.coin!
.validateAddress(
qrResult.rawContent,
)) {
addressController.text = qrResult.rawContent;
ref
.read(addressEntryDataProvider(widget.id))
.address = qrResult.rawContent;
}
}
} on PlatformException catch (e, s) {
// ref
// .read(shouldShowLockscreenOnResumeStateProvider
// .state)
// .state = true;
Logging.instance.log(
"Failed to get camera permissions to scan address qr code: $e\n$s",
level: LogLevel.Warning,
);
}
},
onTap: _onQrTapped,
child: const QrCodeIcon(),
),
const SizedBox(

View file

@ -713,6 +713,60 @@ class _BuyFormState extends ConsumerState<BuyForm> {
}
}
void _onQrTapped() async {
try {
if (FocusScope.of(context).hasFocus) {
FocusScope.of(context).unfocus();
await Future<void>.delayed(
const Duration(milliseconds: 75),
);
}
final qrResult = await scanner.scan();
Logging.instance.log(
"qrResult content: ${qrResult.rawContent}",
level: LogLevel.Info,
);
final paymentData = AddressUtils.parsePaymentUri(
qrResult.rawContent,
logging: Logging.instance,
);
Logging.instance.log(
"qrResult parsed: $paymentData",
level: LogLevel.Info,
);
if (paymentData != null) {
// auto fill address
_address = paymentData.address;
_receiveAddressController.text = _address!;
setState(() {
_addressToggleFlag = _receiveAddressController.text.isNotEmpty;
});
// now check for non standard encoded basic address
} else {
_address = qrResult.rawContent;
_receiveAddressController.text = _address ?? "";
setState(() {
_addressToggleFlag = _receiveAddressController.text.isNotEmpty;
});
}
} on PlatformException catch (e, s) {
// here we ignore the exception caused by not giving permission
// to use the camera to scan a qr code
Logging.instance.log(
"Failed to get camera permissions while trying to scan qr code in SendView: $e\n$s",
level: LogLevel.Warning,
);
}
}
@override
void initState() {
_receiveAddressController = TextEditingController();
@ -1375,63 +1429,7 @@ class _BuyFormState extends ConsumerState<BuyForm> {
!isDesktop)
TextFieldIconButton(
key: const Key("buyViewScanQrButtonKey"),
onTap: () async {
try {
if (FocusScope.of(context).hasFocus) {
FocusScope.of(context).unfocus();
await Future<void>.delayed(
const Duration(milliseconds: 75),
);
}
final qrResult = await scanner.scan();
Logging.instance.log(
"qrResult content: ${qrResult.rawContent}",
level: LogLevel.Info,
);
final results = AddressUtils.parseUri(
qrResult.rawContent,
);
Logging.instance.log(
"qrResult parsed: $results",
level: LogLevel.Info,
);
if (results.isNotEmpty) {
// auto fill address
_address = results["address"] ?? "";
_receiveAddressController.text = _address!;
setState(() {
_addressToggleFlag =
_receiveAddressController
.text.isNotEmpty;
});
// now check for non standard encoded basic address
} else {
_address = qrResult.rawContent;
_receiveAddressController.text =
_address ?? "";
setState(() {
_addressToggleFlag =
_receiveAddressController
.text.isNotEmpty;
});
}
} on PlatformException catch (e, s) {
// here we ignore the exception caused by not giving permission
// to use the camera to scan a qr code
Logging.instance.log(
"Failed to get camera permissions while trying to scan qr code in SendView: $e\n$s",
level: LogLevel.Warning,
);
}
},
onTap: _onQrTapped,
child: const QrCodeIcon(),
),
],

View file

@ -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 {

View 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();
},
),
),
],
),
),
],
),
),
);
}
}

View file

@ -0,0 +1,259 @@
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/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,
),
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();
}
}
},
),
],
),
),
),
),
);
},
),
),
),
),
);
}
}

View 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,
),
],
),
],
),
),
),
);
}
}

View 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,
),
],
),
),
),
),
);
},
),
),
),
);
}
}

View file

@ -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({

View file

@ -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(),
),
],

View file

@ -35,7 +35,7 @@ import '../../wallets/crypto_currency/crypto_currency.dart';
import '../../wallets/isar/providers/wallet_info_provider.dart';
import '../../wallets/models/tx_data.dart';
import '../../wallets/wallet/impl/firo_wallet.dart';
import '../../wallets/wallet/wallet_mixin_interfaces/cw_based_interface.dart';
import '../../wallets/wallet/intermediate/lib_monero_wallet.dart';
import '../../widgets/background.dart';
import '../../widgets/conditional_parent.dart';
import '../../widgets/custom_buttons/app_bar_icon_button.dart';
@ -277,7 +277,7 @@ class _SendFromCardState extends ConsumerState<SendFromCard> {
// access to this screen but this is needed to get past an error that
// would occur only to lead to another error which is why xmr/wow wallets
// don't have access to this screen currently
if (wallet is CwBasedInterface) {
if (wallet is LibMoneroWallet) {
await wallet.init();
await wallet.open();
}

View file

@ -13,10 +13,8 @@ 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 +28,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({
@ -197,7 +198,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());

View file

@ -11,8 +11,10 @@
import 'dart:typed_data';
import 'package:flutter/material.dart';
import '../../../networking/http.dart';
import '../../../services/tor_service.dart';
import '../../../utilities/paynym_is_api.dart';
import '../../../utilities/prefs.dart';
class PayNymBot extends StatelessWidget {
@ -50,7 +52,8 @@ class PayNymBot extends StatelessWidget {
Future<Uint8List> _fetchImage() async {
final HTTP client = HTTP();
final Uri uri = Uri.parse("https://paynym.is/$paymentCodeString/avatar");
final Uri uri =
Uri.parse("${PaynymIsApi.baseURL}/$paymentCodeString/avatar");
final response = await client.get(
url: uri,

View file

@ -26,7 +26,7 @@ import '../../utilities/biometrics.dart';
import '../../utilities/flutter_secure_storage_interface.dart';
import '../../utilities/show_loading.dart';
import '../../utilities/text_styles.dart';
import '../../wallets/wallet/wallet_mixin_interfaces/cw_based_interface.dart';
import '../../wallets/wallet/intermediate/lib_monero_wallet.dart';
import '../../widgets/background.dart';
import '../../widgets/custom_buttons/app_bar_icon_button.dart';
import '../../widgets/custom_buttons/blue_text_button.dart';
@ -102,7 +102,7 @@ class _LockscreenViewState extends ConsumerState<LockscreenView> {
final wallet = ref.read(pWallets).getWallet(walletId);
final Future<void> loadFuture;
if (wallet is CwBasedInterface) {
if (wallet is LibMoneroWallet) {
loadFuture =
wallet.init().then((value) async => await (wallet).open());
} else {

View file

@ -8,6 +8,7 @@
*
*/
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:isar/isar.dart';
@ -147,6 +148,7 @@ class _AddressDetailsViewState extends ConsumerState<AddressDetailsView> {
@override
Widget build(BuildContext context) {
final coin = ref.watch(pWalletCoin(widget.walletId));
final wallet = ref.watch(pWallets).getWallet(widget.walletId);
return ConditionalParent(
condition: !isDesktop,
builder: (child) => Background(
@ -372,13 +374,21 @@ class _AddressDetailsViewState extends ConsumerState<AddressDetailsView> {
detail: address.subType.prettyName,
button: Container(),
),
if (ref.watch(pWallets).getWallet(widget.walletId)
is Bip39HDWallet)
if (kDebugMode)
const _Div(
height: 12,
),
if (ref.watch(pWallets).getWallet(widget.walletId)
is Bip39HDWallet)
if (kDebugMode)
DetailItem(
title: "frost secure (kDebugMode)",
detail: address.zSafeFrost.toString(),
button: Container(),
),
if (wallet is Bip39HDWallet && !wallet.isViewOnly)
const _Div(
height: 12,
),
if (wallet is Bip39HDWallet && !wallet.isViewOnly)
AddressPrivateKey(
walletId: widget.walletId,
address: address,

View file

@ -67,6 +67,19 @@ class _WalletAddressesViewState extends ConsumerState<WalletAddressesView> {
.and()
.not()
.typeEqualTo(AddressType.nonWallet)
.and()
.group(
(q) => q
.group(
(q2) => q2
.typeEqualTo(AddressType.frostMS)
.and()
.zSafeFrostEqualTo(true),
)
.or()
.not()
.typeEqualTo(AddressType.frostMS),
)
.sortByDerivationIndex()
.idProperty()
.findAll();
@ -114,6 +127,19 @@ class _WalletAddressesViewState extends ConsumerState<WalletAddressesView> {
.and()
.not()
.typeEqualTo(AddressType.nonWallet)
.and()
.group(
(q) => q
.group(
(q2) => q2
.typeEqualTo(AddressType.frostMS)
.and()
.zSafeFrostEqualTo(true),
)
.or()
.not()
.typeEqualTo(AddressType.frostMS),
)
.sortByDerivationIndex()
.idProperty()
.findAll();

View file

@ -97,7 +97,7 @@ class _GenerateUriQrCodeViewState extends State<GenerateUriQrCodeView> {
initialDirectory: dir.path,
);
if (path != null) {
if (path != null && mounted) {
final file = File(path);
if (file.existsSync()) {
unawaited(
@ -109,13 +109,15 @@ class _GenerateUriQrCodeViewState extends State<GenerateUriQrCodeView> {
);
} else {
await file.writeAsBytes(pngBytes);
unawaited(
showFloatingFlushBar(
type: FlushBarType.success,
message: "$path saved!",
context: context,
),
);
if (mounted) {
unawaited(
showFloatingFlushBar(
type: FlushBarType.success,
message: "$path saved!",
context: context,
),
);
}
}
}
} else {
@ -144,7 +146,18 @@ class _GenerateUriQrCodeViewState extends State<GenerateUriQrCodeView> {
final amountString = amountController.text;
final noteString = noteController.text;
if (amountString.isNotEmpty && Decimal.tryParse(amountString) == null) {
// try "."
Decimal? amount = Decimal.tryParse(amountString);
if (amount == null) {
// try single instance of ","
final first = amountString.indexOf(",");
final last = amountString.lastIndexOf(",");
if (first == last) {
amount = Decimal.tryParse(amountString.replaceFirst(",", "."));
}
}
if (amountString.isNotEmpty && amount == null) {
showFloatingFlushBar(
type: FlushBarType.warning,
message: "Invalid amount",
@ -156,7 +169,7 @@ class _GenerateUriQrCodeViewState extends State<GenerateUriQrCodeView> {
final Map<String, String> queryParams = {};
if (amountString.isNotEmpty) {
queryParams["amount"] = amountString;
queryParams["amount"] = amount.toString();
}
if (noteString.isNotEmpty) {
queryParams["message"] = noteString;
@ -300,7 +313,7 @@ class _GenerateUriQrCodeViewState extends State<GenerateUriQrCodeView> {
FocusScope.of(context).unfocus();
await Future<void>.delayed(const Duration(milliseconds: 70));
}
if (mounted) {
if (context.mounted) {
Navigator.of(context).pop();
}
},

View file

@ -18,6 +18,7 @@ import 'package:flutter_svg/flutter_svg.dart';
import 'package:isar/isar.dart';
import '../../models/isar/models/isar_models.dart';
import '../../models/keys/view_only_wallet_data.dart';
import '../../notifications/show_flush_bar.dart';
import '../../providers/db/main_db_provider.dart';
import '../../providers/providers.dart';
@ -36,6 +37,7 @@ import '../../wallets/wallet/intermediate/bip39_hd_wallet.dart';
import '../../wallets/wallet/wallet_mixin_interfaces/bcash_interface.dart';
import '../../wallets/wallet/wallet_mixin_interfaces/multi_address_interface.dart';
import '../../wallets/wallet/wallet_mixin_interfaces/spark_interface.dart';
import '../../wallets/wallet/wallet_mixin_interfaces/view_only_option_interface.dart';
import '../../widgets/background.dart';
import '../../widgets/conditional_parent.dart';
import '../../widgets/custom_buttons/app_bar_icon_button.dart';
@ -113,9 +115,15 @@ class _ReceiveViewState extends ConsumerState<ReceiveView> {
address = await wallet.generateNextReceivingAddress(
derivePathType: type,
);
await ref.read(mainDBProvider).isar.writeTxn(() async {
await ref.read(mainDBProvider).isar.addresses.put(address!);
final isar = ref.read(mainDBProvider).isar;
await isar.writeTxn(() async {
await isar.addresses.put(address!);
});
final info = ref.read(pWalletInfo(walletId));
await info.updateReceivingAddress(
newAddress: address.value,
isar: isar,
);
} else {
await wallet.generateNewReceivingAddress();
address = null;
@ -183,10 +191,15 @@ class _ReceiveViewState extends ConsumerState<ReceiveView> {
clipboard = widget.clipboard;
final wallet = ref.read(pWallets).getWallet(walletId);
_supportsSpark = wallet is SparkInterface;
_showMultiType = _supportsSpark ||
(wallet is! BCashInterface &&
wallet is Bip39HDWallet &&
wallet.supportedAddressTypes.length > 1);
if (wallet is ViewOnlyOptionInterface && wallet.isViewOnly) {
_showMultiType = false;
} else {
_showMultiType = _supportsSpark ||
(wallet is! BCashInterface &&
wallet is Bip39HDWallet &&
wallet.supportedAddressTypes.length > 1);
}
_walletAddressTypes.add(wallet.info.mainAddressType);
@ -259,6 +272,18 @@ class _ReceiveViewState extends ConsumerState<ReceiveView> {
address = ref.watch(pWalletReceivingAddress(walletId));
}
final wallet =
ref.watch(pWallets.select((value) => value.getWallet(walletId)));
final bool canGen;
if (wallet is ViewOnlyOptionInterface &&
wallet.isViewOnly &&
wallet.viewOnlyType == ViewOnlyWalletType.addressOnly) {
canGen = false;
} else {
canGen = (wallet is MultiAddressInterface || _supportsSpark);
}
return Background(
child: Scaffold(
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
@ -547,17 +572,11 @@ class _ReceiveViewState extends ConsumerState<ReceiveView> {
);
},
),
if (ref.watch(
pWallets.select((value) => value.getWallet(walletId)),
) is MultiAddressInterface ||
_supportsSpark)
if (canGen)
const SizedBox(
height: 12,
),
if (ref.watch(
pWallets.select((value) => value.getWallet(walletId)),
) is MultiAddressInterface ||
_supportsSpark)
if (canGen)
SecondaryButton(
label: "Generate new address",
onPressed: _supportsSpark &&

View file

@ -120,6 +120,69 @@ class _RecipientState extends ConsumerState<Recipient> {
}
}
void _onQrTapped() async {
try {
if (FocusScope.of(context).hasFocus) {
FocusScope.of(context).unfocus();
await Future<void>.delayed(
const Duration(
milliseconds: 75,
),
);
}
final qrResult = await ref.read(pBarcodeScanner).scan();
Logging.instance.log(
"qrResult content: ${qrResult.rawContent}",
level: LogLevel.Info,
);
final paymentData = AddressUtils.parsePaymentUri(
qrResult.rawContent,
logging: Logging.instance,
);
Logging.instance.log(
"qrResult parsed: $paymentData",
level: LogLevel.Info,
);
if (paymentData != null &&
paymentData.coin?.uriScheme == widget.coin.uriScheme) {
// auto fill address
addressController.text = paymentData.address.trim();
// autofill amount field
if (paymentData.amount != null) {
final Amount amount = Decimal.parse(paymentData.amount!).toAmount(
fractionDigits: widget.coin.fractionDigits,
);
amountController.text =
ref.read(pAmountFormatter(widget.coin)).format(
amount,
withUnitName: false,
);
}
} else {
addressController.text = qrResult.rawContent.trim();
}
setState(() {
_addressIsEmpty = addressController.text.isEmpty;
});
_updateRecipientData();
} on PlatformException catch (e, s) {
Logging.instance.log(
"Failed to get camera permissions while "
"trying to scan qr code in SendView: $e\n$s",
level: LogLevel.Warning,
);
}
}
@override
void initState() {
addressController = TextEditingController();
@ -289,76 +352,7 @@ class _RecipientState extends ConsumerState<Recipient> {
key: const Key(
"sendViewScanQrButtonKey",
),
onTap: () async {
try {
if (FocusScope.of(context).hasFocus) {
FocusScope.of(context).unfocus();
await Future<void>.delayed(
const Duration(
milliseconds: 75,
),
);
}
final qrResult =
await ref.read(pBarcodeScanner).scan();
Logging.instance.log(
"qrResult content: ${qrResult.rawContent}",
level: LogLevel.Info,
);
/// TODO: deal with address utils
final results =
AddressUtils.parseUri(qrResult.rawContent);
Logging.instance.log(
"qrResult parsed: $results",
level: LogLevel.Info,
);
if (results.isNotEmpty &&
results["scheme"] ==
widget.coin.uriScheme) {
// auto fill address
addressController.text =
(results["address"] ?? "").trim();
// autofill amount field
if (results["amount"] != null) {
final Amount amount =
Decimal.parse(results["amount"]!)
.toAmount(
fractionDigits:
widget.coin.fractionDigits,
);
amountController.text = ref
.read(pAmountFormatter(widget.coin))
.format(
amount,
withUnitName: false,
);
}
} else {
addressController.text =
qrResult.rawContent.trim();
}
setState(() {
_addressIsEmpty =
addressController.text.isEmpty;
});
_updateRecipientData();
} on PlatformException catch (e, s) {
Logging.instance.log(
"Failed to get camera permissions while "
"trying to scan qr code in SendView: $e\n$s",
level: LogLevel.Warning,
);
}
},
onTap: _onQrTapped,
child: const QrCodeIcon(),
),
],

View file

@ -67,6 +67,7 @@ class _FrostSendStep1bState extends ConsumerState<FrostSendStep1b> {
final data = Frost.extractDataFromSignConfig(
signConfig: config,
coin: wallet.cryptoCurrency,
serializedKeys: (await wallet.getSerializedKeys())!,
);
final utxos = await ref

View file

@ -11,7 +11,7 @@
import 'dart:async';
import 'dart:io';
import 'package:cw_core/monero_transaction_priority.dart';
import 'package:cs_monero/cs_monero.dart' as lib_monero;
import 'package:decimal/decimal.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@ -163,9 +163,13 @@ class _SendViewState extends ConsumerState<SendView> {
level: LogLevel.Info,
);
final paymentData = AddressUtils.parsePaymentUri(qrResult.rawContent);
final paymentData = AddressUtils.parsePaymentUri(
qrResult.rawContent,
logging: Logging.instance,
);
if (paymentData.coin.uriScheme == coin.uriScheme) {
if (paymentData != null &&
paymentData.coin?.uriScheme == coin.uriScheme) {
// auto fill address
_address = paymentData.address.trim();
sendToController.text = _address!;
@ -195,12 +199,8 @@ class _SendViewState extends ConsumerState<SendView> {
});
// now check for non standard encoded basic address
} else if (ref
.read(pWallets)
.getWallet(walletId)
.cryptoCurrency
.validateAddress(qrResult.rawContent)) {
_address = qrResult.rawContent.trim();
} else {
_address = qrResult.rawContent.split("\n").first.trim();
sendToController.text = _address ?? "";
_setValidAddressProviders(_address);
@ -470,22 +470,22 @@ class _SendViewState extends ConsumerState<SendView> {
Amount fee;
if (coin is Monero) {
MoneroTransactionPriority specialMoneroId;
lib_monero.TransactionPriority specialMoneroId;
switch (ref.read(feeRateTypeStateProvider.state).state) {
case FeeRateType.fast:
specialMoneroId = MoneroTransactionPriority.fast;
specialMoneroId = lib_monero.TransactionPriority.high;
break;
case FeeRateType.average:
specialMoneroId = MoneroTransactionPriority.regular;
specialMoneroId = lib_monero.TransactionPriority.medium;
break;
case FeeRateType.slow:
specialMoneroId = MoneroTransactionPriority.slow;
specialMoneroId = lib_monero.TransactionPriority.normal;
break;
default:
throw ArgumentError("custom fee not available for monero");
}
fee = await wallet.estimateFeeFor(amount, specialMoneroId.raw!);
fee = await wallet.estimateFeeFor(amount, specialMoneroId.value);
cachedFees[amount] = ref.read(pAmountFormatter(coin)).format(
fee,
withUnitName: true,

View file

@ -8,9 +8,10 @@
*
*/
import 'package:cw_core/monero_transaction_priority.dart';
import 'package:flutter/material.dart';
import 'package:cs_monero/cs_monero.dart' as lib_monero;
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../models/paymint/fee_object_model.dart';
import '../../../providers/providers.dart';
import '../../../providers/ui/fee_rate_type_state_provider.dart';
@ -90,7 +91,7 @@ class _TransactionFeeSelectionSheetState
if (coin is Monero || coin is Wownero) {
final fee = await wallet.estimateFeeFor(
amount,
MoneroTransactionPriority.fast.raw!,
lib_monero.TransactionPriority.high.value,
);
ref.read(feeSheetSessionCacheProvider).fast[amount] = fee;
} else if (coin is Firo) {
@ -127,7 +128,7 @@ class _TransactionFeeSelectionSheetState
if (coin is Monero || coin is Wownero) {
final fee = await wallet.estimateFeeFor(
amount,
MoneroTransactionPriority.regular.raw!,
lib_monero.TransactionPriority.medium.value,
);
ref.read(feeSheetSessionCacheProvider).average[amount] = fee;
} else if (coin is Firo) {
@ -163,7 +164,7 @@ class _TransactionFeeSelectionSheetState
if (coin is Monero || coin is Wownero) {
final fee = await wallet.estimateFeeFor(
amount,
MoneroTransactionPriority.slow.raw!,
lib_monero.TransactionPriority.normal.value,
);
ref.read(feeSheetSessionCacheProvider).slow[amount] = fee;
} else if (coin is Firo) {

View file

@ -163,25 +163,30 @@ class _TokenSendViewState extends ConsumerState<TokenSendView> {
level: LogLevel.Info,
);
final results = AddressUtils.parseUri(qrResult.rawContent);
final paymentData = AddressUtils.parsePaymentUri(
qrResult.rawContent,
logging: Logging.instance,
);
Logging.instance.log("qrResult parsed: $results", level: LogLevel.Info);
Logging.instance
.log("qrResult parsed: $paymentData", level: LogLevel.Info);
if (results.isNotEmpty && results["scheme"] == coin.uriScheme) {
if (paymentData != null &&
paymentData.coin?.uriScheme == coin.uriScheme) {
// auto fill address
_address = (results["address"] ?? "").trim();
_address = paymentData.address.trim();
sendToController.text = _address!;
// autofill notes field
if (results["message"] != null) {
noteController.text = results["message"]!;
} else if (results["label"] != null) {
noteController.text = results["label"]!;
if (paymentData.message != null) {
noteController.text = paymentData.message!;
} else if (paymentData.label != null) {
noteController.text = paymentData.label!;
}
// autofill amount field
if (results["amount"] != null) {
final Amount amount = Decimal.parse(results["amount"]!).toAmount(
if (paymentData.amount != null) {
final Amount amount = Decimal.parse(paymentData.amount!).toAmount(
fractionDigits: tokenContract.decimals,
);
cryptoAmountController.text = ref.read(pAmountFormatter(coin)).format(
@ -198,12 +203,8 @@ class _TokenSendViewState extends ConsumerState<TokenSendView> {
});
// now check for non standard encoded basic address
} else if (ref
.read(pWallets)
.getWallet(walletId)
.cryptoCurrency
.validateAddress(qrResult.rawContent)) {
_address = qrResult.rawContent.trim();
} else {
_address = qrResult.rawContent.split("\n").first.trim();
sendToController.text = _address ?? "";
_updatePreviewButtonState(_address, _amountToSend);

View file

@ -272,48 +272,48 @@ class AboutView extends ConsumerWidget {
const SizedBox(
height: 12,
),
if (AppConfig.coins.whereType<Monero>().isNotEmpty)
FutureBuilder(
future: GitStatus.getMoneroCommitStatus(),
builder: (
context,
AsyncSnapshot<CommitStatus> snapshot,
) {
CommitStatus stateOfCommit =
CommitStatus.notLoaded;
if (snapshot.connectionState ==
ConnectionState.done &&
snapshot.hasData) {
stateOfCommit = snapshot.data!;
}
return RoundedWhiteContainer(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.stretch,
children: [
Text(
"Monero Build Commit",
style: STextStyles.titleBold12(context),
),
const SizedBox(
height: 4,
),
SelectableText(
GitStatus.moneroCommit,
style: GitStatus.styleForStatus(
stateOfCommit,
context,
),
),
],
),
);
},
),
const SizedBox(
height: 12,
),
// if (AppConfig.coins.whereType<Monero>().isNotEmpty)
// FutureBuilder(
// future: GitStatus.getMoneroCommitStatus(),
// builder: (
// context,
// AsyncSnapshot<CommitStatus> snapshot,
// ) {
// CommitStatus stateOfCommit =
// CommitStatus.notLoaded;
//
// if (snapshot.connectionState ==
// ConnectionState.done &&
// snapshot.hasData) {
// stateOfCommit = snapshot.data!;
// }
// return RoundedWhiteContainer(
// child: Column(
// crossAxisAlignment:
// CrossAxisAlignment.stretch,
// children: [
// Text(
// "Monero Build Commit",
// style: STextStyles.titleBold12(context),
// ),
// const SizedBox(
// height: 4,
// ),
// SelectableText(
// GitStatus.moneroCommit,
// style: GitStatus.styleForStatus(
// stateOfCommit,
// context,
// ),
// ),
// ],
// ),
// );
// },
// ),
// const SizedBox(
// height: 12,
// ),
RoundedWhiteContainer(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,

View file

@ -18,7 +18,7 @@ import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_libepiccash/git_versions.dart' as EPIC_VERSIONS;
import 'package:flutter_libmonero/git_versions.dart' as MONERO_VERSIONS;
// import 'package:flutter_libmonero/git_versions.dart' as MONERO_VERSIONS;
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
import 'package:lelantus/git_versions.dart' as FIRO_VERSIONS;
@ -313,8 +313,8 @@ class _DebugViewState extends ConsumerState<DebugView> {
FIRO_VERSIONS.getPluginVersion();
final String epicCashCommit =
EPIC_VERSIONS.getPluginVersion();
final String moneroCommit =
MONERO_VERSIONS.getPluginVersion();
// final String moneroCommit =
// MONERO_VERSIONS.getPluginVersion();
final DeviceInfoPlugin deviceInfoPlugin =
DeviceInfoPlugin();
final deviceInfo =
@ -347,7 +347,7 @@ class _DebugViewState extends ConsumerState<DebugView> {
"appName": appName,
"firoCommit": firoCommit,
"epicCashCommit": epicCashCommit,
"moneroCommit": moneroCommit,
// "moneroCommit": moneroCommit,
"deviceInfoMap": deviceInfoMap,
"errorLogs": errorLogs,
};

View file

@ -3,9 +3,9 @@ import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
import 'edit_coin_units_view.dart';
import '../../../../../providers/global/prefs_provider.dart';
import '../../../../../app_config.dart';
import '../../../../../providers/global/prefs_provider.dart';
import '../../../../../themes/coin_icon_provider.dart';
import '../../../../../themes/stack_colors.dart';
import '../../../../../utilities/assets.dart';
@ -18,6 +18,7 @@ import '../../../../../widgets/custom_buttons/app_bar_icon_button.dart';
import '../../../../../widgets/desktop/desktop_dialog.dart';
import '../../../../../widgets/desktop/desktop_dialog_close_button.dart';
import '../../../../../widgets/rounded_white_container.dart';
import 'edit_coin_units_view.dart';
class ManageCoinUnitsView extends ConsumerWidget {
const ManageCoinUnitsView({super.key});
@ -44,13 +45,9 @@ class ManageCoinUnitsView extends ConsumerWidget {
prefsChangeNotifierProvider.select((value) => value.showTestNetCoins),
);
final _coins = AppConfig.coins
.where((e) => e is! Firo && e.network != CryptoCurrencyNetwork.test)
.toList();
final coins = showTestNet
? _coins
: _coins.where((e) => e.network != CryptoCurrencyNetwork.test).toList();
? AppConfig.coins
: AppConfig.coins.where((e) => !e.network.isTestNet).toList();
return ConditionalParent(
condition: Util.isDesktop,

View file

@ -29,7 +29,7 @@ import '../../../../utilities/text_styles.dart';
import '../../../../utilities/util.dart';
import '../../../../wallets/crypto_currency/crypto_currency.dart';
import '../../../../wallets/crypto_currency/intermediate/cryptonote_currency.dart';
import '../../../../wallets/wallet/wallet_mixin_interfaces/cw_based_interface.dart';
import '../../../../wallets/wallet/intermediate/lib_monero_wallet.dart';
import '../../../../widgets/background.dart';
import '../../../../widgets/conditional_parent.dart';
import '../../../../widgets/custom_buttons/app_bar_icon_button.dart';
@ -222,7 +222,7 @@ class _AddEditNodeViewState extends ConsumerState<AddEditNodeView> {
// strip unused path
String address = formData.host!;
if (coin is CwBasedInterface) {
if (coin is LibMoneroWallet) {
if (address.startsWith("http")) {
final uri = Uri.parse(address);
address = "${uri.scheme}://${uri.host}";
@ -837,7 +837,7 @@ class _NodeFormState extends ConsumerState<NodeForm> {
} else {
enableSSLCheckbox = true;
}
} else if (widget.coin is CwBasedInterface) {
} else if (widget.coin is LibMoneroWallet) {
if (newValue.startsWith("https://")) {
_useSSL = true;
} else if (newValue.startsWith("http://")) {
@ -1052,7 +1052,7 @@ class _NodeFormState extends ConsumerState<NodeForm> {
),
],
),
if (widget.coin is CwBasedInterface)
if (widget.coin is LibMoneroWallet)
Row(
children: [
GestureDetector(

View file

@ -62,7 +62,7 @@ class _ManageNodesViewState extends ConsumerState<ManageNodesView> {
final coins = showTestNet
? _coins
: _coins.where((e) => e.network != CryptoCurrencyNetwork.test).toList();
: _coins.where((e) => !e.network.isTestNet).toList();
return Background(
child: Scaffold(

View file

@ -18,7 +18,7 @@ import 'package:isar/isar.dart';
import 'package:stack_wallet_backup/stack_wallet_backup.dart';
import 'package:tuple/tuple.dart';
import 'package:uuid/uuid.dart';
import 'package:wakelock/wakelock.dart';
import 'package:wakelock_plus/wakelock_plus.dart';
import '../../../../../app_config.dart';
import '../../../../../db/hive/db.dart';
@ -27,6 +27,7 @@ import '../../../../../models/exchange/change_now/exchange_transaction.dart';
import '../../../../../models/exchange/response_objects/trade.dart';
import '../../../../../models/isar/models/contact_entry.dart';
import '../../../../../models/isar/models/transaction_note.dart';
import '../../../../../models/keys/view_only_wallet_data.dart';
import '../../../../../models/node_model.dart';
import '../../../../../models/stack_restoring_ui_state.dart';
import '../../../../../models/trade_wallet_lookup.dart';
@ -54,10 +55,11 @@ import '../../../../../wallets/wallet/impl/bitcoin_frost_wallet.dart';
import '../../../../../wallets/wallet/impl/epiccash_wallet.dart';
import '../../../../../wallets/wallet/impl/monero_wallet.dart';
import '../../../../../wallets/wallet/impl/wownero_wallet.dart';
import '../../../../../wallets/wallet/intermediate/lib_monero_wallet.dart';
import '../../../../../wallets/wallet/wallet.dart';
import '../../../../../wallets/wallet/wallet_mixin_interfaces/cw_based_interface.dart';
import '../../../../../wallets/wallet/wallet_mixin_interfaces/mnemonic_interface.dart';
import '../../../../../wallets/wallet/wallet_mixin_interfaces/private_key_interface.dart';
import '../../../../../wallets/wallet/wallet_mixin_interfaces/view_only_option_interface.dart';
class PreRestoreState {
final Set<String> walletIds;
@ -312,7 +314,10 @@ abstract class SWB {
backupWallet['isFavorite'] = wallet.info.isFavourite;
backupWallet['otherDataJsonString'] = wallet.info.otherDataJsonString;
if (wallet is MnemonicInterface) {
if (wallet is ViewOnlyOptionInterface && wallet.isViewOnly) {
backupWallet['viewOnlyWalletDataKey'] =
(await wallet.getViewOnlyWalletData()).toJsonEncodedString();
} else if (wallet is MnemonicInterface) {
backupWallet['mnemonic'] = await wallet.getMnemonic();
backupWallet['mnemonicPassphrase'] =
await wallet.getMnemonicPassphrase();
@ -419,7 +424,16 @@ abstract class SWB {
String? mnemonic, mnemonicPassphrase, privateKey;
if (walletbackup['mnemonic'] == null) {
ViewOnlyWalletData? viewOnlyData;
if (info.isViewOnly) {
final viewOnlyDataEncoded =
walletbackup['viewOnlyWalletDataKey'] as String;
viewOnlyData = ViewOnlyWalletData.fromJsonEncodedString(
viewOnlyDataEncoded,
walletId: info.walletId,
);
} else if (walletbackup['mnemonic'] == null) {
// probably private key based
if (walletbackup['privateKey'] != null) {
privateKey = walletbackup['privateKey'] as String;
@ -486,6 +500,7 @@ abstract class SWB {
mnemonic: mnemonic,
mnemonicPassphrase: mnemonicPassphrase,
privateKey: privateKey,
viewOnlyData: viewOnlyData,
);
if (wallet is MoneroWallet /*|| wallet is WowneroWallet doesn't work.*/) {
@ -503,7 +518,7 @@ abstract class SWB {
Future<void>? restoringFuture;
if (!(wallet is CwBasedInterface || wallet is EpiccashWallet)) {
if (!(wallet is LibMoneroWallet || wallet is EpiccashWallet)) {
if (wallet is BitcoinFrostWallet) {
restoringFuture = wallet.recover(
isRescan: false,
@ -700,7 +715,7 @@ abstract class SWB {
StackRestoringUIState? uiState,
SecureStorageInterface secureStorageInterface,
) async {
if (!Platform.isLinux) await Wakelock.enable();
if (!Platform.isLinux) await WakelockPlus.enable();
Logging.instance.log(
"SWB creating temp backup",
@ -914,7 +929,7 @@ abstract class SWB {
await status;
}
if (!Platform.isLinux) await Wakelock.disable();
if (!Platform.isLinux) await WakelockPlus.disable();
// check if cancel was requested and restore previous state
if (_checkShouldCancel(
preRestoreState,

View file

@ -0,0 +1,86 @@
import 'package:flutter/material.dart';
import '../../../models/keys/view_only_wallet_data.dart';
import '../../../utilities/util.dart';
import '../../../widgets/custom_buttons/simple_copy_button.dart';
import '../../../widgets/detail_item.dart';
import '../../wallet_view/transaction_views/transaction_details_view.dart';
class ViewOnlyWalletDataWidget extends StatelessWidget {
const ViewOnlyWalletDataWidget({
super.key,
required this.data,
});
final ViewOnlyWalletData data;
@override
Widget build(BuildContext context) {
return switch (data) {
final CryptonoteViewOnlyWalletData e => Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
DetailItem(
title: "Address",
detail: e.address,
button: Util.isDesktop
? IconCopyButton(
data: e.address,
)
: SimpleCopyButton(
data: e.address,
),
),
const SizedBox(
height: 16,
),
DetailItem(
title: "Private view key",
detail: e.privateViewKey,
button: Util.isDesktop
? IconCopyButton(
data: e.privateViewKey,
)
: SimpleCopyButton(
data: e.privateViewKey,
),
),
],
),
final AddressViewOnlyWalletData e => Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
DetailItem(
title: "Address",
detail: e.address,
button: Util.isDesktop
? IconCopyButton(
data: e.address,
)
: SimpleCopyButton(
data: e.address,
),
),
],
),
final ExtendedKeysViewOnlyWalletData e => Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
...e.xPubs.map(
(xPub) => DetailItem(
title: xPub.path,
detail: xPub.encoded,
button: Util.isDesktop
? IconCopyButton(
data: xPub.encoded,
)
: SimpleCopyButton(
data: xPub.encoded,
),
),
),
],
),
};
}
}

View file

@ -17,6 +17,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../../app_config.dart';
import '../../../../models/keys/cw_key_data.dart';
import '../../../../models/keys/key_data_interface.dart';
import '../../../../models/keys/view_only_wallet_data.dart';
import '../../../../models/keys/xpriv_data.dart';
import '../../../../notifications/show_flush_bar.dart';
import '../../../../themes/stack_colors.dart';
@ -39,6 +40,7 @@ import '../../../../widgets/rounded_white_container.dart';
import '../../../../widgets/stack_dialog.dart';
import '../../../add_wallet_views/new_wallet_recovery_phrase_view/sub_widgets/mnemonic_table.dart';
import '../../../wallet_view/transaction_views/transaction_details_view.dart';
import '../../sub_widgets/view_only_wallet_data_widget.dart';
import 'cn_wallet_keys.dart';
import 'wallet_xprivs.dart';
@ -87,9 +89,10 @@ class WalletBackupView extends ConsumerWidget {
Padding(
padding: const EdgeInsets.all(10),
child: CustomTextButton(
text: switch (keyData.runtimeType) {
const (XPrivData) => "xpriv(s)",
const (CWKeyData) => "keys",
text: switch (keyData) {
final XPrivData _ => "xpriv(s)",
final CWKeyData _ => "keys",
final ViewOnlyWalletData _ => "keys",
_ => throw UnimplementedError(
"Don't forget to add your KeyDataInterface here! ${keyData.runtimeType}",
),
@ -426,7 +429,7 @@ class _FrostKeys extends StatelessWidget {
}
}
class MobileKeyDataView extends StatelessWidget {
class MobileKeyDataView extends ConsumerWidget {
const MobileKeyDataView({
super.key,
required this.walletId,
@ -441,7 +444,7 @@ class MobileKeyDataView extends StatelessWidget {
final KeyDataInterface keyData;
@override
Widget build(BuildContext context) {
Widget build(BuildContext context, WidgetRef ref) {
return Background(
child: Scaffold(
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
@ -452,9 +455,10 @@ class MobileKeyDataView extends StatelessWidget {
},
),
title: Text(
"Wallet ${switch (keyData.runtimeType) {
const (XPrivData) => "xpriv(s)",
const (CWKeyData) => "keys",
"Wallet ${switch (keyData) {
final XPrivData _ => "xpriv(s)",
final CWKeyData _ => "keys",
final ViewOnlyWalletData _ => "keys",
_ => throw UnimplementedError(
"Don't forget to add your KeyDataInterface here!",
),
@ -474,14 +478,18 @@ class MobileKeyDataView extends StatelessWidget {
mainAxisSize: MainAxisSize.min,
children: [
Expanded(
child: switch (keyData.runtimeType) {
const (XPrivData) => WalletXPrivs(
child: switch (keyData) {
final XPrivData e => WalletXPrivs(
walletId: walletId,
xprivData: keyData as XPrivData,
xprivData: e,
),
const (CWKeyData) => CNWalletKeys(
final CWKeyData e => CNWalletKeys(
walletId: walletId,
cwKeyData: keyData as CWKeyData,
cwKeyData: e,
),
final ViewOnlyWalletData e =>
ViewOnlyWalletDataWidget(
data: e,
),
_ => throw UnimplementedError(
"Don't forget to add your KeyDataInterface here!",

View file

@ -49,7 +49,7 @@ class WalletXPrivsState extends ConsumerState<WalletXPrivs> {
late String _currentDropDownValue;
String _current(String key) =>
widget.xprivData.xprivs.firstWhere((e) => e.path == key).xpriv;
widget.xprivData.xprivs.firstWhere((e) => e.path == key).encoded;
Future<void> _copy() async {
await widget.clipboardInterface.setData(

View file

@ -16,7 +16,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
import 'package:tuple/tuple.dart';
import 'package:wakelock/wakelock.dart';
import 'package:wakelock_plus/wakelock_plus.dart';
import '../../../../providers/providers.dart';
import '../../../../route_generator.dart';
@ -125,7 +125,7 @@ class _WalletNetworkSettingsViewState
}
Future<void> _attemptRescan() async {
if (!Platform.isLinux) await Wakelock.enable();
if (!Platform.isLinux) await WakelockPlus.enable();
try {
if (mounted) {
@ -180,7 +180,7 @@ class _WalletNetworkSettingsViewState
);
}
} catch (e) {
if (!Platform.isLinux) await Wakelock.disable();
if (!Platform.isLinux) await WakelockPlus.disable();
if (mounted) {
// pop rescanning dialog
@ -212,7 +212,7 @@ class _WalletNetworkSettingsViewState
}
}
} finally {
if (!Platform.isLinux) await Wakelock.disable();
if (!Platform.isLinux) await WakelockPlus.disable();
}
}

View file

@ -19,6 +19,7 @@ import '../../../db/hive/db.dart';
import '../../../db/sqlite/firo_cache.dart';
import '../../../models/epicbox_config_model.dart';
import '../../../models/keys/key_data_interface.dart';
import '../../../models/keys/view_only_wallet_data.dart';
import '../../../notifications/show_flush_bar.dart';
import '../../../providers/global/wallets_provider.dart';
import '../../../providers/ui/transaction_filter_provider.dart';
@ -36,9 +37,10 @@ import '../../../wallets/crypto_currency/intermediate/frost_currency.dart';
import '../../../wallets/crypto_currency/intermediate/nano_currency.dart';
import '../../../wallets/wallet/impl/bitcoin_frost_wallet.dart';
import '../../../wallets/wallet/impl/epiccash_wallet.dart';
import '../../../wallets/wallet/wallet_mixin_interfaces/cw_based_interface.dart';
import '../../../wallets/wallet/intermediate/lib_monero_wallet.dart';
import '../../../wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.dart';
import '../../../wallets/wallet/wallet_mixin_interfaces/mnemonic_interface.dart';
import '../../../wallets/wallet/wallet_mixin_interfaces/view_only_option_interface.dart';
import '../../../widgets/background.dart';
import '../../../widgets/custom_buttons/app_bar_icon_button.dart';
import '../../../widgets/desktop/secondary_button.dart';
@ -98,8 +100,13 @@ class _WalletSettingsViewState extends ConsumerState<WalletSettingsView> {
void initState() {
walletId = widget.walletId;
coin = widget.coin;
xPubEnabled =
ref.read(pWallets).getWallet(walletId) is ExtendedKeysInterface;
final wallet = ref.read(pWallets).getWallet(walletId);
if (wallet is ViewOnlyOptionInterface && wallet.isViewOnly) {
xPubEnabled = false;
} else {
xPubEnabled = wallet is ExtendedKeysInterface;
}
xpub = "";
@ -165,6 +172,15 @@ class _WalletSettingsViewState extends ConsumerState<WalletSettingsView> {
@override
Widget build(BuildContext context) {
debugPrint("BUILD: $runtimeType");
final wallet = ref.read(pWallets).getWallet(widget.walletId);
bool canBackup = true;
if (wallet is ViewOnlyOptionInterface &&
wallet.isViewOnly &&
wallet.viewOnlyType == ViewOnlyWalletType.addressOnly) {
canBackup = false;
}
return Background(
child: Scaffold(
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
@ -247,106 +263,155 @@ class _WalletSettingsViewState extends ConsumerState<WalletSettingsView> {
);
},
),
const SizedBox(
height: 8,
),
Consumer(
builder: (_, ref, __) {
return SettingsListButton(
iconAssetName: Assets.svg.lock,
iconSize: 16,
title: "Wallet backup",
onPressed: () async {
final wallet = ref
.read(pWallets)
.getWallet(widget.walletId);
if (canBackup)
const SizedBox(
height: 8,
),
if (canBackup)
Consumer(
builder: (_, ref, __) {
return SettingsListButton(
iconAssetName: Assets.svg.lock,
iconSize: 16,
title: "Wallet backup",
onPressed: () async {
// TODO: [prio=med] take wallets that don't have a mnemonic into account
// TODO: [prio=med] take wallets that don't have a mnemonic into account
List<String>? mnemonic;
({
String myName,
String config,
String keys,
List<String>? mnemonic;
({
String myName,
String config,
String keys
})? prevGen,
})? frostWalletData;
if (wallet is BitcoinFrostWallet) {
final futures = [
wallet.getSerializedKeys(),
wallet.getMultisigConfig(),
wallet.getSerializedKeysPrevGen(),
wallet.getMultisigConfigPrevGen(),
];
String keys,
({
String config,
String keys
})? prevGen,
})? frostWalletData;
if (wallet is BitcoinFrostWallet) {
final futures = [
wallet.getSerializedKeys(),
wallet.getMultisigConfig(),
wallet.getSerializedKeysPrevGen(),
wallet.getMultisigConfigPrevGen(),
];
final results =
await Future.wait(futures);
final results =
await Future.wait(futures);
if (results.length == 4) {
frostWalletData = (
myName: wallet.frostInfo.myName,
config: results[1]!,
keys: results[0]!,
prevGen: results[2] == null ||
results[3] == null
? null
: (
config: results[3]!,
keys: results[2]!,
),
);
if (results.length == 4) {
frostWalletData = (
myName: wallet.frostInfo.myName,
config: results[1]!,
keys: results[0]!,
prevGen: results[2] == null ||
results[3] == null
? null
: (
config: results[3]!,
keys: results[2]!,
),
);
}
} else {
if (wallet is MnemonicInterface) {
if (wallet
is ViewOnlyOptionInterface &&
!(wallet
as ViewOnlyOptionInterface)
.isViewOnly) {
mnemonic = await wallet
.getMnemonicAsWords();
}
}
}
} else if (wallet
is MnemonicInterface) {
mnemonic =
await wallet.getMnemonicAsWords();
}
KeyDataInterface? keyData;
if (wallet is ExtendedKeysInterface) {
keyData = await wallet.getXPrivs();
} else if (wallet is CwBasedInterface) {
keyData = await wallet.getKeys();
}
KeyDataInterface? keyData;
if (wallet
is ViewOnlyOptionInterface &&
wallet.isViewOnly) {
keyData = await wallet
.getViewOnlyWalletData();
} else if (wallet
is ExtendedKeysInterface) {
keyData = await wallet.getXPrivs();
} else if (wallet
is LibMoneroWallet) {
keyData = await wallet.getKeys();
}
if (context.mounted) {
await Navigator.push(
context,
RouteGenerator.getRoute(
shouldUseMaterialRoute:
RouteGenerator
.useMaterialPageRoute,
builder: (_) => LockscreenView(
routeOnSuccessArguments: (
walletId: walletId,
mnemonic: mnemonic ?? [],
frostWalletData:
frostWalletData,
keyData: keyData,
if (context.mounted) {
if (keyData != null &&
wallet
is ViewOnlyOptionInterface &&
wallet.isViewOnly) {
await Navigator.push(
context,
RouteGenerator.getRoute(
shouldUseMaterialRoute:
RouteGenerator
.useMaterialPageRoute,
builder: (_) =>
LockscreenView(
routeOnSuccessArguments: (
walletId: walletId,
keyData: keyData,
),
showBackButton: true,
routeOnSuccess:
MobileKeyDataView
.routeName,
biometricsCancelButtonString:
"CANCEL",
biometricsLocalizedReason:
"Authenticate to view recovery data",
biometricsAuthenticationTitle:
"View recovery data",
),
settings: const RouteSettings(
name:
"/viewRecoveryDataLockscreen",
),
),
showBackButton: true,
routeOnSuccess:
WalletBackupView.routeName,
biometricsCancelButtonString:
"CANCEL",
biometricsLocalizedReason:
"Authenticate to view recovery phrase",
biometricsAuthenticationTitle:
"View recovery phrase",
),
settings: const RouteSettings(
name:
"/viewRecoverPhraseLockscreen",
),
),
);
}
},
);
},
),
);
} else {
await Navigator.push(
context,
RouteGenerator.getRoute(
shouldUseMaterialRoute:
RouteGenerator
.useMaterialPageRoute,
builder: (_) =>
LockscreenView(
routeOnSuccessArguments: (
walletId: walletId,
mnemonic: mnemonic ?? [],
frostWalletData:
frostWalletData,
keyData: keyData,
),
showBackButton: true,
routeOnSuccess:
WalletBackupView
.routeName,
biometricsCancelButtonString:
"CANCEL",
biometricsLocalizedReason:
"Authenticate to view recovery phrase",
biometricsAuthenticationTitle:
"View recovery phrase",
),
settings: const RouteSettings(
name:
"/viewRecoverPhraseLockscreen",
),
),
);
}
}
},
);
},
),
const SizedBox(
height: 8,
),

View file

@ -0,0 +1,177 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../../app_config.dart';
import '../../../../models/keys/view_only_wallet_data.dart';
import '../../../../pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/delete_wallet_keys_popup.dart';
import '../../../../providers/global/secure_store_provider.dart';
import '../../../../providers/global/wallets_provider.dart';
import '../../../../route_generator.dart';
import '../../../../themes/stack_colors.dart';
import '../../../../utilities/text_styles.dart';
import '../../../../utilities/util.dart';
import '../../../../wallets/isar/providers/wallet_info_provider.dart';
import '../../../../widgets/conditional_parent.dart';
import '../../../../widgets/custom_buttons/app_bar_icon_button.dart';
import '../../../../widgets/desktop/primary_button.dart';
import '../../../../widgets/rounded_white_container.dart';
import '../../../../widgets/stack_dialog.dart';
import '../../../home_view/home_view.dart';
import '../../sub_widgets/view_only_wallet_data_widget.dart';
class DeleteViewOnlyWalletKeysView extends ConsumerStatefulWidget {
const DeleteViewOnlyWalletKeysView({
super.key,
required this.walletId,
required this.data,
});
static const routeName = "/deleteWalletViewOnlyData";
final String walletId;
final ViewOnlyWalletData data;
@override
ConsumerState<DeleteViewOnlyWalletKeysView> createState() =>
_DeleteViewOnlyWalletKeysViewState();
}
class _DeleteViewOnlyWalletKeysViewState
extends ConsumerState<DeleteViewOnlyWalletKeysView> {
bool _lock = false;
void _continuePressed() async {
if (_lock) {
return;
}
_lock = true;
try {
if (Util.isDesktop) {
await Navigator.of(context).push(
RouteGenerator.getRoute(
builder: (context) {
return ConfirmDelete(
walletId: widget.walletId,
);
},
settings: const RouteSettings(
name: "/desktopConfirmDelete",
),
),
);
} else {
await showDialog<dynamic>(
barrierDismissible: true,
context: context,
builder: (_) => StackDialog(
title: "Thanks! Your wallet will be deleted.",
leftButton: TextButton(
style: Theme.of(context)
.extension<StackColors>()!
.getSecondaryEnabledButtonStyle(context),
onPressed: () {
Navigator.pop(context);
},
child: Text(
"Cancel",
style: STextStyles.button(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.accentColorDark,
),
),
),
rightButton: TextButton(
style: Theme.of(context)
.extension<StackColors>()!
.getPrimaryEnabledButtonStyle(context),
onPressed: () async {
await ref.read(pWallets).deleteWallet(
ref.read(pWalletInfo(widget.walletId)),
ref.read(secureStoreProvider),
);
if (mounted) {
Navigator.of(context).popUntil(
ModalRoute.withName(HomeView.routeName),
);
}
},
child: Text(
"Ok",
style: STextStyles.button(context),
),
),
),
);
}
} finally {
_lock = false;
}
}
@override
Widget build(BuildContext context) {
return ConditionalParent(
condition: !Util.isDesktop,
builder: (child) => Scaffold(
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () {
Navigator.of(context).pop();
},
),
),
body: SafeArea(
child: LayoutBuilder(
builder: (context, cons) {
return SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(minHeight: cons.maxHeight),
child: IntrinsicHeight(
child: Padding(
padding: const EdgeInsets.all(16),
child: child,
),
),
),
);
},
),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
RoundedWhiteContainer(
child: Text(
"Please write down your backup data. Keep it safe and "
"never share it with anyone. "
"Your backup data is the only way you can access your "
"wallet if you forget your PIN, lose your phone, etc."
"\n\n"
"${AppConfig.appName} does not keep nor is able to restore "
"your backup data. "
"Only you have access to your wallet.",
style: STextStyles.label(context),
),
),
const SizedBox(
height: 24,
),
ViewOnlyWalletDataWidget(
data: widget.data,
),
if (!Util.isDesktop) const Spacer(),
const SizedBox(
height: 16,
),
PrimaryButton(
label: "Continue",
onPressed: _continuePressed,
),
],
),
);
}
}

View file

@ -14,11 +14,9 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
import '../../../../app_config.dart';
import '../../../../notifications/show_flush_bar.dart';
import '../../../add_wallet_views/new_wallet_recovery_phrase_view/sub_widgets/mnemonic_table.dart';
import '../../../home_view/home_view.dart';
import '../../../wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart';
import '../../../../providers/global/secure_store_provider.dart';
import '../../../../providers/global/wallets_provider.dart';
import '../../../../themes/stack_colors.dart';
@ -35,6 +33,9 @@ import '../../../../widgets/desktop/primary_button.dart';
import '../../../../widgets/detail_item.dart';
import '../../../../widgets/rounded_white_container.dart';
import '../../../../widgets/stack_dialog.dart';
import '../../../add_wallet_views/new_wallet_recovery_phrase_view/sub_widgets/mnemonic_table.dart';
import '../../../home_view/home_view.dart';
import '../../../wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart';
class DeleteWalletRecoveryPhraseView extends ConsumerStatefulWidget {
const DeleteWalletRecoveryPhraseView({
@ -69,7 +70,6 @@ class _DeleteWalletRecoveryPhraseViewState
late ClipboardInterface _clipboardInterface;
bool _lock = false;
void _continuePressed() {
if (_lock) {
return;

View file

@ -12,14 +12,17 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../../app_config.dart';
import '../../../../models/keys/view_only_wallet_data.dart';
import '../../../../providers/providers.dart';
import '../../../../themes/stack_colors.dart';
import '../../../../utilities/text_styles.dart';
import '../../../../wallets/wallet/impl/bitcoin_frost_wallet.dart';
import '../../../../wallets/wallet/wallet_mixin_interfaces/mnemonic_interface.dart';
import '../../../../wallets/wallet/wallet_mixin_interfaces/view_only_option_interface.dart';
import '../../../../widgets/background.dart';
import '../../../../widgets/custom_buttons/app_bar_icon_button.dart';
import '../../../../widgets/rounded_container.dart';
import 'delete_view_only_wallet_keys_view.dart';
import 'delete_wallet_recovery_phrase_view.dart';
class DeleteWalletWarningView extends ConsumerWidget {
@ -118,6 +121,7 @@ class DeleteWalletWarningView extends ConsumerWidget {
String keys,
({String config, String keys})? prevGen,
})? frostWalletData;
ViewOnlyWalletData? viewOnlyData;
if (wallet is BitcoinFrostWallet) {
final futures = [
@ -142,18 +146,33 @@ class DeleteWalletWarningView extends ConsumerWidget {
),
);
}
} else if (wallet is MnemonicInterface) {
mnemonic = await wallet.getMnemonicAsWords();
} else {
if (wallet is ViewOnlyOptionInterface &&
wallet.isViewOnly) {
viewOnlyData = await wallet.getViewOnlyWalletData();
} else if (wallet is MnemonicInterface) {
mnemonic = await wallet.getMnemonicAsWords();
}
}
if (context.mounted) {
await Navigator.of(context).pushNamed(
DeleteWalletRecoveryPhraseView.routeName,
arguments: (
walletId: walletId,
mnemonicWords: mnemonic ?? [],
frostWalletData: frostWalletData,
),
);
if (viewOnlyData != null) {
await Navigator.of(context).pushNamed(
DeleteViewOnlyWalletKeysView.routeName,
arguments: (
walletId: walletId,
data: viewOnlyData,
),
);
} else {
await Navigator.of(context).pushNamed(
DeleteWalletRecoveryPhraseView.routeName,
arguments: (
walletId: walletId,
mnemonicWords: mnemonic ?? [],
frostWalletData: frostWalletData,
),
);
}
}
},
child: Text(

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