mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2025-04-21 13:38:15 +00:00
Compare commits
195 commits
Author | SHA1 | Date | |
---|---|---|---|
|
e2014df4df | ||
|
2aedb15489 | ||
|
aa53690f06 | ||
|
89f55c7fec | ||
|
6aa6b8c950 | ||
|
7f69eb8eff | ||
|
f45f8cfa76 | ||
|
7c408a1af6 | ||
|
13d23a42e3 | ||
|
a4de7d34cc | ||
|
27dd2a2134 | ||
|
422c191e65 | ||
|
56bcc23c74 | ||
|
b435838dc3 | ||
|
6534bf62dd | ||
|
29416bdfc6 | ||
|
f43a0ba97d | ||
|
d9acb6728a | ||
|
906c2c3a97 | ||
|
913a4ac7c5 | ||
|
d2e77c9ff0 | ||
|
93212d7970 | ||
|
cfba818f12 | ||
|
ce639c9955 | ||
|
a1b4a35180 | ||
|
86e8c38266 | ||
|
321ae2e7ad | ||
|
fcc2df5e5b | ||
|
5bb7813234 | ||
|
02fec3d581 | ||
|
3251159814 | ||
|
a62b58041a | ||
|
72fa218a98 | ||
|
ec2777b9ff | ||
|
65e6c50a66 | ||
|
6f8c6003d1 | ||
|
dabb2aa1b3 | ||
|
d789ae120b | ||
|
815c16a736 | ||
|
f1014a6c78 | ||
|
3fbc6daba0 | ||
|
15a7a34ece | ||
|
3118968230 | ||
|
37f318a902 | ||
|
42d7275b51 | ||
|
7cf966c443 | ||
|
03f4b2fdea | ||
|
4431d8c689 | ||
|
50163b5d19 | ||
|
78b4e2d6b7 | ||
|
22ff0b1709 | ||
|
e18a254f0c | ||
|
ae0631adeb | ||
|
302ceaaf1f | ||
|
176ed0f89f | ||
|
a503861c0f | ||
|
b32ec57a8d | ||
|
87101c86c2 | ||
|
8c48930feb | ||
|
e4ac7d8569 | ||
|
65782bb711 | ||
|
574d0e82ff | ||
|
fe43785546 | ||
|
f77950de68 | ||
|
e07d878eae | ||
|
8f5d17d026 | ||
|
a40fdfecda | ||
|
6e04f8e34d | ||
|
2298a12afb | ||
|
6361d9f048 | ||
|
ce5d9d43e1 | ||
|
d835b14230 | ||
|
1cddb14bf1 | ||
|
5adfee831e | ||
|
b3e02b64de | ||
|
564c3ba715 | ||
|
441bc8c113 | ||
|
8e703f128c | ||
|
c24935dabf | ||
|
fe2514e97e | ||
|
b190907cae | ||
|
e39c817ec5 | ||
|
6b33aee103 | ||
|
30dedee63f | ||
|
6e725a5bb5 | ||
|
4feb14c7da | ||
|
0f7e44fadd | ||
|
08c5a5fbc7 | ||
|
0d20cb6b3b | ||
|
dd67d2fdbb | ||
|
f7b73620e2 | ||
|
4e26e4c246 | ||
|
c1cd9869c0 | ||
|
448fd0c94d | ||
|
0ed0ef3ed9 | ||
|
f56519ec14 | ||
|
8c6f660ec6 | ||
|
2879e5bc03 | ||
|
a06945b3a2 | ||
|
8b41d0b588 | ||
|
102fab5fae | ||
|
0dad4ad591 | ||
|
2f02d4dc58 | ||
|
e04efc7247 | ||
|
1463ea7972 | ||
|
e1964ea68e | ||
|
c6bff81648 | ||
|
7a3558e7e9 | ||
|
24a20238bc | ||
|
96478de9cc | ||
|
5f9466ca3c | ||
|
34cee82018 | ||
|
2061eba2f2 | ||
|
2edfe0f3cc | ||
|
709eebc1b7 | ||
|
dbc805ed21 | ||
|
7439dbf9fc | ||
|
bf5bfc3d71 | ||
|
60d47f235d | ||
|
48d2ac5e9b | ||
|
3146f4dce9 | ||
|
5ddde67555 | ||
|
fd223ddaea | ||
|
03dcb2babf | ||
|
44f0d0d8df | ||
|
ed65ab5648 | ||
|
29802ddda5 | ||
|
7dd919f795 | ||
|
6539b75f9f | ||
|
05767dea13 | ||
|
9fc6368644 | ||
|
edaaeda838 | ||
|
ae97d3ce24 | ||
|
a576e45199 | ||
|
ad07d7abb9 | ||
|
0d1bfc191f | ||
|
06b0584691 | ||
|
7cdbe581ef | ||
|
116b5747cf | ||
|
63474ac6eb | ||
|
321cf855a7 | ||
|
51b0cc1510 | ||
|
cd9907db5c | ||
|
667f2f504e | ||
|
f1e1bd0dc0 | ||
|
534ee51275 | ||
|
cdffec81df | ||
|
69b3e9b1ab | ||
|
6d291408a3 | ||
|
873fc63045 | ||
|
5a39bb2b64 | ||
|
3ea469bb1f | ||
|
c277cd9641 | ||
|
0623023b3a | ||
|
2e737b5911 | ||
|
5169dfd7fe | ||
|
1c6b84c823 | ||
|
56314e7f24 | ||
|
024086910b | ||
|
8ca4421c2a | ||
|
694910ab18 | ||
|
2dfacd42c5 | ||
|
c4db10c9d3 | ||
|
4589e42ac8 | ||
|
68c7ba6910 | ||
|
176f0ba331 | ||
|
b9ed3ae0a0 | ||
|
452f4a7daa | ||
|
b05f664088 | ||
|
de047339f8 | ||
|
90e421174a | ||
|
75f6e65fd9 | ||
|
1d9b2e39fe | ||
|
6c31e4662d | ||
|
1a3a09a325 | ||
|
ab450684b2 | ||
|
9bd343c987 | ||
|
128dc14ce7 | ||
|
cb11d58c47 | ||
|
1a094d3745 | ||
|
a01dce1c72 | ||
|
38e66bfcb1 | ||
|
0d1bf5895d | ||
|
ad667025ac | ||
|
e7ad498b71 | ||
|
975fe733f0 | ||
|
22f9d4c653 | ||
|
130895a449 | ||
|
2aa548e3e0 | ||
|
4c7cb0c309 | ||
|
516b503f31 | ||
|
1137b50b8a | ||
|
d64c344fd0 | ||
|
9f0f94b29b | ||
|
0d8df43d97 |
321 changed files with 16006 additions and 6389 deletions
.gitignore
android
asset_sources/default_themes
crypto_plugins
docs
fastlane
Appfile
metadata/android/en-US
changelogs
full_description.txtimages
icon.png
short_description.txtphoneScreenshots
05a1aa12278525211a470eb8a4636ea21eac6172836117bc05b5446c25008abe.png1f346858134354959f6d0c4c7776245b125d92235b3d0190f92c44616dc8a509.png29bb7d3c55248043cdcb1db7528c01b3c8bd329b85e1de1e1cc9467a1885bd26.png337c2f1ca500347ade5d2b42ace7f3d72455acbe8a200995789c8e1c6d1a1c38.png6e64d5cfe73fc22c796f621e9caa35b44799fde6ae444b93f9d1d4eaa656ea82.png990f447807ee10406e8992b6b86653308f97b95e602b7080df0138a3973a971a.png9bf57ffd707362f7780534786ec909a7bf3077c54bacc5e20631edcc3e2435db.png
ios
lib
db
electrumx_rpc
cached_electrumx_client.dartclient_manager.dartelectrumx_client.dartsubscribable_electrumx_client.dart
main.dartmodels
networking
pages
add_wallet_views
add_wallet_view
frost_ms
new/steps
reshare
frost_reshare_step_1a.dartfrost_reshare_step_1b.dartfrost_reshare_step_1c.dartfrost_reshare_step_2abd.dartfrost_reshare_step_2c.dartfrost_reshare_step_3abd.dartfrost_reshare_step_4.dartfrost_reshare_step_5.dart
restore
new_wallet_recovery_phrase_warning_view
restore_wallet_view
verify_recovery_phrase_view
address_book_views/subviews
buy_view
coin_control
exchange_view
namecoin_names
11
.gitignore
vendored
11
.gitignore
vendored
|
@ -20,6 +20,9 @@
|
|||
# is commented out by default.
|
||||
#.vscode/
|
||||
|
||||
#CppWinRT manual install
|
||||
Microsoft.Windows*
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
**/doc/api/
|
||||
.dart_tool/
|
||||
|
@ -29,6 +32,7 @@
|
|||
.pub-cache/
|
||||
.pub/
|
||||
/build/
|
||||
android/app/.cxx
|
||||
|
||||
# Web related
|
||||
lib/generated_plugin_registrant.dart
|
||||
|
@ -58,8 +62,6 @@ coverage
|
|||
scripts/**/build
|
||||
/lib/external_api_keys.dart
|
||||
|
||||
libcw_monero.dll
|
||||
libcw_wownero.dll
|
||||
libepic_cash_wallet.dll
|
||||
libmobileliblelantus.dll
|
||||
libtor_ffi.dll
|
||||
|
@ -69,6 +71,10 @@ secp256k1.dll
|
|||
/lib/app_config.g.dart
|
||||
/android/app/src/main/app_icon-playstore.png
|
||||
|
||||
# Dart generated files (Freezed, Riverpod, GoRouter etc..)
|
||||
lib/**/*.g.dart
|
||||
lib/**/*.freezed.dart
|
||||
|
||||
## other generated project files
|
||||
|
||||
pubspec.yaml
|
||||
|
@ -105,3 +111,4 @@ scripts/linux/build/libsecret/subprojects/gi-docgen/.meson-subproject-wrap-hash.
|
|||
|
||||
crypto_plugins/cs_monero/built_outputs
|
||||
crypto_plugins/cs_monero/build
|
||||
crypto_plugins/*.diff
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G
|
||||
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
|
@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
|
|||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip
|
||||
|
|
|
@ -18,7 +18,7 @@ pluginManagement {
|
|||
|
||||
plugins {
|
||||
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
||||
id "com.android.application" version '8.6.0' apply false
|
||||
id "com.android.application" version '8.7.0' apply false
|
||||
id "org.jetbrains.kotlin.android" version "1.8.22" apply false
|
||||
}
|
||||
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1 +1 @@
|
|||
Subproject commit 0bb1b1ced6e0d3c66e383698f89825754c692986
|
||||
Subproject commit 25e6cb3a3e7bee04e425af6beccb47e8d0708fdb
|
|
@ -1 +1 @@
|
|||
Subproject commit 5b08645a5b5d30955f4bde2a624ff89ef516e452
|
||||
Subproject commit 7b325030bce46a423aa46497d1a608b7a8a58976
|
|
@ -1 +1 @@
|
|||
Subproject commit 2451deab817b456ad93d5579c0d0687cb681392a
|
||||
Subproject commit 6f1310eccd336fb3c8dc00b61e39a3f0f3a2b59a
|
|
@ -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.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
|
||||
Install Flutter 3.29.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.29.2` 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.3
|
||||
git checkout 3.29.2
|
||||
echo 'export PATH="$PATH:'"$FLUTTER_DIR"'/bin"' >> "$HOME/.profile"
|
||||
source "$HOME/.profile"
|
||||
flutter precache
|
||||
|
@ -38,7 +38,7 @@ Use `Tools > SDK Manager` to install:
|
|||
- `SDK Tools > Android SDK command line tools`
|
||||
- `SDK Tools > CMake`
|
||||
and for Android builds,
|
||||
- `SDK Tools > Android SDK (API 30)`
|
||||
- `SDK Tools > Android SDK (API 35)`
|
||||
- `SDK Tools > NDK`
|
||||
|
||||
Then in `File > Settings > Plugins`, install the **Flutter** and **Dart** plugins and restart the IDE. In `File > Settings > Languages & Frameworks > Flutter > Editor`, enable auto format on save to match the project's code style. If you have problems with the Dart SDK, make sure to run `flutter` in a terminal to download it (use `source ~/.bashrc` to update your environment variables if you're still using the same terminal from which you ran `setup.sh`). Run `flutter doctor` to install any missing dependencies and review and agree to any license agreements.
|
||||
|
@ -58,7 +58,7 @@ sudo apt-get install libssl-dev curl unzip automake build-essential file pkg-con
|
|||
|
||||
For Ubuntu 20.04,
|
||||
```
|
||||
sudo apt-get install vapigen
|
||||
sudo apt-get install valac
|
||||
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
|
||||
```
|
||||
|
||||
|
@ -68,20 +68,13 @@ sudo apt install pipx libgcrypt20-dev libglib2.0-dev libsecret-1-dev
|
|||
pipx install meson==0.64.1 markdown==3.4.1 markupsafe==2.1.1 jinja2==3.1.2 pygments==2.13.0 toml==0.10.2 typogrify==2.0.7 tomli==2.0.1
|
||||
```
|
||||
|
||||
Install `libtinfo5` (required by [monero_c](https://github.com/MrCyjaneK/monero_c), should be dropped in the future):
|
||||
```
|
||||
wget http://mirrors.kernel.org/ubuntu/pool/universe/n/ncurses/libtinfo5_6.3-2ubuntu0.1_amd64.deb -O libtinfo5.deb \
|
||||
&& apt install ./libtinfo5.deb \
|
||||
&& rm libtinfo5.deb
|
||||
```
|
||||
|
||||
Install [Rust](https://www.rust-lang.org/tools/install) via [rustup.rs](https://rustup.rs), the required Rust toolchains, and `cargo-ndk 2.12.7` with command:
|
||||
Install [Rust](https://www.rust-lang.org/tools/install) via [rustup.rs](https://rustup.rs), the required Rust toolchains, and `cargo-ndk` with command:
|
||||
```
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||
source ~/.bashrc
|
||||
rustup install 1.67.1 1.71.0 1.72.0 1.73.0
|
||||
rustup default 1.67.1
|
||||
cargo install cargo-ndk --version 2.12.7 --locked
|
||||
rustup install 1.85.1 1.81.0
|
||||
rustup default 1.85.1
|
||||
cargo install cargo-ndk
|
||||
```
|
||||
|
||||
Android specific dependencies:
|
||||
|
@ -162,19 +155,6 @@ cd scripts
|
|||
cd scripts
|
||||
./build_app.sh -a stack_wallet -p linux
|
||||
```
|
||||
<!--
|
||||
##### Remove system packages (may be needed for building flutter_libmonero)
|
||||
[`flutter_libmonero`](https://github.com/cypherstack/flutter_libmonero) may have issues building due to conflicts with system packages: if so, follow this section.
|
||||
|
||||
Remove pre-installed system libraries for the following packages built by cryptography plugins in the crypto_plugins folder: `boost iconv libjson-dev libsecret openssl sodium unbound zmq`. You can use
|
||||
```
|
||||
sudo apt list --installed | grep boost
|
||||
```
|
||||
for example to find which pre-installed packages you may need to remove with `sudo apt remove`. Be careful, as some packages (especially boost) are linked to GNOME (GUI) packages: when in doubt, remove `-dev` packages first like with
|
||||
```
|
||||
sudo apt-get remove '^libboost.*-dev.*'
|
||||
```
|
||||
TODO: configure compiler to prefer built over system libraries. Should already use them? -->
|
||||
|
||||
#### Building plugins and configure for Windows
|
||||
Install dependencies like MXE:
|
||||
|
@ -229,13 +209,13 @@ brew install brotli cairo coreutils gdbm gettext glib gmp libevent libidn2 libng
|
|||
```
|
||||
<!-- TODO: determine which of the above list are not needed at all. -->
|
||||
|
||||
Download and install [Rust](https://www.rust-lang.org/tools/install). [Rustup](https://rustup.rs/) is recommended for Rust setup. Use `rustc` to confirm successful installation. Install toolchains 1.67.1 and 1.72.0 and `cbindgen` and `cargo-lipo` too. You will also have to add the platform target(s) `aarch64-apple-ios` and/or `aarch64-apple-darwin`. You can use the command(s):
|
||||
Download and install [Rust](https://www.rust-lang.org/tools/install). [Rustup](https://rustup.rs/) is recommended for Rust setup. Use `rustc` to confirm successful installation. Install toolchains 1.81.0 and 1.85.1 and `cbindgen` and `cargo-lipo` too. You will also have to add the platform target(s) `aarch64-apple-ios` and/or `aarch64-apple-darwin`. You can use the command(s):
|
||||
```
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||
source ~/.bashrc
|
||||
rustup install 1.67.1 1.71.0 1.72.0 1.73.0
|
||||
rustup default 1.67.1
|
||||
cargo install cargo-ndk --version 2.12.7 --locked
|
||||
rustup install 1.85.1 1.81.0
|
||||
rustup default 1.85.1
|
||||
cargo install cargo-ndk
|
||||
cargo install cbindgen cargo-lipo
|
||||
rustup target add aarch64-apple-ios aarch64-apple-darwin
|
||||
```
|
||||
|
@ -243,7 +223,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.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.
|
||||
Install [Flutter](https://docs.flutter.dev/get-started/install) 3.29.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.
|
||||
|
||||
### Build plugins and configure
|
||||
#### Building plugins for iOS
|
||||
|
@ -304,22 +284,19 @@ If the DLLs were built on the WSL filesystem instead of on Windows, copy the res
|
|||
|
||||
- `stack_wallet/crypto_plugins/flutter_libepiccash/scripts/windows/build/libepic_cash_wallet.dll`
|
||||
- `stack_wallet/crypto_plugins/flutter_liblelantus/scripts/windows/build/libmobileliblelantus.dll`
|
||||
<!--
|
||||
- `stack_wallet/crypto_plugins/flutter_libmonero/scripts/windows/build/libcw_monero.dll`
|
||||
- `stack_wallet/crypto_plugins/flutter_libmonero/scripts/windows/build/libcw_wownero.dll`
|
||||
-->
|
||||
|
||||
<!-- TODO: script the copying or installation of libraries from WSL2 to the parent Windows host -->
|
||||
|
||||
Frostdart will be built by the Windows host later.
|
||||
|
||||
### Install Flutter on Windows host
|
||||
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
|
||||
Install Flutter 3.29.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.29.2` 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.3
|
||||
git checkout 3.29.2
|
||||
setx PATH "%PATH%;%FLUTTER_DIR%\bin"
|
||||
echo Flutter setup completed. Please restart your command prompt.
|
||||
```
|
||||
|
@ -329,9 +306,9 @@ Run `flutter doctor` in PowerShell to confirm its installation.
|
|||
### Rust
|
||||
Install [Rust](https://www.rust-lang.org/tools/install) on the Windows host (not in WSL2). Download the installer from [rustup.rs](https://rustup.rs), make sure it works on the commandline (you may need to open a new terminal), and install the following versions:
|
||||
```
|
||||
rustup install 1.67.1 1.71.0 1.72.0 1.73.0
|
||||
rustup default 1.67.1
|
||||
cargo install cargo-ndk --version 2.12.7 --locked
|
||||
rustup install 1.85.1 1.81.0
|
||||
rustup default 1.85.1
|
||||
cargo install cargo-ndk
|
||||
```
|
||||
|
||||
### Windows SDK and Developer Mode
|
||||
|
|
1
fastlane/Appfile
Normal file
1
fastlane/Appfile
Normal file
|
@ -0,0 +1 @@
|
|||
package_name("com.cypherstack.stackwallet")
|
0
fastlane/metadata/android/en-US/changelogs/.gitkeep
Normal file
0
fastlane/metadata/android/en-US/changelogs/.gitkeep
Normal file
11
fastlane/metadata/android/en-US/full_description.txt
Normal file
11
fastlane/metadata/android/en-US/full_description.txt
Normal file
|
@ -0,0 +1,11 @@
|
|||
Stack Wallet is a fully open source cryptocurrency wallet. With an easy to use user interface and quick and speedy transactions, this wallet is ideal for anyone no matter how much they know about the cryptocurrency space. The app is actively maintained to provide new user friendly features.
|
||||
|
||||
Highlights include:
|
||||
- 10 Different cryptocurrencies
|
||||
- All private keys and seeds stay on device and are never shared.
|
||||
- Easy backup and restore feature to save all the information that's important to you.
|
||||
- Trading cryptocurrencies through our partners.
|
||||
- Custom address book
|
||||
- Favorite wallets with fast syncing
|
||||
- Custom Nodes.
|
||||
- Open source software.
|
BIN
fastlane/metadata/android/en-US/images/icon.png
Normal file
BIN
fastlane/metadata/android/en-US/images/icon.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 26 KiB |
Binary file not shown.
After ![]() (image error) Size: 62 KiB |
Binary file not shown.
After ![]() (image error) Size: 57 KiB |
Binary file not shown.
After ![]() (image error) Size: 46 KiB |
Binary file not shown.
After ![]() (image error) Size: 43 KiB |
Binary file not shown.
After ![]() (image error) Size: 65 KiB |
Binary file not shown.
After ![]() (image error) Size: 54 KiB |
Binary file not shown.
After ![]() (image error) Size: 46 KiB |
1
fastlane/metadata/android/en-US/short_description.txt
Normal file
1
fastlane/metadata/android/en-US/short_description.txt
Normal file
|
@ -0,0 +1 @@
|
|||
An open source, non-custodial cryptocurrency wallet.
|
|
@ -9,7 +9,7 @@ PODS:
|
|||
- connectivity_plus (0.0.1):
|
||||
- Flutter
|
||||
- ReachabilitySwift
|
||||
- cs_monero_flutter_libs (0.0.1):
|
||||
- cs_monero_flutter_libs_ios (0.0.1):
|
||||
- Flutter
|
||||
- device_info_plus (0.0.1):
|
||||
- Flutter
|
||||
|
@ -87,8 +87,6 @@ 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)":
|
||||
|
@ -97,8 +95,7 @@ PODS:
|
|||
- sqlite3/common
|
||||
- sqlite3_flutter_libs (0.0.1):
|
||||
- Flutter
|
||||
- "sqlite3 (~> 3.46.0+1)"
|
||||
- sqlite3/dbstatvtab
|
||||
- sqlite3 (~> 3.46.0)
|
||||
- sqlite3/fts5
|
||||
- sqlite3/perf-threadsafe
|
||||
- sqlite3/rtree
|
||||
|
@ -112,12 +109,14 @@ PODS:
|
|||
- Flutter
|
||||
- wakelock_plus (0.0.1):
|
||||
- Flutter
|
||||
- xelis_flutter (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`)
|
||||
- cs_monero_flutter_libs_ios (from `.symlinks/plugins/cs_monero_flutter_libs_ios/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`)
|
||||
|
@ -141,6 +140,7 @@ DEPENDENCIES:
|
|||
- tor_ffi_plugin (from `.symlinks/plugins/tor_ffi_plugin/ios`)
|
||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||
- wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`)
|
||||
- xelis_flutter (from `.symlinks/plugins/xelis_flutter/ios`)
|
||||
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
|
@ -160,8 +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"
|
||||
cs_monero_flutter_libs_ios:
|
||||
:path: ".symlinks/plugins/cs_monero_flutter_libs_ios/ios"
|
||||
device_info_plus:
|
||||
:path: ".symlinks/plugins/device_info_plus/ios"
|
||||
devicelocale:
|
||||
|
@ -208,17 +208,19 @@ EXTERNAL SOURCES:
|
|||
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
||||
wakelock_plus:
|
||||
:path: ".symlinks/plugins/wakelock_plus/ios"
|
||||
xelis_flutter:
|
||||
:path: ".symlinks/plugins/xelis_flutter/ios"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
barcode_scan2: 0af2bb63c81b4565aab6cd78278e4c0fa136dbb0
|
||||
coinlib_flutter: 9275e8255ef67d3da33beb6e117d09ced4f46eb5
|
||||
connectivity_plus: 07c49e96d7fc92bc9920617b83238c4d178b446a
|
||||
cs_monero_flutter_libs: 43cda3474c2bc907f2b2b5bb26fd89cb864fcfc6
|
||||
cs_monero_flutter_libs_ios: fd353631682247f72a36493ff060d4328d6f720d
|
||||
device_info_plus: 97af1d7e84681a90d0693e63169a5d50e0839a0d
|
||||
devicelocale: 35ba84dc7f45f527c3001535d8c8d104edd5d926
|
||||
DKImagePickerController: b512c28220a2b8ac7419f21c491fc8534b7601ac
|
||||
DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179
|
||||
file_picker: 09aa5ec1ab24135ccd7a1621c46c84134bfd6655
|
||||
file_picker: b159e0c068aef54932bb15dc9fd1571818edaf49
|
||||
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
||||
flutter_libepiccash: 36241aa7d3126f6521529985ccb3dc5eaf7bb317
|
||||
flutter_libsparkmobile: 6373955cc3327a926d17059e7405dde2fb12f99f
|
||||
|
@ -231,14 +233,14 @@ SPEC CHECKSUMS:
|
|||
lelantus: 417f0221260013dfc052cae9cf4b741b6479edba
|
||||
local_auth_darwin: 66e40372f1c29f383a314c738c7446e2f7fdadc3
|
||||
MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb
|
||||
package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c
|
||||
package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4
|
||||
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
|
||||
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
|
||||
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
|
||||
SDWebImage: 72f86271a6f3139cc7e4a89220946489d4b9a866
|
||||
share_plus: c3fef564749587fc939ef86ffb283ceac0baf9f5
|
||||
sqlite3: 292c3e1bfe89f64e51ea7fc7dab9182a017c8630
|
||||
sqlite3_flutter_libs: c00457ebd31e59fa6bb830380ddba24d44fbcd3b
|
||||
sqlite3_flutter_libs: 0d611efdf6d1c9297d5ab03dab21b75aeebdae31
|
||||
stack_wallet_backup: 5b8563aba5d8ffbf2ce1944331ff7294a0ec7c03
|
||||
SwiftProtobuf: 6ef3f0e422ef90d6605ca20b21a94f6c1324d6b3
|
||||
SwiftyGif: 6c3eafd0ce693cad58bb63d2b2fb9bacb8552780
|
||||
|
@ -248,4 +250,4 @@ SPEC CHECKSUMS:
|
|||
|
||||
PODFILE CHECKSUM: 57c8aed26fba39d3ec9424816221f294a07c58eb
|
||||
|
||||
COCOAPODS: 1.15.2
|
||||
COCOAPODS: 1.16.2
|
||||
|
|
|
@ -48,6 +48,7 @@
|
|||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
enableGPUValidationMode = "1"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
|
|
|
@ -29,6 +29,8 @@ import '../utilities/constants.dart';
|
|||
import '../utilities/flutter_secure_storage_interface.dart';
|
||||
import '../utilities/logger.dart';
|
||||
import '../utilities/prefs.dart';
|
||||
import '../utilities/stack_file_system.dart';
|
||||
import '../utilities/util.dart';
|
||||
import '../wallets/crypto_currency/crypto_currency.dart';
|
||||
import 'hive/db.dart';
|
||||
import 'isar/main_db.dart';
|
||||
|
@ -43,10 +45,7 @@ class DbVersionMigrator with WalletDB {
|
|||
// safe to skip to v11 for campfire
|
||||
fromVersion = 11;
|
||||
}
|
||||
Logging.instance.log(
|
||||
"Running migrate fromVersion $fromVersion",
|
||||
level: LogLevel.Warning,
|
||||
);
|
||||
Logging.instance.i("Running migrate fromVersion $fromVersion");
|
||||
switch (fromVersion) {
|
||||
case 0:
|
||||
await DB.instance.hive.openBox<dynamic>(DB.boxNameAllWalletsData);
|
||||
|
@ -100,12 +99,13 @@ class DbVersionMigrator with WalletDB {
|
|||
|
||||
try {
|
||||
latestSetId = await client.getLelantusLatestCoinId();
|
||||
} catch (e) {
|
||||
} catch (e, s) {
|
||||
// default to 2 for now
|
||||
latestSetId = 2;
|
||||
Logging.instance.log(
|
||||
Logging.instance.w(
|
||||
"Failed to fetch latest coin id during firo db migrate: $e \nUsing a default value of 2",
|
||||
level: LogLevel.Warning,
|
||||
error: e,
|
||||
stackTrace: s,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -144,7 +144,6 @@ class DbVersionMigrator with WalletDB {
|
|||
),
|
||||
});
|
||||
}
|
||||
Logger.print("newcoins $coins", normalLength: false);
|
||||
await DB.instance.put<dynamic>(
|
||||
boxName: walletInfo.walletId,
|
||||
key: '_lelantus_coins',
|
||||
|
@ -443,6 +442,20 @@ class DbVersionMigrator with WalletDB {
|
|||
// try to continue migrating
|
||||
return await migrate(13, secureStore: secureStore);
|
||||
|
||||
case 13:
|
||||
// migrate
|
||||
await _v13(secureStore);
|
||||
|
||||
// update version
|
||||
await DB.instance.put<dynamic>(
|
||||
boxName: DB.boxNameDBInfo,
|
||||
key: "hive_data_version",
|
||||
value: 14,
|
||||
);
|
||||
|
||||
// try to continue migrating
|
||||
return await migrate(14, secureStore: secureStore);
|
||||
|
||||
default:
|
||||
// finally return
|
||||
return;
|
||||
|
@ -734,4 +747,31 @@ class DbVersionMigrator with WalletDB {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _v13(SecureStorageInterface secureStore) async {
|
||||
if (!(Util.isArmLinux || Util.isTestEnv)) {
|
||||
// open logs db
|
||||
final isar = await Isar.open(
|
||||
[isar_models.LogSchema],
|
||||
directory: (await StackFileSystem.applicationIsarDirectory()).path,
|
||||
inspector: false,
|
||||
maxSizeMiB: 512,
|
||||
);
|
||||
|
||||
// fetch all logs
|
||||
final allLogs = await isar.logs.where().findAll();
|
||||
|
||||
// migrate to simple file based logs. Date/time may be out of order
|
||||
for (final log in allLogs) {
|
||||
Logging.instance.log(
|
||||
log.logLevel.getLoggerLevel(),
|
||||
"MIGRATED LOG::=> ${log.message}",
|
||||
time: DateTime.fromMillisecondsSinceEpoch(log.timestampInMillisUTC),
|
||||
);
|
||||
}
|
||||
|
||||
// finally delete logs db
|
||||
await isar.close(deleteFromDisk: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -166,9 +166,10 @@ class DB {
|
|||
AppConfig.getCryptoCurrencyFor(jsonObject["coin"] as String);
|
||||
return false;
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
Logging.instance.e(
|
||||
"Error, ${jsonObject["coin"]} does not exist, $name wallet cannot be loaded",
|
||||
level: LogLevel.Error,
|
||||
error: e,
|
||||
stackTrace: s,
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
@ -343,7 +344,7 @@ class DB {
|
|||
await DB.instance.deleteBoxFromDisk(boxName: "theme");
|
||||
return true;
|
||||
} catch (e, s) {
|
||||
Logging.instance.log("$e $s", level: LogLevel.Error);
|
||||
Logging.instance.e("$e $s", error: e, stackTrace: s);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ import 'dart:async';
|
|||
import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_libsparkmobile/flutter_libsparkmobile.dart';
|
||||
import 'package:mutex/mutex.dart';
|
||||
import 'package:sqlite3/sqlite3.dart';
|
||||
|
@ -20,16 +19,6 @@ part 'firo_cache_reader.dart';
|
|||
part 'firo_cache_worker.dart';
|
||||
part 'firo_cache_writer.dart';
|
||||
|
||||
/// Temporary debugging log function for this file
|
||||
void _debugLog(Object? object) {
|
||||
if (kDebugMode) {
|
||||
Logging.instance.log(
|
||||
object,
|
||||
level: LogLevel.Debug,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _FiroCache {
|
||||
static const int _setCacheVersion = 2;
|
||||
static const int _tagsCacheVersion = 2;
|
||||
|
@ -116,7 +105,8 @@ abstract class _FiroCache {
|
|||
VACUUM;
|
||||
""",
|
||||
);
|
||||
_debugLog(
|
||||
|
||||
Logging.instance.d(
|
||||
"_deleteAllCache() "
|
||||
"duration = ${DateTime.now().difference(start)}",
|
||||
);
|
||||
|
|
|
@ -42,14 +42,8 @@ abstract class FiroCacheCoordinator {
|
|||
? await usedTagsCacheFile.length()
|
||||
: 0;
|
||||
|
||||
Logging.instance.log(
|
||||
"Spark cache used tags size: $tagsSize",
|
||||
level: LogLevel.Debug,
|
||||
);
|
||||
Logging.instance.log(
|
||||
"Spark cache anon set size: $setSize",
|
||||
level: LogLevel.Debug,
|
||||
);
|
||||
Logging.instance.d("Spark cache used tags size: $tagsSize");
|
||||
Logging.instance.d("Spark cache anon set size: $setSize");
|
||||
|
||||
final int bytes = tagsSize + setSize;
|
||||
|
||||
|
@ -111,10 +105,7 @@ abstract class FiroCacheCoordinator {
|
|||
progressUpdated?.call(prevSize, meta.size);
|
||||
|
||||
if (prevMeta?.blockHash == meta.blockHash) {
|
||||
Logging.instance.log(
|
||||
"prevMeta?.blockHash == meta.blockHash",
|
||||
level: LogLevel.Debug,
|
||||
);
|
||||
Logging.instance.d("prevMeta?.blockHash == meta.blockHash");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -100,17 +100,17 @@ class CachedElectrumXClient {
|
|||
}
|
||||
// save set to db
|
||||
await box.put(groupId, set);
|
||||
Logging.instance.log(
|
||||
Logging.instance.d(
|
||||
"Updated current anonymity set for ${cryptoCurrency.identifier} with group ID $groupId",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
}
|
||||
|
||||
return set;
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"Failed to process CachedElectrumX.getAnonymitySet(): $e\n$s",
|
||||
level: LogLevel.Error,
|
||||
Logging.instance.e(
|
||||
"Failed to process CachedElectrumX.getAnonymitySet(): ",
|
||||
error: e,
|
||||
stackTrace: s,
|
||||
);
|
||||
rethrow;
|
||||
}
|
||||
|
@ -155,16 +155,17 @@ class CachedElectrumXClient {
|
|||
await box.put(txHash, result);
|
||||
}
|
||||
|
||||
// Logging.instance.log("using fetched result", level: LogLevel.Info);
|
||||
// Logging.instance.log("using fetched result");
|
||||
return result;
|
||||
} else {
|
||||
// Logging.instance.log("using cached result", level: LogLevel.Info);
|
||||
// Logging.instance.log("using cached result");
|
||||
return Map<String, dynamic>.from(cachedTx);
|
||||
}
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"Failed to process CachedElectrumX.getTransaction(): $e\n$s",
|
||||
level: LogLevel.Error,
|
||||
Logging.instance.e(
|
||||
"Failed to process CachedElectrumX.getTransaction(): ",
|
||||
error: e,
|
||||
stackTrace: s,
|
||||
);
|
||||
rethrow;
|
||||
}
|
||||
|
@ -212,9 +213,10 @@ class CachedElectrumXClient {
|
|||
|
||||
return resultingList;
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"Failed to process CachedElectrumX.getUsedCoinSerials(): $e\n$s",
|
||||
level: LogLevel.Error,
|
||||
Logging.instance.e(
|
||||
"Failed to process CachedElectrumX.getUsedCoinSerials(): ",
|
||||
error: e,
|
||||
stackTrace: s,
|
||||
);
|
||||
rethrow;
|
||||
}
|
||||
|
|
|
@ -69,9 +69,10 @@ class ClientManager {
|
|||
_heightCompleters[key]!.complete(event.height);
|
||||
}
|
||||
},
|
||||
onError: (Object err, StackTrace s) => Logging.instance.log(
|
||||
"ClientManager listen: $err\n$s",
|
||||
level: LogLevel.Error,
|
||||
onError: (Object err, StackTrace s) => Logging.instance.e(
|
||||
"ClientManager listen",
|
||||
error: err,
|
||||
stackTrace: s,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -233,10 +233,9 @@ class ElectrumXClient {
|
|||
if (!_prefs.torKillSwitch) {
|
||||
// Then we'll just proceed and connect to ElectrumX through
|
||||
// clearnet at the bottom of this function.
|
||||
Logging.instance.log(
|
||||
Logging.instance.w(
|
||||
"Tor preference set but Tor is not enabled, killswitch not set,"
|
||||
" connecting to Electrum adapter through clearnet",
|
||||
level: LogLevel.Warning,
|
||||
);
|
||||
} else {
|
||||
// ... But if the killswitch is set, then we throw an exception.
|
||||
|
@ -398,9 +397,13 @@ class ElectrumXClient {
|
|||
} else {
|
||||
rethrow;
|
||||
}
|
||||
} catch (e) {
|
||||
} catch (e, s) {
|
||||
final errorMessage = e.toString();
|
||||
Logging.instance.log("$host $e", level: LogLevel.Debug);
|
||||
Logging.instance.w(
|
||||
"$host $e",
|
||||
error: e,
|
||||
stackTrace: s,
|
||||
);
|
||||
if (errorMessage.contains("JSON-RPC error")) {
|
||||
currentFailoverIndex = _failovers.length;
|
||||
}
|
||||
|
@ -535,9 +538,8 @@ class ElectrumXClient {
|
|||
).timeout(
|
||||
const Duration(seconds: 30),
|
||||
onTimeout: () {
|
||||
Logging.instance.log(
|
||||
Logging.instance.d(
|
||||
"ElectrumxClient.ping timed out with retryCount=$retryCount, host=$_host",
|
||||
level: LogLevel.Debug,
|
||||
);
|
||||
},
|
||||
) as bool;
|
||||
|
@ -562,10 +564,7 @@ class ElectrumXClient {
|
|||
command: 'blockchain.headers.subscribe',
|
||||
);
|
||||
if (response == null) {
|
||||
Logging.instance.log(
|
||||
"getBlockHeadTip returned null response",
|
||||
level: LogLevel.Error,
|
||||
);
|
||||
Logging.instance.e("getBlockHeadTip returned null response");
|
||||
throw 'getBlockHeadTip returned null response';
|
||||
}
|
||||
return Map<String, dynamic>.from(response as Map);
|
||||
|
@ -756,14 +755,15 @@ class ElectrumXClient {
|
|||
try {
|
||||
final data = List<Map<String, dynamic>>.from(response[i] as List);
|
||||
result.add(data);
|
||||
} catch (e) {
|
||||
} catch (e, s) {
|
||||
// to ensure we keep same length of responses as requests/args
|
||||
// add empty list on error
|
||||
result.add([]);
|
||||
|
||||
Logging.instance.log(
|
||||
Logging.instance.e(
|
||||
"getBatchUTXOs failed to parse response=${response[i]}: $e",
|
||||
level: LogLevel.Error,
|
||||
error: e,
|
||||
stackTrace: s,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -826,15 +826,13 @@ class ElectrumXClient {
|
|||
bool verbose = true,
|
||||
String? requestID,
|
||||
}) async {
|
||||
Logging.instance.log(
|
||||
Logging.instance.d(
|
||||
"attempting to fetch blockchain.transaction.get...",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
await checkElectrumAdapter();
|
||||
final dynamic response = await getElectrumAdapter()!.getTransaction(txHash);
|
||||
Logging.instance.log(
|
||||
Logging.instance.d(
|
||||
"Fetching blockchain.transaction.get finished",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
|
||||
if (!verbose) {
|
||||
|
@ -863,17 +861,15 @@ class ElectrumXClient {
|
|||
String blockhash = "",
|
||||
String? requestID,
|
||||
}) async {
|
||||
Logging.instance.log(
|
||||
Logging.instance.d(
|
||||
"attempting to fetch lelantus.getanonymityset...",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
await checkElectrumAdapter();
|
||||
final Map<String, dynamic> response =
|
||||
await (getElectrumAdapter() as FiroElectrumClient)
|
||||
.getLelantusAnonymitySet(groupId: groupId, blockHash: blockhash);
|
||||
Logging.instance.log(
|
||||
Logging.instance.d(
|
||||
"Fetching lelantus.getanonymityset finished",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
return response;
|
||||
}
|
||||
|
@ -886,16 +882,14 @@ class ElectrumXClient {
|
|||
dynamic mints,
|
||||
String? requestID,
|
||||
}) async {
|
||||
Logging.instance.log(
|
||||
Logging.instance.d(
|
||||
"attempting to fetch lelantus.getmintmetadata...",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
await checkElectrumAdapter();
|
||||
final dynamic response = await (getElectrumAdapter() as FiroElectrumClient)
|
||||
.getLelantusMintData(mints: mints);
|
||||
Logging.instance.log(
|
||||
Logging.instance.d(
|
||||
"Fetching lelantus.getmintmetadata finished",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
return response;
|
||||
}
|
||||
|
@ -906,9 +900,8 @@ class ElectrumXClient {
|
|||
String? requestID,
|
||||
required int startNumber,
|
||||
}) async {
|
||||
Logging.instance.log(
|
||||
Logging.instance.d(
|
||||
"attempting to fetch lelantus.getusedcoinserials...",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
await checkElectrumAdapter();
|
||||
|
||||
|
@ -919,9 +912,8 @@ class ElectrumXClient {
|
|||
response = await (getElectrumAdapter() as FiroElectrumClient)
|
||||
.getLelantusUsedCoinSerials(startNumber: startNumber);
|
||||
// TODO add 2 minute timeout.
|
||||
Logging.instance.log(
|
||||
Logging.instance.d(
|
||||
"Fetching lelantus.getusedcoinserials finished",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
|
||||
retryCount--;
|
||||
|
@ -934,16 +926,14 @@ class ElectrumXClient {
|
|||
///
|
||||
/// ex: 1
|
||||
Future<int> getLelantusLatestCoinId({String? requestID}) async {
|
||||
Logging.instance.log(
|
||||
Logging.instance.d(
|
||||
"attempting to fetch lelantus.getlatestcoinid...",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
await checkElectrumAdapter();
|
||||
final int response =
|
||||
await (getElectrumAdapter() as FiroElectrumClient).getLatestCoinId();
|
||||
Logging.instance.log(
|
||||
Logging.instance.d(
|
||||
"Fetching lelantus.getlatestcoinid finished",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
return response;
|
||||
}
|
||||
|
@ -977,12 +967,11 @@ class ElectrumXClient {
|
|||
coinGroupId: coinGroupId,
|
||||
startBlockHash: startBlockHash,
|
||||
);
|
||||
Logging.instance.log(
|
||||
Logging.instance.d(
|
||||
"Finished ElectrumXClient.getSparkAnonymitySet(coinGroupId"
|
||||
"=$coinGroupId, startBlockHash=$startBlockHash). "
|
||||
"coins.length: ${(response["coins"] as List?)?.length}"
|
||||
"Duration=${DateTime.now().difference(start)}",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
return response;
|
||||
} catch (e) {
|
||||
|
@ -1020,7 +1009,7 @@ class ElectrumXClient {
|
|||
// );
|
||||
//
|
||||
// return tags;
|
||||
// } catch (e) {
|
||||
// } catch (e, s) {
|
||||
// Logging.instance.log(e, level: LogLevel.Error);
|
||||
// rethrow;
|
||||
// }
|
||||
|
@ -1055,7 +1044,7 @@ class ElectrumXClient {
|
|||
// level: LogLevel.Info,
|
||||
// );
|
||||
// return List<Map<String, dynamic>>.from(response);
|
||||
// } catch (e) {
|
||||
// } catch (e, s) {
|
||||
// Logging.instance.log(e, level: LogLevel.Error);
|
||||
// rethrow;
|
||||
// }
|
||||
|
@ -1068,20 +1057,22 @@ class ElectrumXClient {
|
|||
String? requestID,
|
||||
}) async {
|
||||
try {
|
||||
Logging.instance.log(
|
||||
Logging.instance.d(
|
||||
"attempting to fetch spark.getsparklatestcoinid...",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
await checkElectrumAdapter();
|
||||
final int response = await (getElectrumAdapter() as FiroElectrumClient)
|
||||
.getSparkLatestCoinId();
|
||||
Logging.instance.log(
|
||||
Logging.instance.d(
|
||||
"Fetching spark.getsparklatestcoinid finished",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
return response;
|
||||
} catch (e) {
|
||||
Logging.instance.log(e, level: LogLevel.Error);
|
||||
} catch (e, s) {
|
||||
Logging.instance.e(
|
||||
e,
|
||||
error: e,
|
||||
stackTrace: s,
|
||||
);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
@ -1101,15 +1092,18 @@ class ElectrumXClient {
|
|||
.map((e) => e.toHexReversedFromBase64)
|
||||
.toSet();
|
||||
|
||||
Logging.instance.log(
|
||||
Logging.instance.d(
|
||||
"Finished ElectrumXClient.getMempoolTxids(). "
|
||||
"Duration=${DateTime.now().difference(start)}",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
|
||||
return txids;
|
||||
} catch (e) {
|
||||
Logging.instance.log(e, level: LogLevel.Error);
|
||||
} catch (e, s) {
|
||||
Logging.instance.e(
|
||||
e,
|
||||
error: e,
|
||||
stackTrace: s,
|
||||
);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
@ -1146,15 +1140,14 @@ class ElectrumXClient {
|
|||
);
|
||||
}
|
||||
|
||||
Logging.instance.log(
|
||||
Logging.instance.d(
|
||||
"Finished ElectrumXClient.getMempoolSparkData(txids: $txids). "
|
||||
"Duration=${DateTime.now().difference(start)}",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
|
||||
return result;
|
||||
} catch (e, s) {
|
||||
Logging.instance.log("$e\n$s", level: LogLevel.Error);
|
||||
Logging.instance.e("$e\n$s", error: e, stackTrace: s);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
@ -1178,16 +1171,19 @@ class ElectrumXClient {
|
|||
final map = Map<String, dynamic>.from(response as Map);
|
||||
final tags = List<List<dynamic>>.from(map["tagsandtxids"] as List);
|
||||
|
||||
Logging.instance.log(
|
||||
Logging.instance.d(
|
||||
"Finished ElectrumXClient.getSparkUnhashedUsedCoinsTagsWithTxHashes("
|
||||
"startNumber=$startNumber). # of tags fetched=${tags.length}, "
|
||||
"Duration=${DateTime.now().difference(start)}",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
|
||||
return tags;
|
||||
} catch (e) {
|
||||
Logging.instance.log(e, level: LogLevel.Error);
|
||||
} catch (e, s) {
|
||||
Logging.instance.e(
|
||||
e,
|
||||
error: e,
|
||||
stackTrace: s,
|
||||
);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
@ -1199,9 +1195,8 @@ class ElectrumXClient {
|
|||
}) async {
|
||||
try {
|
||||
const command = "spark.getsparkanonymitysetmeta";
|
||||
Logging.instance.log(
|
||||
Logging.instance.d(
|
||||
"[${getElectrumAdapter()?.host}] => attempting to fetch $command...",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
|
||||
final start = DateTime.now();
|
||||
|
@ -1222,18 +1217,21 @@ class ElectrumXClient {
|
|||
size: map["size"] as int,
|
||||
);
|
||||
|
||||
Logging.instance.log(
|
||||
Logging.instance.d(
|
||||
"Finished ElectrumXClient.getSparkAnonymitySetMeta("
|
||||
"requestID=$requestID, "
|
||||
"coinGroupId=$coinGroupId"
|
||||
"). Set meta=$result, "
|
||||
"Duration=${DateTime.now().difference(start)}",
|
||||
level: LogLevel.Debug,
|
||||
);
|
||||
|
||||
return result;
|
||||
} catch (e) {
|
||||
Logging.instance.log(e, level: LogLevel.Error);
|
||||
} catch (e, s) {
|
||||
Logging.instance.e(
|
||||
e,
|
||||
error: e,
|
||||
stackTrace: s,
|
||||
);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
@ -1264,7 +1262,7 @@ class ElectrumXClient {
|
|||
|
||||
final result = map["coins"] as List;
|
||||
|
||||
Logging.instance.log(
|
||||
Logging.instance.d(
|
||||
"Finished ElectrumXClient.getSparkAnonymitySetBySector("
|
||||
"requestID=$requestID, "
|
||||
"coinGroupId=$coinGroupId, "
|
||||
|
@ -1273,12 +1271,15 @@ class ElectrumXClient {
|
|||
"endIndex=$endIndex"
|
||||
"). # of coins=${result.length}, "
|
||||
"Duration=${DateTime.now().difference(start)}",
|
||||
level: LogLevel.Debug,
|
||||
);
|
||||
|
||||
return result;
|
||||
} catch (e) {
|
||||
Logging.instance.log(e, level: LogLevel.Error);
|
||||
} catch (e, s) {
|
||||
Logging.instance.e(
|
||||
e,
|
||||
error: e,
|
||||
stackTrace: s,
|
||||
);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
@ -1301,16 +1302,19 @@ class ElectrumXClient {
|
|||
],
|
||||
);
|
||||
|
||||
Logging.instance.log(
|
||||
Logging.instance.d(
|
||||
"Finished ElectrumXClient.isMasterNodeCollateral, "
|
||||
"response: $response, "
|
||||
"Duration=${DateTime.now().difference(start)}",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
|
||||
return response as bool;
|
||||
} catch (e) {
|
||||
Logging.instance.log(e, level: LogLevel.Error);
|
||||
} catch (e, s) {
|
||||
Logging.instance.e(
|
||||
e,
|
||||
error: e,
|
||||
stackTrace: s,
|
||||
);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
@ -1369,7 +1373,7 @@ class ElectrumXClient {
|
|||
} catch (e, s) {
|
||||
final String msg = "Error parsing fee rate. Response: $response"
|
||||
"\nResult: $response\nError: $e\nStack trace: $s";
|
||||
Logging.instance.log(msg, level: LogLevel.Fatal);
|
||||
Logging.instance.e(msg, error: e, stackTrace: s);
|
||||
throw Exception(msg);
|
||||
}
|
||||
} catch (e) {
|
||||
|
|
|
@ -503,7 +503,7 @@
|
|||
// _responseHandler(response);
|
||||
// } catch (e, s) {
|
||||
// Logging.instance
|
||||
// .log("JsonRPC jsonDecode: $e\n$s", level: LogLevel.Error);
|
||||
// .log("JsonRPC jsonDecode", error: e, stackTrace: s,);
|
||||
// rethrow;
|
||||
// } finally {
|
||||
// _responseData = [];
|
||||
|
|
390
lib/main.dart
390
lib/main.dart
|
@ -21,10 +21,13 @@ import 'package:flutter/services.dart';
|
|||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:keyboard_dismisser/keyboard_dismisser.dart';
|
||||
import 'package:logger/logger.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:window_size/window_size.dart';
|
||||
import 'package:xelis_flutter/src/api/api.dart' as xelis_api;
|
||||
import 'package:xelis_flutter/src/api/logger.dart' as xelis_logging;
|
||||
import 'package:xelis_flutter/src/frb_generated.dart' as xelis_rust;
|
||||
|
||||
import 'app_config.dart';
|
||||
import 'db/db_version_migration.dart';
|
||||
|
@ -35,7 +38,6 @@ import 'db/sqlite/firo_cache.dart';
|
|||
import 'models/exchange/change_now/exchange_transaction.dart';
|
||||
import 'models/exchange/change_now/exchange_transaction_status.dart';
|
||||
import 'models/exchange/response_objects/trade.dart';
|
||||
import 'models/isar/models/isar_models.dart';
|
||||
import 'models/models.dart';
|
||||
import 'models/node_model.dart';
|
||||
import 'models/notification_model.dart';
|
||||
|
@ -56,8 +58,6 @@ import 'providers/global/base_currencies_provider.dart';
|
|||
import 'providers/global/trades_service_provider.dart';
|
||||
import 'providers/providers.dart';
|
||||
import 'route_generator.dart';
|
||||
// import 'package:stackwallet/services/buy/buy_data_loading_service.dart';
|
||||
import 'services/debug_service.dart';
|
||||
import 'services/exchange/exchange_data_loading_service.dart';
|
||||
import 'services/locale_service.dart';
|
||||
import 'services/node_service.dart';
|
||||
|
@ -75,15 +75,47 @@ import 'utilities/prefs.dart';
|
|||
import 'utilities/stack_file_system.dart';
|
||||
import 'utilities/util.dart';
|
||||
import 'wallets/isar/providers/all_wallets_info_provider.dart';
|
||||
import 'wallets/wallet/wallet_mixin_interfaces/spark_interface.dart';
|
||||
import 'widgets/crypto_notifications.dart';
|
||||
|
||||
final openedFromSWBFileStringStateProvider =
|
||||
StateProvider<String?>((ref) => null);
|
||||
final openedFromSWBFileStringStateProvider = StateProvider<String?>(
|
||||
(ref) => null,
|
||||
);
|
||||
|
||||
void startListeningToRustLogs() {
|
||||
xelis_api.createLogStream().listen(
|
||||
(logEntry) {
|
||||
final Level level;
|
||||
switch (logEntry.level) {
|
||||
case xelis_logging.Level.error:
|
||||
level = Level.error;
|
||||
case xelis_logging.Level.warn:
|
||||
level = Level.warning;
|
||||
case xelis_logging.Level.info:
|
||||
level = Level.info;
|
||||
case xelis_logging.Level.debug:
|
||||
level = Level.debug;
|
||||
case xelis_logging.Level.trace:
|
||||
level = Level.trace;
|
||||
}
|
||||
|
||||
Logging.instance.log(
|
||||
level,
|
||||
"[Xelis Rust Log] ${logEntry.tag}: ${logEntry.msg}",
|
||||
);
|
||||
},
|
||||
onError: (dynamic e) {
|
||||
Logging.instance.e("Error receiving Xelis Rust logs: $e");
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// main() is the entry point to the app. It initializes Hive (local database),
|
||||
// runs the MyApp widget and checks for new users, caching the value in the
|
||||
// miscellaneous box for later use
|
||||
void main(List<String> args) async {
|
||||
// talker.info('initializing Rust lib ...');
|
||||
await xelis_rust.RustLib.init();
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
if (Util.isDesktop && args.length == 2 && args.first == "-d") {
|
||||
|
@ -111,26 +143,11 @@ void main(List<String> args) async {
|
|||
if (screenHeight != null) {
|
||||
// starting to height be 3/4 screen height or 900, whichever is smaller
|
||||
final height = min<double>(screenHeight * 0.75, 900);
|
||||
setWindowFrame(
|
||||
Rect.fromLTWH(0, 0, 1220, height),
|
||||
);
|
||||
setWindowFrame(Rect.fromLTWH(0, 0, 1220, height));
|
||||
}
|
||||
}
|
||||
|
||||
// FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding);
|
||||
if (!(Logging.isArmLinux || Logging.isTestEnv)) {
|
||||
final isar = await Isar.open(
|
||||
[LogSchema],
|
||||
directory: (await StackFileSystem.applicationIsarDirectory()).path,
|
||||
inspector: false,
|
||||
maxSizeMiB: 512,
|
||||
);
|
||||
await Logging.instance.init(isar);
|
||||
await DebugService.instance.init(isar);
|
||||
|
||||
// clear out all info logs on startup. No need to await and block
|
||||
unawaited(DebugService.instance.deleteLogsOlderThan());
|
||||
}
|
||||
|
||||
// Registering Transaction Model Adapters
|
||||
DB.instance.hive.registerAdapter(TransactionDataAdapter());
|
||||
|
@ -162,8 +179,9 @@ void main(List<String> args) async {
|
|||
// node model adapter
|
||||
DB.instance.hive.registerAdapter(NodeModelAdapter());
|
||||
|
||||
if (!DB.instance.hive
|
||||
.isAdapterRegistered(lib_monero_compat.WalletInfoAdapter().typeId)) {
|
||||
if (!DB.instance.hive.isAdapterRegistered(
|
||||
lib_monero_compat.WalletInfoAdapter().typeId,
|
||||
)) {
|
||||
DB.instance.hive.registerAdapter(lib_monero_compat.WalletInfoAdapter());
|
||||
}
|
||||
|
||||
|
@ -179,6 +197,17 @@ void main(List<String> args) async {
|
|||
await DB.instance.hive.openBox<dynamic>(DB.boxNamePrefs);
|
||||
await Prefs.instance.init();
|
||||
|
||||
await Logging.instance.initialize(
|
||||
(await StackFileSystem.applicationLogsDirectory(Prefs.instance)).path,
|
||||
level: Prefs.instance.logLevel,
|
||||
);
|
||||
|
||||
await xelis_api.setUpRustLogger();
|
||||
startListeningToRustLogs();
|
||||
|
||||
// setup lib spark logging
|
||||
initSparkLogging(Prefs.instance.logLevel);
|
||||
|
||||
if (AppConfig.appName == "Campfire" &&
|
||||
!Util.isDesktop &&
|
||||
!CampfireMigration.didRun) {
|
||||
|
@ -202,10 +231,12 @@ void main(List<String> args) async {
|
|||
|
||||
// Desktop migrate handled elsewhere (currently desktop_login_view.dart)
|
||||
if (!Util.isDesktop) {
|
||||
final int dbVersion = DB.instance.get<dynamic>(
|
||||
boxName: DB.boxNameDBInfo,
|
||||
key: "hive_data_version",
|
||||
) as int? ??
|
||||
final int dbVersion =
|
||||
DB.instance.get<dynamic>(
|
||||
boxName: DB.boxNameDBInfo,
|
||||
key: "hive_data_version",
|
||||
)
|
||||
as int? ??
|
||||
0;
|
||||
if (dbVersion < Constants.currentDataVersion) {
|
||||
try {
|
||||
|
@ -217,10 +248,10 @@ void main(List<String> args) async {
|
|||
),
|
||||
);
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"Cannot migrate mobile database\n$e $s",
|
||||
level: LogLevel.Error,
|
||||
printFullLength: true,
|
||||
Logging.instance.w(
|
||||
"Cannot migrate mobile database",
|
||||
error: e,
|
||||
stackTrace: s,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -240,22 +271,25 @@ void main(List<String> args) async {
|
|||
|
||||
// verify current user preference theme and revert to default
|
||||
// if problems are found to prevent app being unusable
|
||||
if (!(await ThemeService.instance
|
||||
.verifyInstalled(themeId: Prefs.instance.themeId))) {
|
||||
if (!(await ThemeService.instance.verifyInstalled(
|
||||
themeId: Prefs.instance.themeId,
|
||||
))) {
|
||||
Prefs.instance.themeId = "light";
|
||||
}
|
||||
|
||||
// verify current user preference light brightness theme and revert to default
|
||||
// if problems are found to prevent app being unusable
|
||||
if (!(await ThemeService.instance
|
||||
.verifyInstalled(themeId: Prefs.instance.systemBrightnessLightThemeId))) {
|
||||
if (!(await ThemeService.instance.verifyInstalled(
|
||||
themeId: Prefs.instance.systemBrightnessLightThemeId,
|
||||
))) {
|
||||
Prefs.instance.systemBrightnessLightThemeId = "light";
|
||||
}
|
||||
|
||||
// verify current user preference dark brightness theme and revert to default
|
||||
// if problems are found to prevent app being unusable
|
||||
if (!(await ThemeService.instance
|
||||
.verifyInstalled(themeId: Prefs.instance.systemBrightnessDarkThemeId))) {
|
||||
if (!(await ThemeService.instance.verifyInstalled(
|
||||
themeId: Prefs.instance.systemBrightnessDarkThemeId,
|
||||
))) {
|
||||
Prefs.instance.systemBrightnessDarkThemeId = "dark";
|
||||
}
|
||||
|
||||
|
@ -271,18 +305,14 @@ class MyApp extends StatelessWidget {
|
|||
final localeService = LocaleService();
|
||||
localeService.loadLocale();
|
||||
|
||||
return const KeyboardDismisser(
|
||||
child: MaterialAppWithTheme(),
|
||||
);
|
||||
return const KeyboardDismisser(child: MaterialAppWithTheme());
|
||||
}
|
||||
}
|
||||
|
||||
// Sidenote: MaterialAppWithTheme and InitView are only separated for clarity. No other reason.
|
||||
|
||||
class MaterialAppWithTheme extends ConsumerStatefulWidget {
|
||||
const MaterialAppWithTheme({
|
||||
super.key,
|
||||
});
|
||||
const MaterialAppWithTheme({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<MaterialAppWithTheme> createState() =>
|
||||
|
@ -356,7 +386,9 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
|
|||
prefs: ref.read(prefsChangeNotifierProvider),
|
||||
);
|
||||
ref.read(priceAnd24hChangeNotifierProvider).start(true);
|
||||
await ref.read(pWallets).load(
|
||||
await ref
|
||||
.read(pWallets)
|
||||
.load(
|
||||
ref.read(prefsChangeNotifierProvider),
|
||||
ref.read(mainDBProvider),
|
||||
);
|
||||
|
@ -386,7 +418,9 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
|
|||
if (ref.read(prefsChangeNotifierProvider).isAutoBackupEnabled) {
|
||||
switch (ref.read(prefsChangeNotifierProvider).backupFrequencyType) {
|
||||
case BackupFrequencyType.everyTenMinutes:
|
||||
ref.read(autoSWBServiceProvider).startPeriodicBackupTimer(
|
||||
ref
|
||||
.read(autoSWBServiceProvider)
|
||||
.startPeriodicBackupTimer(
|
||||
duration: const Duration(minutes: 10),
|
||||
);
|
||||
break;
|
||||
|
@ -404,7 +438,7 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
|
|||
// .userID; // Just reading the ref should set it if it's not already set
|
||||
// We shouldn't need to do this, instead only generating an ID when (or if) the userID is looked up when creating a quote
|
||||
} catch (e, s) {
|
||||
Logger.print("$e $s", normalLength: false);
|
||||
Logging.instance.e("load failure", error: e, stackTrace: s);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -419,9 +453,10 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
|
|||
ref.read(prefsChangeNotifierProvider).systemBrightnessDarkThemeId;
|
||||
break;
|
||||
case Brightness.light:
|
||||
themeId = ref
|
||||
.read(prefsChangeNotifierProvider)
|
||||
.systemBrightnessLightThemeId;
|
||||
themeId =
|
||||
ref
|
||||
.read(prefsChangeNotifierProvider)
|
||||
.systemBrightnessLightThemeId;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
|
@ -440,9 +475,8 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
|
|||
ref.read(applicationThemesDirectoryPathProvider.notifier).state =
|
||||
StackFileSystem.themesDir!.path;
|
||||
|
||||
ref.read(themeProvider.state).state = ref.read(pThemeService).getTheme(
|
||||
themeId: themeId,
|
||||
)!;
|
||||
ref.read(themeProvider.state).state =
|
||||
ref.read(pThemeService).getTheme(themeId: themeId)!;
|
||||
|
||||
if (Platform.isAndroid) {
|
||||
// fetch open file if it exists
|
||||
|
@ -470,18 +504,17 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
|
|||
ref.read(prefsChangeNotifierProvider).systemBrightnessDarkThemeId;
|
||||
break;
|
||||
case Brightness.light:
|
||||
themeId = ref
|
||||
.read(prefsChangeNotifierProvider)
|
||||
.systemBrightnessLightThemeId;
|
||||
themeId =
|
||||
ref
|
||||
.read(prefsChangeNotifierProvider)
|
||||
.systemBrightnessLightThemeId;
|
||||
break;
|
||||
}
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (ref.read(prefsChangeNotifierProvider).enableSystemBrightness) {
|
||||
ref.read(themeProvider.state).state =
|
||||
ref.read(pThemeService).getTheme(
|
||||
themeId: themeId,
|
||||
)!;
|
||||
ref.read(pThemeService).getTheme(themeId: themeId)!;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@ -560,15 +593,14 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
|
|||
/// should only be called on android currently
|
||||
Future<void> getOpenFile() async {
|
||||
// update provider with new file content state
|
||||
ref.read(openedFromSWBFileStringStateProvider.state).state =
|
||||
await platform.invokeMethod("getOpenFile");
|
||||
ref.read(openedFromSWBFileStringStateProvider.state).state = await platform
|
||||
.invokeMethod("getOpenFile");
|
||||
|
||||
// call reset to clear cached value
|
||||
await resetOpenPath();
|
||||
|
||||
Logging.instance.log(
|
||||
Logging.instance.d(
|
||||
"This is the .swb content from intent: ${ref.read(openedFromSWBFileStringStateProvider.state).state}",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -579,9 +611,9 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
|
|||
|
||||
Future<void> goToRestoreSWB(String encrypted) async {
|
||||
if (!ref.read(prefsChangeNotifierProvider).hasPin) {
|
||||
await Navigator.of(navigatorKey.currentContext!)
|
||||
.pushNamed(CreatePinView.routeName, arguments: true)
|
||||
.then((value) {
|
||||
await Navigator.of(
|
||||
navigatorKey.currentContext!,
|
||||
).pushNamed(CreatePinView.routeName, arguments: true).then((value) {
|
||||
if (value is! bool || value == false) {
|
||||
Navigator.of(navigatorKey.currentContext!).pushNamed(
|
||||
RestoreFromEncryptedStringView.routeName,
|
||||
|
@ -595,16 +627,17 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
|
|||
navigatorKey.currentContext!,
|
||||
RouteGenerator.getRoute(
|
||||
shouldUseMaterialRoute: RouteGenerator.useMaterialPageRoute,
|
||||
builder: (_) => LockscreenView(
|
||||
showBackButton: true,
|
||||
routeOnSuccess: RestoreFromEncryptedStringView.routeName,
|
||||
routeOnSuccessArguments: encrypted,
|
||||
biometricsCancelButtonString: "CANCEL",
|
||||
biometricsLocalizedReason:
|
||||
"Authenticate to restore ${AppConfig.appName} backup",
|
||||
biometricsAuthenticationTitle:
|
||||
"Restore ${AppConfig.prefix} backup",
|
||||
),
|
||||
builder:
|
||||
(_) => LockscreenView(
|
||||
showBackButton: true,
|
||||
routeOnSuccess: RestoreFromEncryptedStringView.routeName,
|
||||
routeOnSuccessArguments: encrypted,
|
||||
biometricsCancelButtonString: "CANCEL",
|
||||
biometricsLocalizedReason:
|
||||
"Authenticate to restore ${AppConfig.appName} backup",
|
||||
biometricsAuthenticationTitle:
|
||||
"Restore ${AppConfig.prefix} backup",
|
||||
),
|
||||
settings: const RouteSettings(name: "/swbrestorelockscreen"),
|
||||
),
|
||||
),
|
||||
|
@ -614,10 +647,7 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
|
|||
|
||||
InputBorder _buildOutlineInputBorder(Color color) {
|
||||
return OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
width: 1,
|
||||
color: color,
|
||||
),
|
||||
borderSide: BorderSide(width: 1, color: color),
|
||||
borderRadius: BorderRadius.circular(Constants.size.circularBorderRadius),
|
||||
);
|
||||
}
|
||||
|
@ -655,9 +685,7 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
|
|||
),
|
||||
// splashFactory: NoSplash.splashFactory,
|
||||
splashColor: Colors.transparent,
|
||||
buttonTheme: ButtonThemeData(
|
||||
splashColor: colorScheme.splash,
|
||||
),
|
||||
buttonTheme: ButtonThemeData(splashColor: colorScheme.splash),
|
||||
textButtonTheme: TextButtonThemeData(
|
||||
style: ButtonStyle(
|
||||
// splashFactory: NoSplash.splashFactory,
|
||||
|
@ -665,8 +693,9 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
|
|||
minimumSize: MaterialStateProperty.all<Size>(const Size(46, 46)),
|
||||
// textStyle: MaterialStateProperty.all<TextStyle>(
|
||||
// STextStyles.button(context)),
|
||||
foregroundColor:
|
||||
MaterialStateProperty.all(colorScheme.buttonTextSecondary),
|
||||
foregroundColor: MaterialStateProperty.all(
|
||||
colorScheme.buttonTextSecondary,
|
||||
),
|
||||
backgroundColor: MaterialStateProperty.all<Color>(
|
||||
colorScheme.buttonBackSecondary,
|
||||
),
|
||||
|
@ -683,25 +712,22 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
|
|||
checkboxTheme: CheckboxThemeData(
|
||||
splashRadius: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius:
|
||||
BorderRadius.circular(Constants.size.checkboxBorderRadius),
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.checkboxBorderRadius,
|
||||
),
|
||||
),
|
||||
checkColor: MaterialStateColor.resolveWith(
|
||||
(state) {
|
||||
if (state.contains(MaterialState.selected)) {
|
||||
return colorScheme.checkboxIconChecked;
|
||||
}
|
||||
checkColor: MaterialStateColor.resolveWith((state) {
|
||||
if (state.contains(MaterialState.selected)) {
|
||||
return colorScheme.checkboxIconChecked;
|
||||
}
|
||||
return colorScheme.checkboxBGChecked;
|
||||
}),
|
||||
fillColor: MaterialStateColor.resolveWith((states) {
|
||||
if (states.contains(MaterialState.selected)) {
|
||||
return colorScheme.checkboxBGChecked;
|
||||
},
|
||||
),
|
||||
fillColor: MaterialStateColor.resolveWith(
|
||||
(states) {
|
||||
if (states.contains(MaterialState.selected)) {
|
||||
return colorScheme.checkboxBGChecked;
|
||||
}
|
||||
return colorScheme.checkboxBorderEmpty;
|
||||
},
|
||||
),
|
||||
}
|
||||
return colorScheme.checkboxBorderEmpty;
|
||||
}),
|
||||
),
|
||||
appBarTheme: AppBarTheme(
|
||||
centerTitle: false,
|
||||
|
@ -719,91 +745,101 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
|
|||
),
|
||||
// labelStyle: STextStyles.fieldLabel(context),
|
||||
// hintStyle: STextStyles.fieldLabel(context),
|
||||
enabledBorder:
|
||||
_buildOutlineInputBorder(colorScheme.textFieldDefaultBG),
|
||||
focusedBorder:
|
||||
_buildOutlineInputBorder(colorScheme.textFieldDefaultBG),
|
||||
enabledBorder: _buildOutlineInputBorder(
|
||||
colorScheme.textFieldDefaultBG,
|
||||
),
|
||||
focusedBorder: _buildOutlineInputBorder(
|
||||
colorScheme.textFieldDefaultBG,
|
||||
),
|
||||
errorBorder: _buildOutlineInputBorder(colorScheme.textFieldDefaultBG),
|
||||
disabledBorder:
|
||||
_buildOutlineInputBorder(colorScheme.textFieldDefaultBG),
|
||||
focusedErrorBorder:
|
||||
_buildOutlineInputBorder(colorScheme.textFieldDefaultBG),
|
||||
disabledBorder: _buildOutlineInputBorder(
|
||||
colorScheme.textFieldDefaultBG,
|
||||
),
|
||||
focusedErrorBorder: _buildOutlineInputBorder(
|
||||
colorScheme.textFieldDefaultBG,
|
||||
),
|
||||
),
|
||||
),
|
||||
home: CryptoNotifications(
|
||||
child: Util.isDesktop
|
||||
? FutureBuilder(
|
||||
future: loadShared(),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
if (_desktopHasPassword) {
|
||||
String? startupWalletId;
|
||||
if (ref
|
||||
.read(prefsChangeNotifierProvider)
|
||||
.gotoWalletOnStartup) {
|
||||
startupWalletId = ref
|
||||
child:
|
||||
Util.isDesktop
|
||||
? FutureBuilder(
|
||||
future: loadShared(),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
if (_desktopHasPassword) {
|
||||
String? startupWalletId;
|
||||
if (ref
|
||||
.read(prefsChangeNotifierProvider)
|
||||
.startupWalletId;
|
||||
}
|
||||
.gotoWalletOnStartup) {
|
||||
startupWalletId =
|
||||
ref
|
||||
.read(prefsChangeNotifierProvider)
|
||||
.startupWalletId;
|
||||
}
|
||||
|
||||
return DesktopLoginView(
|
||||
startupWalletId: startupWalletId,
|
||||
load: load,
|
||||
);
|
||||
} else {
|
||||
return const IntroView();
|
||||
}
|
||||
} else {
|
||||
return const LoadingView();
|
||||
}
|
||||
},
|
||||
)
|
||||
: FutureBuilder(
|
||||
future: load(),
|
||||
builder: (BuildContext context, AsyncSnapshot<void> snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
// FlutterNativeSplash.remove();
|
||||
if (ref.read(pAllWalletsInfo).isNotEmpty ||
|
||||
ref.read(prefsChangeNotifierProvider).hasPin) {
|
||||
// return HomeView();
|
||||
|
||||
String? startupWalletId;
|
||||
if (ref
|
||||
.read(prefsChangeNotifierProvider)
|
||||
.gotoWalletOnStartup) {
|
||||
startupWalletId = ref
|
||||
.read(prefsChangeNotifierProvider)
|
||||
.startupWalletId;
|
||||
}
|
||||
|
||||
return LockscreenView(
|
||||
isInitialAppLogin: true,
|
||||
routeOnSuccess: HomeView.routeName,
|
||||
routeOnSuccessArguments: startupWalletId,
|
||||
biometricsAuthenticationTitle:
|
||||
"Unlock ${AppConfig.prefix}",
|
||||
biometricsLocalizedReason:
|
||||
"Unlock your ${AppConfig.appName} using biometrics",
|
||||
biometricsCancelButtonString: "Cancel",
|
||||
);
|
||||
} else {
|
||||
if (AppConfig.appName == "Campfire" &&
|
||||
!CampfireMigration.didRun &&
|
||||
CampfireMigration.hasOldWallets) {
|
||||
return const CampfireMigrateView();
|
||||
return DesktopLoginView(
|
||||
startupWalletId: startupWalletId,
|
||||
load: load,
|
||||
);
|
||||
} else {
|
||||
return const IntroView();
|
||||
}
|
||||
} else {
|
||||
return const LoadingView();
|
||||
}
|
||||
} else {
|
||||
// CURRENTLY DISABLED as cannot be animated
|
||||
// technically not needed as FlutterNativeSplash will overlay
|
||||
// anything returned here until the future completes but
|
||||
// FutureBuilder requires you to return something
|
||||
return const LoadingView();
|
||||
}
|
||||
},
|
||||
),
|
||||
},
|
||||
)
|
||||
: FutureBuilder(
|
||||
future: load(),
|
||||
builder: (
|
||||
BuildContext context,
|
||||
AsyncSnapshot<void> snapshot,
|
||||
) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
// FlutterNativeSplash.remove();
|
||||
if (ref.read(pAllWalletsInfo).isNotEmpty ||
|
||||
ref.read(prefsChangeNotifierProvider).hasPin) {
|
||||
// return HomeView();
|
||||
|
||||
String? startupWalletId;
|
||||
if (ref
|
||||
.read(prefsChangeNotifierProvider)
|
||||
.gotoWalletOnStartup) {
|
||||
startupWalletId =
|
||||
ref
|
||||
.read(prefsChangeNotifierProvider)
|
||||
.startupWalletId;
|
||||
}
|
||||
|
||||
return LockscreenView(
|
||||
isInitialAppLogin: true,
|
||||
routeOnSuccess: HomeView.routeName,
|
||||
routeOnSuccessArguments: startupWalletId,
|
||||
biometricsAuthenticationTitle:
|
||||
"Unlock ${AppConfig.prefix}",
|
||||
biometricsLocalizedReason:
|
||||
"Unlock your ${AppConfig.appName} using biometrics",
|
||||
biometricsCancelButtonString: "Cancel",
|
||||
);
|
||||
} else {
|
||||
if (AppConfig.appName == "Campfire" &&
|
||||
!CampfireMigration.didRun &&
|
||||
CampfireMigration.hasOldWallets) {
|
||||
return const CampfireMigrateView();
|
||||
} else {
|
||||
return const IntroView();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// CURRENTLY DISABLED as cannot be animated
|
||||
// technically not needed as FlutterNativeSplash will overlay
|
||||
// anything returned here until the future completes but
|
||||
// FutureBuilder requires you to return something
|
||||
return const LoadingView();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
*/
|
||||
|
||||
import 'package:decimal/decimal.dart';
|
||||
|
||||
import '../../../utilities/logger.dart';
|
||||
|
||||
enum CNEstimateType { direct, reverse }
|
||||
|
@ -112,8 +113,11 @@ class CNExchangeEstimate {
|
|||
toAmount: Decimal.parse(json["toAmount"].toString()),
|
||||
);
|
||||
} catch (e, s) {
|
||||
Logging.instance
|
||||
.log("Failed to parse: $json \n$e\n$s", level: LogLevel.Fatal);
|
||||
Logging.instance.e(
|
||||
"Failed to parse: $json",
|
||||
error: e,
|
||||
stackTrace: s,
|
||||
);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,8 +57,11 @@ class EstimatedExchangeAmount {
|
|||
networkFee: Decimal.tryParse(json["networkFee"].toString()),
|
||||
);
|
||||
} catch (e, s) {
|
||||
Logging.instance
|
||||
.log("Failed to parse: $json \n$e\n$s", level: LogLevel.Fatal);
|
||||
Logging.instance.e(
|
||||
"Failed to parse: $json",
|
||||
error: e,
|
||||
stackTrace: s,
|
||||
);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -187,7 +187,7 @@ class ExchangeTransactionStatus {
|
|||
});
|
||||
|
||||
factory ExchangeTransactionStatus.fromJson(Map<String, dynamic> json) {
|
||||
Logging.instance.log(json, printFullLength: true, level: LogLevel.Info);
|
||||
Logging.instance.d(json, stackTrace: StackTrace.current);
|
||||
try {
|
||||
return ExchangeTransactionStatus(
|
||||
status: changeNowTransactionStatusFromStringIgnoreCase(
|
||||
|
@ -228,7 +228,7 @@ class ExchangeTransactionStatus {
|
|||
payload: json["payload"] as Object?,
|
||||
);
|
||||
} catch (e, s) {
|
||||
Logging.instance.log("$e\n$s", level: LogLevel.Fatal);
|
||||
Logging.instance.f("", error: e, stackTrace: s);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
*/
|
||||
|
||||
import 'package:decimal/decimal.dart';
|
||||
|
||||
import '../../../utilities/logger.dart';
|
||||
|
||||
class Estimate {
|
||||
|
@ -18,6 +19,7 @@ class Estimate {
|
|||
final String? warningMessage;
|
||||
final String? rateId;
|
||||
final String exchangeProvider;
|
||||
final String? exchangeProviderLogo;
|
||||
final String? kycRating;
|
||||
|
||||
Estimate({
|
||||
|
@ -27,6 +29,7 @@ class Estimate {
|
|||
this.warningMessage,
|
||||
this.rateId,
|
||||
required this.exchangeProvider,
|
||||
this.exchangeProviderLogo,
|
||||
this.kycRating,
|
||||
});
|
||||
|
||||
|
@ -46,7 +49,11 @@ class Estimate {
|
|||
kycRating: kycRating,
|
||||
);
|
||||
} catch (e, s) {
|
||||
Logging.instance.log("Estimate.fromMap(): $e\n$s", level: LogLevel.Error);
|
||||
Logging.instance.e(
|
||||
"Estimate.fromMap()",
|
||||
error: e,
|
||||
stackTrace: s,
|
||||
);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
*/
|
||||
|
||||
import 'package:decimal/decimal.dart';
|
||||
|
||||
import '../../../utilities/logger.dart';
|
||||
|
||||
class FixedRateMarket {
|
||||
|
@ -53,10 +54,7 @@ class FixedRateMarket {
|
|||
minerFee: Decimal.tryParse(json["minerFee"].toString()),
|
||||
);
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"FixedRateMarket.fromMap(): $e\n$s",
|
||||
level: LogLevel.Error,
|
||||
);
|
||||
Logging.instance.e("FixedRateMarket.fromMap(): ", error: e, stackTrace: s);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -59,10 +59,7 @@ class SPCurrency {
|
|||
warningsTo: json["warnings_to"] as List<dynamic>,
|
||||
);
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"SPCurrency.fromJson failed to parse: $e\n$s",
|
||||
level: LogLevel.Error,
|
||||
);
|
||||
Logging.instance.e("SPCurrency.fromJson failed to parse: ", error: e, stackTrace: s);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -175,7 +175,8 @@ enum AddressType {
|
|||
frostMS,
|
||||
p2tr,
|
||||
solana,
|
||||
cardanoShelley;
|
||||
cardanoShelley,
|
||||
xelis;
|
||||
|
||||
String get readableName {
|
||||
switch (this) {
|
||||
|
@ -213,6 +214,8 @@ enum AddressType {
|
|||
return "P2TR (taproot)";
|
||||
case AddressType.cardanoShelley:
|
||||
return "Cardano Shelley";
|
||||
case AddressType.xelis:
|
||||
return "Xelis";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -279,6 +279,7 @@ const _AddresstypeEnumValueMap = {
|
|||
'p2tr': 14,
|
||||
'solana': 15,
|
||||
'cardanoShelley': 16,
|
||||
'xelis': 17,
|
||||
};
|
||||
const _AddresstypeValueEnumMap = {
|
||||
0: AddressType.p2pkh,
|
||||
|
@ -298,6 +299,7 @@ const _AddresstypeValueEnumMap = {
|
|||
14: AddressType.p2tr,
|
||||
15: AddressType.solana,
|
||||
16: AddressType.cardanoShelley,
|
||||
17: AddressType.xelis,
|
||||
};
|
||||
|
||||
Id _addressGetId(Address object) {
|
||||
|
|
|
@ -77,19 +77,31 @@ class UTXO {
|
|||
int getConfirmations(int currentChainHeight) {
|
||||
if (blockTime == null || blockHash == null) return 0;
|
||||
if (blockHeight == null || blockHeight! <= 0) return 0;
|
||||
return max(0, currentChainHeight - (blockHeight! - 1));
|
||||
return _isMonero()
|
||||
? max(0, currentChainHeight - (blockHeight!))
|
||||
: max(0, currentChainHeight - (blockHeight! - 1));
|
||||
}
|
||||
|
||||
bool isConfirmed(
|
||||
int currentChainHeight,
|
||||
int minimumConfirms,
|
||||
int minimumCoinbaseConfirms,
|
||||
) {
|
||||
int minimumCoinbaseConfirms, {
|
||||
int? overrideMinConfirms, // added to handle namecoin name op outputs
|
||||
}) {
|
||||
final confirmations = getConfirmations(currentChainHeight);
|
||||
|
||||
if (overrideMinConfirms != null) {
|
||||
return confirmations >= overrideMinConfirms;
|
||||
}
|
||||
return confirmations >=
|
||||
(isCoinbase ? minimumCoinbaseConfirms : minimumConfirms);
|
||||
}
|
||||
|
||||
// fuzzy
|
||||
bool _isMonero() {
|
||||
return keyImage != null;
|
||||
}
|
||||
|
||||
@ignore
|
||||
String? get keyImage {
|
||||
if (otherData == null) {
|
||||
|
@ -98,7 +110,7 @@ class UTXO {
|
|||
|
||||
try {
|
||||
final map = jsonDecode(otherData!) as Map;
|
||||
return map["keyImage"] as String;
|
||||
return map[UTXOOtherDataKeys.keyImage] as String;
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
|
@ -169,3 +181,9 @@ class UTXO {
|
|||
@ignore
|
||||
int get hashCode => Object.hashAll([walletId, txid, vout]);
|
||||
}
|
||||
|
||||
abstract final class UTXOOtherDataKeys {
|
||||
static const keyImage = "keyImage";
|
||||
static const spent = "spent";
|
||||
static const nameOpData = "nameOpData";
|
||||
}
|
||||
|
|
|
@ -109,7 +109,9 @@ class TransactionV2 {
|
|||
|
||||
int getConfirmations(int currentChainHeight) {
|
||||
if (height == null || height! <= 0) return 0;
|
||||
return max(0, currentChainHeight - (height! - 1));
|
||||
return _isMonero()
|
||||
? max(0, currentChainHeight - (height!))
|
||||
: max(0, currentChainHeight - (height! - 1));
|
||||
}
|
||||
|
||||
bool isConfirmed(
|
||||
|
|
|
@ -13,6 +13,7 @@ import 'dart:io';
|
|||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
|
||||
import '../../app_config.dart';
|
||||
import '../../utilities/extensions/impl/box_shadow.dart';
|
||||
import '../../utilities/extensions/impl/gradient.dart';
|
||||
|
@ -1884,10 +1885,7 @@ class StackTheme {
|
|||
(map[mainNetId] as String).toBigIntFromHex.toInt(),
|
||||
);
|
||||
} else {
|
||||
Logging.instance.log(
|
||||
"Color not found in theme for $mainNetId",
|
||||
level: LogLevel.Error,
|
||||
);
|
||||
Logging.instance.w("Color not found in theme for $mainNetId");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
25
lib/models/namecoin_dns/dns_a_record_address_type.dart
Normal file
25
lib/models/namecoin_dns/dns_a_record_address_type.dart
Normal file
|
@ -0,0 +1,25 @@
|
|||
enum DNSAddressType {
|
||||
IPv4,
|
||||
IPv6,
|
||||
Tor,
|
||||
Freenet,
|
||||
I2P,
|
||||
ZeroNet;
|
||||
|
||||
String get key {
|
||||
switch (this) {
|
||||
case DNSAddressType.IPv4:
|
||||
return "ip";
|
||||
case DNSAddressType.IPv6:
|
||||
return "ip6";
|
||||
case DNSAddressType.Tor:
|
||||
return "_tor";
|
||||
case DNSAddressType.Freenet:
|
||||
return "freenet";
|
||||
case DNSAddressType.I2P:
|
||||
return "i2p";
|
||||
case DNSAddressType.ZeroNet:
|
||||
return "zeronet";
|
||||
}
|
||||
}
|
||||
}
|
129
lib/models/namecoin_dns/dns_record.dart
Normal file
129
lib/models/namecoin_dns/dns_record.dart
Normal file
|
@ -0,0 +1,129 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:namecoin/namecoin.dart';
|
||||
|
||||
import '../../utilities/extensions/extensions.dart';
|
||||
import 'dns_record_type.dart';
|
||||
|
||||
@Immutable()
|
||||
abstract class DNSRecordBase {
|
||||
final String name;
|
||||
|
||||
DNSRecordBase({required this.name});
|
||||
|
||||
String getValueString();
|
||||
}
|
||||
|
||||
@Immutable()
|
||||
final class RawDNSRecord extends DNSRecordBase {
|
||||
final String value;
|
||||
|
||||
RawDNSRecord({required super.name, required this.value});
|
||||
|
||||
@override
|
||||
String getValueString() => value;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return "RawDNSRecord(name: $name, value: $value)";
|
||||
}
|
||||
}
|
||||
|
||||
@Immutable()
|
||||
final class DNSRecord extends DNSRecordBase {
|
||||
final DNSRecordType type;
|
||||
final Map<String, dynamic> data;
|
||||
|
||||
DNSRecord({
|
||||
required super.name,
|
||||
required this.type,
|
||||
required this.data,
|
||||
});
|
||||
|
||||
@override
|
||||
String getValueString() {
|
||||
// TODO error handling
|
||||
dynamic value = data;
|
||||
while (value is Map) {
|
||||
value = value[value.keys.first];
|
||||
}
|
||||
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
DNSRecord copyWith({
|
||||
DNSRecordType? type,
|
||||
Map<String, dynamic>? data,
|
||||
}) {
|
||||
return DNSRecord(
|
||||
type: type ?? this.type,
|
||||
data: data ?? this.data,
|
||||
name: name,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return "DNSRecord(name: $name, type: $type, data: $data)";
|
||||
}
|
||||
|
||||
static String merge(List<DNSRecord> records) {
|
||||
final Map<String, dynamic> result = {};
|
||||
|
||||
for (final record in records) {
|
||||
switch (record.type) {
|
||||
case DNSRecordType.CNAME:
|
||||
if (result[record.data.keys.first] != null) {
|
||||
throw Exception("CNAME record already exists");
|
||||
}
|
||||
_deepMerge(result, record.data);
|
||||
break;
|
||||
|
||||
case DNSRecordType.TLS:
|
||||
case DNSRecordType.NS:
|
||||
case DNSRecordType.DS:
|
||||
case DNSRecordType.SRV:
|
||||
case DNSRecordType.SSH:
|
||||
case DNSRecordType.TXT:
|
||||
case DNSRecordType.IMPORT:
|
||||
case DNSRecordType.A:
|
||||
_deepMerge(result, record.data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
final string = jsonEncode(result);
|
||||
if (string.toUint8ListFromUtf8.length > valueMaxLength) {
|
||||
throw Exception(
|
||||
"Value length (${string.toUint8ListFromUtf8.length}) exceeds maximum"
|
||||
" allowed ($valueMaxLength)",
|
||||
);
|
||||
}
|
||||
|
||||
return string;
|
||||
}
|
||||
}
|
||||
|
||||
void _deepMerge(Map<String, dynamic> base, Map<String, dynamic> updates) {
|
||||
updates.forEach((key, value) {
|
||||
if (value is Map<String, dynamic> && base[key] is Map<String, dynamic>) {
|
||||
_deepMerge(base[key] as Map<String, dynamic>, value);
|
||||
} else if (value is List && base[key] is List) {
|
||||
(base[key] as List).addAll(value);
|
||||
} else {
|
||||
if (base[key] != null) {
|
||||
throw Exception(
|
||||
"Attempted to overwrite value: ${base[key]} where key=$key",
|
||||
);
|
||||
}
|
||||
if (value is Map) {
|
||||
base[key] = Map<String, dynamic>.from(value);
|
||||
} else if (value is List) {
|
||||
base[key] = List<dynamic>.from(value);
|
||||
} else {
|
||||
base[key] = value;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
68
lib/models/namecoin_dns/dns_record_type.dart
Normal file
68
lib/models/namecoin_dns/dns_record_type.dart
Normal file
|
@ -0,0 +1,68 @@
|
|||
enum DNSRecordType {
|
||||
A,
|
||||
CNAME,
|
||||
NS,
|
||||
DS,
|
||||
TLS,
|
||||
SRV,
|
||||
TXT,
|
||||
IMPORT,
|
||||
SSH;
|
||||
|
||||
String get info {
|
||||
switch (this) {
|
||||
case DNSRecordType.A:
|
||||
return "An A record maps your domain to an address (IPv4, IPv6, Tor,"
|
||||
" Freenet, I2P, or ZeroNet).";
|
||||
case DNSRecordType.CNAME:
|
||||
return "A CNAME record redirects your domain to another domain,"
|
||||
" essentially acting as an alias.";
|
||||
case DNSRecordType.NS:
|
||||
return "An NS record specifies the nameservers that are authoritative"
|
||||
" for your domain.";
|
||||
case DNSRecordType.DS:
|
||||
return "A DS record holds information about DNSSEC (DNS Security "
|
||||
"Extensions) for your domain, helping with verification and "
|
||||
"integrity.";
|
||||
case DNSRecordType.TLS:
|
||||
return "A TLS record is used for specifying details about how to "
|
||||
"establish secure connections (like TLS certificates) for your"
|
||||
" domain.";
|
||||
case DNSRecordType.SRV:
|
||||
return "An SRV record specifies the location of servers for specific"
|
||||
" services, such as SIP, XMPP, or Minecraft servers.";
|
||||
case DNSRecordType.TXT:
|
||||
return "A TXT record allows you to add arbitrary text to your domain's"
|
||||
" DNS record, often used for verification (e.g., SPF, DKIM).";
|
||||
case DNSRecordType.IMPORT:
|
||||
return "An IMPORT record is used to bring in DNS records from an"
|
||||
" external source into your domain's configuration.";
|
||||
case DNSRecordType.SSH:
|
||||
return "An SSH record provides information related to SSH public keys"
|
||||
" for securely connecting to your domain's services.";
|
||||
}
|
||||
}
|
||||
|
||||
String? get key {
|
||||
switch (this) {
|
||||
case DNSRecordType.A:
|
||||
return null;
|
||||
case DNSRecordType.CNAME:
|
||||
return "alias";
|
||||
case DNSRecordType.NS:
|
||||
return "ns";
|
||||
case DNSRecordType.DS:
|
||||
return "ds";
|
||||
case DNSRecordType.TLS:
|
||||
return "tls";
|
||||
case DNSRecordType.SRV:
|
||||
return "srv";
|
||||
case DNSRecordType.TXT:
|
||||
return "txt";
|
||||
case DNSRecordType.IMPORT:
|
||||
return "import";
|
||||
case DNSRecordType.SSH:
|
||||
return "sshfp";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -65,12 +65,12 @@ class NodeModel {
|
|||
int? port,
|
||||
String? name,
|
||||
bool? useSSL,
|
||||
String? loginName,
|
||||
required String? loginName,
|
||||
bool? enabled,
|
||||
String? coinName,
|
||||
bool? isFailover,
|
||||
bool? isDown,
|
||||
bool? trusted,
|
||||
required bool? trusted,
|
||||
bool? torEnabled,
|
||||
bool? clearnetEnabled,
|
||||
}) {
|
||||
|
@ -80,12 +80,12 @@ class NodeModel {
|
|||
name: name ?? this.name,
|
||||
id: id,
|
||||
useSSL: useSSL ?? this.useSSL,
|
||||
loginName: loginName ?? this.loginName,
|
||||
loginName: loginName,
|
||||
enabled: enabled ?? this.enabled,
|
||||
coinName: coinName ?? this.coinName,
|
||||
isFailover: isFailover ?? this.isFailover,
|
||||
isDown: isDown ?? this.isDown,
|
||||
trusted: trusted ?? this.trusted,
|
||||
trusted: trusted,
|
||||
torEnabled: torEnabled ?? this.torEnabled,
|
||||
clearnetEnabled: clearnetEnabled ?? this.clearnetEnabled,
|
||||
);
|
||||
|
|
|
@ -53,10 +53,7 @@ class HTTP {
|
|||
response.statusCode,
|
||||
);
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"HTTP.get() rethrew: $e\n$s",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
Logging.instance.w("HTTP.get() rethrew: ", error: e, stackTrace: s);
|
||||
rethrow;
|
||||
} finally {
|
||||
httpClient.close(force: true);
|
||||
|
@ -99,10 +96,7 @@ class HTTP {
|
|||
response.statusCode,
|
||||
);
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"HTTP.post() rethrew: $e\n$s",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
Logging.instance.w("HTTP.post() rethrew: ", error: e, stackTrace: s);
|
||||
rethrow;
|
||||
} finally {
|
||||
httpClient.close(force: true);
|
||||
|
@ -119,9 +113,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,
|
||||
onError: (Object err, StackTrace s) => Logging.instance.e(
|
||||
"Http wrapper layer listen",
|
||||
error: err,
|
||||
stackTrace: s,
|
||||
),
|
||||
);
|
||||
return completer.future;
|
||||
|
|
|
@ -153,7 +153,12 @@ class _AddWalletViewState extends ConsumerState<AddWalletView> {
|
|||
}
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
ref.refresh(addWalletSelectedEntityStateProvider);
|
||||
if (mounted) {
|
||||
ref.refresh(addWalletSelectedEntityStateProvider);
|
||||
if (isDesktop) {
|
||||
_searchFocusNode.requestFocus();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
super.initState();
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../../../../../frost_route_generator.dart';
|
||||
import '../../../../wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart';
|
||||
import '../../../../../providers/frost_wallet/frost_wallet_providers.dart';
|
||||
import '../../../../../services/frost.dart';
|
||||
import '../../../../../utilities/logger.dart';
|
||||
|
@ -15,6 +15,7 @@ import '../../../../../widgets/dialogs/frost/frost_error_dialog.dart';
|
|||
import '../../../../../widgets/frost_step_user_steps.dart';
|
||||
import '../../../../../widgets/stack_dialog.dart';
|
||||
import '../../../../../widgets/textfields/frost_step_field.dart';
|
||||
import '../../../../wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart';
|
||||
|
||||
class FrostCreateStep2 extends ConsumerStatefulWidget {
|
||||
const FrostCreateStep2({
|
||||
|
@ -177,10 +178,7 @@ class _FrostCreateStep2State extends ConsumerState<FrostCreateStep2> {
|
|||
.routeName,
|
||||
);
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"$e\n$s",
|
||||
level: LogLevel.Fatal,
|
||||
);
|
||||
Logging.instance.f("$e\n$s", error: e, stackTrace: s,);
|
||||
if (context.mounted) {
|
||||
return await showDialog<void>(
|
||||
context: context,
|
||||
|
|
|
@ -178,10 +178,7 @@ class _FrostCreateStep3State extends ConsumerState<FrostCreateStep3> {
|
|||
.routeName,
|
||||
);
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"$e\n$s",
|
||||
level: LogLevel.Fatal,
|
||||
);
|
||||
Logging.instance.f("$e\n$s", error: e, stackTrace: s,);
|
||||
|
||||
if (context.mounted) {
|
||||
return await showDialog<void>(
|
||||
|
|
|
@ -219,10 +219,7 @@ class _FrostCreateStep5State extends ConsumerState<FrostCreateStep5> {
|
|||
);
|
||||
}
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"$e\n$s",
|
||||
level: LogLevel.Fatal,
|
||||
);
|
||||
Logging.instance.f("$e\n$s", error: e, stackTrace: s,);
|
||||
|
||||
// pop progress dialog
|
||||
if (context.mounted && !progressPopped) {
|
||||
|
|
|
@ -81,10 +81,7 @@ class _FrostReshareStep1aState extends ConsumerState<FrostReshareStep1a> {
|
|||
);
|
||||
}
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"$e\n$s",
|
||||
level: LogLevel.Fatal,
|
||||
);
|
||||
Logging.instance.f("$e\n$s", error: e, stackTrace: s,);
|
||||
|
||||
if (mounted) {
|
||||
await showDialog<void>(
|
||||
|
|
|
@ -117,10 +117,7 @@ class _FrostReshareStep1bState extends ConsumerState<FrostReshareStep1b> {
|
|||
);
|
||||
}
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"$e\n$s",
|
||||
level: LogLevel.Fatal,
|
||||
);
|
||||
Logging.instance.f("$e\n$s", error: e, stackTrace: s,);
|
||||
|
||||
if (mounted) {
|
||||
await showDialog<void>(
|
||||
|
|
|
@ -204,10 +204,7 @@ class _FrostReshareStep1cState extends ConsumerState<FrostReshareStep1c> {
|
|||
);
|
||||
}
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"$e\n$s",
|
||||
level: LogLevel.Fatal,
|
||||
);
|
||||
Logging.instance.f("$e\n$s", error: e, stackTrace: s,);
|
||||
|
||||
if (context.mounted) {
|
||||
await showDialog<void>(
|
||||
|
|
|
@ -79,10 +79,7 @@ class _FrostReshareStep2abdState extends ConsumerState<FrostReshareStep2abd> {
|
|||
.routeName,
|
||||
);
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"$e\n$s",
|
||||
level: LogLevel.Fatal,
|
||||
);
|
||||
Logging.instance.f("$e\n$s", error: e, stackTrace: s,);
|
||||
|
||||
if (mounted) {
|
||||
await showDialog<void>(
|
||||
|
|
|
@ -59,10 +59,7 @@ class _FrostReshareStep2cState extends ConsumerState<FrostReshareStep2c> {
|
|||
.routeName,
|
||||
);
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"$e\n$s",
|
||||
level: LogLevel.Fatal,
|
||||
);
|
||||
Logging.instance.f("$e\n$s", error: e, stackTrace: s,);
|
||||
|
||||
if (mounted) {
|
||||
await showDialog<void>(
|
||||
|
|
|
@ -70,10 +70,7 @@ class _FrostReshareStep3abdState extends ConsumerState<FrostReshareStep3abd> {
|
|||
.routeName,
|
||||
);
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"$e\n$s",
|
||||
level: LogLevel.Fatal,
|
||||
);
|
||||
Logging.instance.f("$e\n$s", error: e, stackTrace: s,);
|
||||
if (mounted) {
|
||||
await showDialog<void>(
|
||||
context: context,
|
||||
|
|
|
@ -88,10 +88,7 @@ class _FrostReshareStep4State extends ConsumerState<FrostReshareStep4> {
|
|||
);
|
||||
}
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"$e\n$s",
|
||||
level: LogLevel.Fatal,
|
||||
);
|
||||
Logging.instance.f("$e\n$s", error: e, stackTrace: s,);
|
||||
if (mounted) {
|
||||
await showDialog<void>(
|
||||
context: context,
|
||||
|
|
|
@ -104,10 +104,7 @@ class _FrostReshareStep5State extends ConsumerState<FrostReshareStep5> {
|
|||
}
|
||||
}
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"$e\n$s",
|
||||
level: LogLevel.Fatal,
|
||||
);
|
||||
Logging.instance.f("$e\n$s", error: e, stackTrace: s,);
|
||||
if (mounted) {
|
||||
await showDialog<void>(
|
||||
context: context,
|
||||
|
|
|
@ -171,9 +171,10 @@ class _RestoreFrostMsWalletViewState
|
|||
);
|
||||
}
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"$e\n$s",
|
||||
level: LogLevel.Fatal,
|
||||
Logging.instance.e(
|
||||
"",
|
||||
error: e,
|
||||
stackTrace: s,
|
||||
);
|
||||
|
||||
if (mounted) {
|
||||
|
@ -234,9 +235,8 @@ class _RestoreFrostMsWalletViewState
|
|||
);
|
||||
|
||||
if (qrResult == null) {
|
||||
Logging.instance.log(
|
||||
Logging.instance.d(
|
||||
"Qr scanning cancelled",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
} else {
|
||||
// TODO [prio=low]: Validate QR code data.
|
||||
|
@ -248,9 +248,10 @@ class _RestoreFrostMsWalletViewState
|
|||
}
|
||||
}
|
||||
} on PlatformException catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"Failed to get camera permissions while trying to scan qr code: $e\n$s",
|
||||
level: LogLevel.Warning,
|
||||
Logging.instance.w(
|
||||
"Failed to get camera permissions while trying to scan qr code: ",
|
||||
error: e,
|
||||
stackTrace: s,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -191,7 +191,7 @@ class _NewWalletRecoveryPhraseWarningViewState
|
|||
|
||||
// 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) {
|
||||
if (coin is Monero || coin is Wownero || coin is Xelis) {
|
||||
// currently a special case due to the
|
||||
// xmr/wow libraries handling their
|
||||
// own mnemonic generation
|
||||
|
@ -274,10 +274,7 @@ class _NewWalletRecoveryPhraseWarningViewState
|
|||
|
||||
return (wallet, fetchedMnemonic);
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"$e\n$s",
|
||||
level: LogLevel.Fatal,
|
||||
);
|
||||
Logging.instance.f("$e\n$s", error: e, stackTrace: s,);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ import '../../../wallets/isar/models/wallet_info.dart';
|
|||
import '../../../wallets/wallet/impl/epiccash_wallet.dart';
|
||||
import '../../../wallets/wallet/impl/monero_wallet.dart';
|
||||
import '../../../wallets/wallet/impl/wownero_wallet.dart';
|
||||
import '../../../wallets/wallet/impl/xelis_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';
|
||||
|
@ -264,6 +265,10 @@ class _RestoreViewOnlyWalletViewState
|
|||
await (wallet as WowneroWallet).init(isRestore: true);
|
||||
break;
|
||||
|
||||
case const (XelisWallet):
|
||||
await (wallet as XelisWallet).init(isRestore: true);
|
||||
break;
|
||||
|
||||
default:
|
||||
await wallet.init();
|
||||
}
|
||||
|
|
|
@ -25,6 +25,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:wakelock_plus/wakelock_plus.dart';
|
||||
|
||||
import 'package:xelis_flutter/src/api/seed_search_engine.dart' as x_seed;
|
||||
|
||||
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';
|
||||
|
@ -48,7 +50,8 @@ import '../../../wallets/isar/models/wallet_info.dart';
|
|||
import '../../../wallets/wallet/impl/epiccash_wallet.dart';
|
||||
import '../../../wallets/wallet/impl/monero_wallet.dart';
|
||||
import '../../../wallets/wallet/impl/wownero_wallet.dart';
|
||||
import '../../../wallets/wallet/intermediate/lib_monero_wallet.dart';
|
||||
import '../../../wallets/wallet/intermediate/external_wallet.dart';
|
||||
import '../../../wallets/wallet/impl/xelis_wallet.dart';
|
||||
import '../../../wallets/wallet/supporting/epiccash_wallet_info_extension.dart';
|
||||
import '../../../wallets/wallet/wallet.dart';
|
||||
import '../../../widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
|
@ -103,6 +106,7 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
|
|||
late final int _seedWordCount;
|
||||
late final bool isDesktop;
|
||||
|
||||
x_seed.SearchEngine? _xelisSeedSearch;
|
||||
final HashSet<String> _wordListHashSet = HashSet.from(bip39wordlist.WORDLIST);
|
||||
final ScrollController controller = ScrollController();
|
||||
|
||||
|
@ -114,6 +118,8 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
|
|||
|
||||
late final TextSelectionControls textSelectionControls;
|
||||
|
||||
bool _hideSeedWords = false;
|
||||
|
||||
Future<void> onControlsPaste(TextSelectionDelegate delegate) async {
|
||||
final data = await widget.clipboard.getData(Clipboard.kTextPlain);
|
||||
if (data?.text == null) {
|
||||
|
@ -165,6 +171,10 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
|
|||
// _focusNodes.add(FocusNode());
|
||||
}
|
||||
|
||||
if (widget.coin is Xelis) {
|
||||
_xelisSeedSearch = x_seed.SearchEngine.init(languageIndex: BigInt.from(0));
|
||||
}
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
|
@ -197,6 +207,9 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
|
|||
);
|
||||
return wowneroWordList.contains(word);
|
||||
}
|
||||
if (widget.coin is Xelis) {
|
||||
return _xelisSeedSearch!.search(query: word).length > 0;
|
||||
}
|
||||
return _wordListHashSet.contains(word);
|
||||
}
|
||||
|
||||
|
@ -212,6 +225,8 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
|
|||
|
||||
Future<void> attemptRestore() async {
|
||||
if (_formKey.currentState!.validate()) {
|
||||
if (mounted) setState(() => _hideSeedWords = true);
|
||||
|
||||
String mnemonic = "";
|
||||
for (final element in _controllers) {
|
||||
mnemonic += " ${element.text.trim().toLowerCase()}";
|
||||
|
@ -279,9 +294,9 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
|
|||
);
|
||||
}
|
||||
|
||||
// TODO: do actual check to make sure it is a valid mnemonic for monero
|
||||
// TODO: do actual check to make sure it is a valid mnemonic for monero + xelis
|
||||
if (bip39.validateMnemonic(mnemonic) == false &&
|
||||
!(widget.coin is Monero || widget.coin is Wownero)) {
|
||||
!(widget.coin is Monero || widget.coin is Wownero || widget.coin is Xelis)) {
|
||||
unawaited(
|
||||
showFloatingFlushBar(
|
||||
type: FlushBarType.warning,
|
||||
|
@ -313,6 +328,8 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
|
|||
onCancel: () async {
|
||||
isRestoring = false;
|
||||
|
||||
if (mounted) setState(() => _hideSeedWords = false);
|
||||
|
||||
await ref.read(pWallets).deleteWallet(
|
||||
info,
|
||||
ref.read(secureStoreProvider),
|
||||
|
@ -364,13 +381,17 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
|
|||
await (wallet as WowneroWallet).init(isRestore: true);
|
||||
break;
|
||||
|
||||
case const (XelisWallet):
|
||||
await (wallet as XelisWallet).init(isRestore: true);
|
||||
break;
|
||||
|
||||
default:
|
||||
await wallet.init();
|
||||
}
|
||||
|
||||
await wallet.recover(isRescan: false);
|
||||
|
||||
if (wallet is LibMoneroWallet) {
|
||||
if (wallet is ExternalWallet) {
|
||||
await wallet.exit();
|
||||
}
|
||||
|
||||
|
@ -471,6 +492,8 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
|
|||
);
|
||||
},
|
||||
);
|
||||
|
||||
if (mounted) setState(() => _hideSeedWords = false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -619,24 +642,24 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
|
|||
|
||||
final results = AddressUtils.decodeQRSeedData(qrResult.rawContent);
|
||||
|
||||
Logging.instance.log("scan parsed: $results", level: LogLevel.Info);
|
||||
|
||||
if (results["mnemonic"] != null) {
|
||||
final list = (results["mnemonic"] as List)
|
||||
.map((value) => value as String)
|
||||
.toList(growable: false);
|
||||
if (list.isNotEmpty) {
|
||||
_clearAndPopulateMnemonic(list);
|
||||
Logging.instance.log("mnemonic populated", level: LogLevel.Info);
|
||||
Logging.instance.i("mnemonic populated");
|
||||
} else {
|
||||
Logging.instance
|
||||
.log("mnemonic failed to populate", level: LogLevel.Info);
|
||||
Logging.instance.i("mnemonic failed to populate");
|
||||
}
|
||||
}
|
||||
} on PlatformException catch (e) {
|
||||
} on PlatformException catch (e, s) {
|
||||
// likely failed to get camera permissions
|
||||
Logging.instance
|
||||
.log("Restore wallet qr scan failed: $e", level: LogLevel.Warning);
|
||||
Logging.instance.e(
|
||||
"Restore wallet qr scan failed: $e",
|
||||
error: e,
|
||||
stackTrace: s,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -868,6 +891,7 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
|
|||
child: Column(
|
||||
children: [
|
||||
TextFormField(
|
||||
obscureText: _hideSeedWords,
|
||||
autocorrect: !isDesktop,
|
||||
enableSuggestions: !isDesktop,
|
||||
textCapitalization:
|
||||
|
@ -1001,6 +1025,7 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
|
|||
child: Column(
|
||||
children: [
|
||||
TextFormField(
|
||||
obscureText: _hideSeedWords,
|
||||
autocorrect: !isDesktop,
|
||||
enableSuggestions: !isDesktop,
|
||||
textCapitalization:
|
||||
|
@ -1135,6 +1160,7 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
|
|||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 4),
|
||||
child: TextFormField(
|
||||
obscureText: _hideSeedWords,
|
||||
autocorrect: !isDesktop,
|
||||
enableSuggestions: !isDesktop,
|
||||
textCapitalization: TextCapitalization.none,
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../../../../providers/global/secure_store_provider.dart';
|
||||
import '../../../../providers/providers.dart';
|
||||
import '../../../../themes/stack_colors.dart';
|
||||
|
@ -72,11 +73,8 @@ class _RestoreFailedDialogState extends ConsumerState<RestoreFailedDialog> {
|
|||
ref.read(secureStoreProvider),
|
||||
);
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"Error while getting wallet info in restore failed dialog\n"
|
||||
"Error: $e\nStack trace: $s",
|
||||
level: LogLevel.Error,
|
||||
);
|
||||
Logging.instance.e("Error while getting wallet info in restore failed dialog\n"
|
||||
"Error: $e\nStack trace: $s");
|
||||
} finally {
|
||||
if (mounted) {
|
||||
Navigator.of(context).pop();
|
||||
|
|
|
@ -40,6 +40,7 @@ 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/impl/xelis_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';
|
||||
|
@ -225,6 +226,10 @@ class _VerifyRecoveryPhraseViewState
|
|||
await (voWallet as WowneroWallet).init(isRestore: true);
|
||||
break;
|
||||
|
||||
case const (XelisWallet):
|
||||
await (voWallet as XelisWallet).init(isRestore: true);
|
||||
break;
|
||||
|
||||
default:
|
||||
await voWallet.init();
|
||||
}
|
||||
|
@ -306,10 +311,7 @@ class _VerifyRecoveryPhraseViewState
|
|||
throw ex!;
|
||||
}
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"$e\n$s",
|
||||
level: LogLevel.Fatal,
|
||||
);
|
||||
Logging.instance.f("$e\n$s", error: e, stackTrace: s,);
|
||||
|
||||
if (mounted) {
|
||||
await showDialog<void>(
|
||||
|
|
|
@ -115,10 +115,7 @@ class _NewContactAddressEntryFormState
|
|||
// .read(shouldShowLockscreenOnResumeStateProvider
|
||||
// .state)
|
||||
// .state = true;
|
||||
Logging.instance.log(
|
||||
"Failed to get camera permissions to scan address qr code: $e\n$s",
|
||||
level: LogLevel.Warning,
|
||||
);
|
||||
Logging.instance.w("Failed to get camera permissions to scan address qr code: ", error: e, stackTrace: s);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -311,10 +311,7 @@ class _BuyFormState extends ConsumerState<BuyForm> {
|
|||
.read(simplexProvider)
|
||||
.updateSupportedCryptos(response.value!); // TODO validate
|
||||
} else {
|
||||
Logging.instance.log(
|
||||
"_loadSimplexCurrencies: $response",
|
||||
level: LogLevel.Warning,
|
||||
);
|
||||
Logging.instance.d("_loadSimplexCurrencies: $response");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -326,10 +323,7 @@ class _BuyFormState extends ConsumerState<BuyForm> {
|
|||
.read(simplexProvider)
|
||||
.updateSupportedFiats(response.value!); // TODO validate
|
||||
} else {
|
||||
Logging.instance.log(
|
||||
"_loadSimplexCurrencies: $response",
|
||||
level: LogLevel.Warning,
|
||||
);
|
||||
Logging.instance.d("_loadSimplexCurrencies: $response");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -626,10 +620,7 @@ class _BuyFormState extends ConsumerState<BuyForm> {
|
|||
ref.read(simplexProvider).updateQuote(response.value!);
|
||||
return BuyResponse(value: response.value!);
|
||||
} else {
|
||||
Logging.instance.log(
|
||||
"_loadQuote: $response",
|
||||
level: LogLevel.Warning,
|
||||
);
|
||||
Logging.instance.d("_loadQuote: $response");
|
||||
return BuyResponse(
|
||||
exception: response.exception ??
|
||||
BuyException(
|
||||
|
@ -724,20 +715,14 @@ class _BuyFormState extends ConsumerState<BuyForm> {
|
|||
|
||||
final qrResult = await scanner.scan();
|
||||
|
||||
Logging.instance.log(
|
||||
"qrResult content: ${qrResult.rawContent}",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
Logging.instance.d("qrResult content: ${qrResult.rawContent}");
|
||||
|
||||
final paymentData = AddressUtils.parsePaymentUri(
|
||||
qrResult.rawContent,
|
||||
logging: Logging.instance,
|
||||
);
|
||||
|
||||
Logging.instance.log(
|
||||
"qrResult parsed: $paymentData",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
Logging.instance.d("qrResult parsed: $paymentData");
|
||||
|
||||
if (paymentData != null) {
|
||||
// auto fill address
|
||||
|
@ -760,9 +745,10 @@ class _BuyFormState extends ConsumerState<BuyForm> {
|
|||
} 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,
|
||||
Logging.instance.e(
|
||||
"Failed to get camera permissions while trying to scan qr code in SendView: ",
|
||||
error: e,
|
||||
stackTrace: s,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1241,7 +1227,11 @@ class _BuyFormState extends ConsumerState<BuyForm> {
|
|||
}
|
||||
});
|
||||
} catch (e, s) {
|
||||
Logging.instance.log("$e\n$s", level: LogLevel.Info);
|
||||
Logging.instance.w(
|
||||
"",
|
||||
error: e,
|
||||
stackTrace: s,
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
|
|
|
@ -304,7 +304,7 @@ class CoinIconForTicker extends ConsumerWidget {
|
|||
height: size,
|
||||
);
|
||||
} catch (e, s) {
|
||||
Logging.instance.log("$e\n$s", level: LogLevel.Fatal);
|
||||
Logging.instance.f("", error: e, stackTrace: s);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,6 +26,8 @@ import '../../utilities/assets.dart';
|
|||
import '../../utilities/constants.dart';
|
||||
import '../../utilities/text_styles.dart';
|
||||
import '../../wallets/isar/providers/wallet_info_provider.dart';
|
||||
import '../../wallets/wallet/impl/namecoin_wallet.dart';
|
||||
import '../../wallets/wallet/wallet.dart';
|
||||
import '../../wallets/wallet/wallet_mixin_interfaces/coin_control_interface.dart';
|
||||
import '../../widgets/animated_widgets/rotate_icon.dart';
|
||||
import '../../widgets/app_bar_field.dart';
|
||||
|
@ -88,6 +90,18 @@ class _CoinControlViewState extends ConsumerState<CoinControlView> {
|
|||
await coinControlInterface.updateBalance();
|
||||
}
|
||||
|
||||
bool _isConfirmed(UTXO utxo, int currentChainHeight, Wallet wallet) {
|
||||
if (wallet is NamecoinWallet) {
|
||||
return wallet.checkUtxoConfirmed(utxo, currentChainHeight);
|
||||
} else {
|
||||
return utxo.isConfirmed(
|
||||
currentChainHeight,
|
||||
wallet.cryptoCurrency.minConfirms,
|
||||
wallet.cryptoCurrency.minCoinbaseConfirms,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
if (widget.selectedUTXOs != null) {
|
||||
|
@ -347,10 +361,15 @@ class _CoinControlViewState extends ConsumerState<CoinControlView> {
|
|||
CoinControlViewType.manage ||
|
||||
(widget.type == CoinControlViewType.use &&
|
||||
!utxo.isBlocked &&
|
||||
utxo.isConfirmed(
|
||||
_isConfirmed(
|
||||
utxo,
|
||||
currentHeight,
|
||||
minConfirms,
|
||||
coin.minCoinbaseConfirms,
|
||||
ref.watch(
|
||||
pWallets.select(
|
||||
(s) => s
|
||||
.getWallet(widget.walletId),
|
||||
),
|
||||
),
|
||||
)),
|
||||
initialSelectedState: isSelected,
|
||||
onSelectedChanged: (value) {
|
||||
|
@ -412,10 +431,16 @@ class _CoinControlViewState extends ConsumerState<CoinControlView> {
|
|||
(widget.type ==
|
||||
CoinControlViewType.use &&
|
||||
!_showBlocked &&
|
||||
utxo.isConfirmed(
|
||||
_isConfirmed(
|
||||
utxo,
|
||||
currentHeight,
|
||||
minConfirms,
|
||||
coin.minCoinbaseConfirms,
|
||||
ref.watch(
|
||||
pWallets.select(
|
||||
(s) => s.getWallet(
|
||||
widget.walletId,
|
||||
),
|
||||
),
|
||||
),
|
||||
)),
|
||||
initialSelectedState: isSelected,
|
||||
onSelectedChanged: (value) {
|
||||
|
@ -557,10 +582,16 @@ class _CoinControlViewState extends ConsumerState<CoinControlView> {
|
|||
CoinControlViewType
|
||||
.use &&
|
||||
!utxo.isBlocked &&
|
||||
utxo.isConfirmed(
|
||||
_isConfirmed(
|
||||
utxo,
|
||||
currentHeight,
|
||||
minConfirms,
|
||||
coin.minCoinbaseConfirms,
|
||||
ref.watch(
|
||||
pWallets.select(
|
||||
(s) => s.getWallet(
|
||||
widget.walletId,
|
||||
),
|
||||
),
|
||||
),
|
||||
)),
|
||||
initialSelectedState: isSelected,
|
||||
onSelectedChanged: (value) {
|
||||
|
|
|
@ -20,6 +20,8 @@ import '../../utilities/amount/amount_formatter.dart';
|
|||
import '../../utilities/constants.dart';
|
||||
import '../../utilities/text_styles.dart';
|
||||
import '../../wallets/isar/providers/wallet_info_provider.dart';
|
||||
import '../../wallets/wallet/impl/namecoin_wallet.dart';
|
||||
import '../../wallets/wallet/wallet.dart';
|
||||
import '../../widgets/conditional_parent.dart';
|
||||
import '../../widgets/icon_widgets/utxo_status_icon.dart';
|
||||
import '../../widgets/rounded_container.dart';
|
||||
|
@ -52,6 +54,18 @@ class _UtxoCardState extends ConsumerState<UtxoCard> {
|
|||
|
||||
late bool _selected;
|
||||
|
||||
bool _isConfirmed(UTXO utxo, int currentChainHeight, Wallet wallet) {
|
||||
if (wallet is NamecoinWallet) {
|
||||
return wallet.checkUtxoConfirmed(utxo, currentChainHeight);
|
||||
} else {
|
||||
return utxo.isConfirmed(
|
||||
currentChainHeight,
|
||||
wallet.cryptoCurrency.minConfirms,
|
||||
wallet.cryptoCurrency.minCoinbaseConfirms,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_selected = widget.initialSelectedState;
|
||||
|
@ -110,18 +124,16 @@ class _UtxoCardState extends ConsumerState<UtxoCard> {
|
|||
),
|
||||
child: UTXOStatusIcon(
|
||||
blocked: utxo.isBlocked,
|
||||
status: utxo.isConfirmed(
|
||||
status: _isConfirmed(
|
||||
utxo,
|
||||
currentHeight,
|
||||
ref
|
||||
.watch(pWallets)
|
||||
.getWallet(widget.walletId)
|
||||
.cryptoCurrency
|
||||
.minConfirms,
|
||||
ref
|
||||
.watch(pWallets)
|
||||
.getWallet(widget.walletId)
|
||||
.cryptoCurrency
|
||||
.minCoinbaseConfirms,
|
||||
ref.watch(
|
||||
pWallets.select(
|
||||
(s) => s.getWallet(
|
||||
widget.walletId,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
? UTXOStatusIconStatus.confirmed
|
||||
: UTXOStatusIconStatus.unconfirmed,
|
||||
|
|
|
@ -23,6 +23,8 @@ import '../../utilities/amount/amount_formatter.dart';
|
|||
import '../../utilities/text_styles.dart';
|
||||
import '../../utilities/util.dart';
|
||||
import '../../wallets/isar/providers/wallet_info_provider.dart';
|
||||
import '../../wallets/wallet/impl/namecoin_wallet.dart';
|
||||
import '../../wallets/wallet/wallet.dart';
|
||||
import '../../widgets/background.dart';
|
||||
import '../../widgets/conditional_parent.dart';
|
||||
import '../../widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
|
@ -67,6 +69,18 @@ class _UtxoDetailsViewState extends ConsumerState<UtxoDetailsView> {
|
|||
await MainDB.instance.putUTXO(utxo!.copyWith(isBlocked: !utxo!.isBlocked));
|
||||
}
|
||||
|
||||
bool _isConfirmed(UTXO utxo, int currentChainHeight, Wallet wallet) {
|
||||
if (wallet is NamecoinWallet) {
|
||||
return wallet.checkUtxoConfirmed(utxo, currentChainHeight);
|
||||
} else {
|
||||
return utxo.isConfirmed(
|
||||
currentChainHeight,
|
||||
wallet.cryptoCurrency.minConfirms,
|
||||
wallet.cryptoCurrency.minCoinbaseConfirms,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
utxo = MainDB.instance.isar.utxos
|
||||
|
@ -95,14 +109,14 @@ class _UtxoDetailsViewState extends ConsumerState<UtxoDetailsView> {
|
|||
final coin = ref.watch(pWalletCoin(widget.walletId));
|
||||
final currentHeight = ref.watch(pWalletChainHeight(widget.walletId));
|
||||
|
||||
final confirmed = utxo!.isConfirmed(
|
||||
final confirmed = _isConfirmed(
|
||||
utxo!,
|
||||
currentHeight,
|
||||
ref.watch(pWallets).getWallet(widget.walletId).cryptoCurrency.minConfirms,
|
||||
ref
|
||||
.watch(pWallets)
|
||||
.getWallet(widget.walletId)
|
||||
.cryptoCurrency
|
||||
.minCoinbaseConfirms,
|
||||
ref.watch(
|
||||
pWallets.select(
|
||||
(s) => s.getWallet(widget.walletId),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return ConditionalParent(
|
||||
|
|
|
@ -162,10 +162,7 @@ class _ConfirmChangeNowSendViewState
|
|||
Navigator.of(context).popUntil(ModalRoute.withName(routeOnSuccessName));
|
||||
}
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"Broadcast transaction failed: $e\n$s",
|
||||
level: LogLevel.Error,
|
||||
);
|
||||
Logging.instance.e("Broadcast transaction failed: ", error: e, stackTrace: s);
|
||||
|
||||
// pop sending dialog
|
||||
Navigator.of(context).pop();
|
||||
|
|
|
@ -98,9 +98,10 @@ class _Step2ViewState extends ConsumerState<Step2View> {
|
|||
});
|
||||
}
|
||||
} 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,
|
||||
Logging.instance.w(
|
||||
"Failed to get camera permissions while trying to scan qr code in SendView: ",
|
||||
error: e,
|
||||
stackTrace: s,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -135,9 +136,10 @@ class _Step2ViewState extends ConsumerState<Step2View> {
|
|||
});
|
||||
}
|
||||
} 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,
|
||||
Logging.instance.w(
|
||||
"Failed to get camera permissions while trying to scan qr code in SendView: ",
|
||||
error: e,
|
||||
stackTrace: s,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -303,8 +305,11 @@ class _Step2ViewState extends ConsumerState<Step2View> {
|
|||
}
|
||||
});
|
||||
} catch (e, s) {
|
||||
Logging.instance
|
||||
.log("$e\n$s", level: LogLevel.Info);
|
||||
Logging.instance.e(
|
||||
"",
|
||||
error: e,
|
||||
stackTrace: s,
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
|
@ -543,9 +548,10 @@ class _Step2ViewState extends ConsumerState<Step2View> {
|
|||
});
|
||||
});
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
Logging.instance.i(
|
||||
"$e\n$s",
|
||||
level: LogLevel.Info,
|
||||
error: e,
|
||||
stackTrace: s,
|
||||
);
|
||||
}
|
||||
},
|
||||
|
|
|
@ -317,7 +317,7 @@ class _Step4ViewState extends ConsumerState<Step4View> {
|
|||
}
|
||||
}
|
||||
} catch (e, s) {
|
||||
Logging.instance.log("$e\n$s", level: LogLevel.Error);
|
||||
Logging.instance.e("$e\n$s", error: e, stackTrace: s);
|
||||
if (mounted && !wasCancelled) {
|
||||
// pop building dialog
|
||||
Navigator.of(context).pop();
|
||||
|
|
|
@ -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/intermediate/lib_monero_wallet.dart';
|
||||
import '../../wallets/wallet/intermediate/external_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 LibMoneroWallet) {
|
||||
if (wallet is ExternalWallet) {
|
||||
await wallet.init();
|
||||
await wallet.open();
|
||||
}
|
||||
|
@ -387,7 +387,7 @@ class _SendFromCardState extends ConsumerState<SendFromCard> {
|
|||
}
|
||||
}
|
||||
} catch (e, s) {
|
||||
Logging.instance.log("$e\n$s", level: LogLevel.Error);
|
||||
Logging.instance.e("$e\n$s", error: e, stackTrace: s);
|
||||
if (mounted) {
|
||||
// pop building dialog
|
||||
Navigator.of(context).pop();
|
||||
|
|
|
@ -173,9 +173,8 @@ class _ExchangeOptionState extends ConsumerState<ExchangeOption> {
|
|||
],
|
||||
);
|
||||
} else {
|
||||
Logging.instance.log(
|
||||
Logging.instance.w(
|
||||
"$runtimeType rate unavailable for ${widget.exchange.name}: $data",
|
||||
level: LogLevel.Warning,
|
||||
);
|
||||
|
||||
return Consumer(
|
||||
|
@ -315,7 +314,43 @@ class _ProviderOptionState extends ConsumerState<_ProviderOption> {
|
|||
child: SizedBox(
|
||||
width: isDesktop ? 32 : 24,
|
||||
height: isDesktop ? 32 : 24,
|
||||
child: SvgPicture.asset(
|
||||
child: widget.estimate?.exchangeProviderLogo != null &&
|
||||
widget
|
||||
.estimate!
|
||||
.exchangeProviderLogo!
|
||||
.isNotEmpty
|
||||
? ClipRRect(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
child: Image.network(
|
||||
widget.estimate!.exchangeProviderLogo!,
|
||||
loadingBuilder: (
|
||||
context,
|
||||
child,
|
||||
loadingProgress,
|
||||
) {
|
||||
if (loadingProgress == null) {
|
||||
return child;
|
||||
} else {
|
||||
return const Center(
|
||||
child:
|
||||
CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
},
|
||||
errorBuilder: (context, error, stackTrace) {
|
||||
return SvgPicture.asset(
|
||||
Assets.exchange.getIconFor(
|
||||
exchangeName: widget.exchange.name,
|
||||
),
|
||||
width: isDesktop ? 32 : 24,
|
||||
height: isDesktop ? 32 : 24,
|
||||
);
|
||||
},
|
||||
width: isDesktop ? 32 : 24,
|
||||
height: isDesktop ? 32 : 24,
|
||||
),
|
||||
)
|
||||
: SvgPicture.asset(
|
||||
Assets.exchange.getIconFor(
|
||||
exchangeName: widget.exchange.name,
|
||||
),
|
||||
|
|
257
lib/pages/namecoin_names/add_dns_record/add_dns_step_1.dart
Normal file
257
lib/pages/namecoin_names/add_dns_record/add_dns_step_1.dart
Normal file
|
@ -0,0 +1,257 @@
|
|||
import 'package:dropdown_button2/dropdown_button2.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
|
||||
import '../../../models/namecoin_dns/dns_record_type.dart';
|
||||
import '../../../route_generator.dart';
|
||||
import '../../../themes/stack_colors.dart';
|
||||
import '../../../utilities/assets.dart';
|
||||
import '../../../utilities/constants.dart';
|
||||
import '../../../utilities/text_styles.dart';
|
||||
import '../../../utilities/util.dart';
|
||||
import '../../../widgets/desktop/desktop_dialog.dart';
|
||||
import '../../../widgets/desktop/desktop_dialog_close_button.dart';
|
||||
import '../../../widgets/desktop/primary_button.dart';
|
||||
import '../../../widgets/desktop/secondary_button.dart';
|
||||
import '../../../widgets/stack_dialog.dart';
|
||||
import 'add_dns_step_2.dart';
|
||||
|
||||
class AddDnsStep1 extends StatefulWidget {
|
||||
const AddDnsStep1({super.key, required this.name});
|
||||
|
||||
final String name;
|
||||
|
||||
@override
|
||||
State<AddDnsStep1> createState() => _AddDnsStep1State();
|
||||
}
|
||||
|
||||
class _AddDnsStep1State extends State<AddDnsStep1> {
|
||||
DNSRecordType? _recordType;
|
||||
|
||||
bool _nextLock = false;
|
||||
void _next() {
|
||||
if (_nextLock) return;
|
||||
_nextLock = true;
|
||||
try {
|
||||
if (mounted) {
|
||||
Navigator.of(context).push(
|
||||
RouteGenerator.getRoute(
|
||||
builder: (context) {
|
||||
return Util.isDesktop
|
||||
? DesktopDialog(
|
||||
maxHeight: double.infinity,
|
||||
maxWidth: 580,
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 32,
|
||||
),
|
||||
child: Text(
|
||||
"Add DNS record",
|
||||
style: STextStyles.desktopH3(
|
||||
context,
|
||||
),
|
||||
),
|
||||
),
|
||||
const DesktopDialogCloseButton(),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 32,
|
||||
),
|
||||
child: AddDnsStep2(
|
||||
recordType: _recordType!,
|
||||
name: widget.name,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: StackDialogBase(
|
||||
keyboardPaddingAmount:
|
||||
MediaQuery.of(context).viewInsets.bottom,
|
||||
child: AddDnsStep2(
|
||||
recordType: _recordType!,
|
||||
name: widget.name,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
_nextLock = false;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (!Util.isDesktop)
|
||||
Text(
|
||||
"Add DNS record",
|
||||
style: STextStyles.pageTitleH2(context),
|
||||
),
|
||||
SizedBox(
|
||||
height: Util.isDesktop ? 24 : 16,
|
||||
),
|
||||
Text(
|
||||
"Choose a record type",
|
||||
style: Util.isDesktop
|
||||
? STextStyles.w500_12(context).copyWith(
|
||||
color: Theme.of(context).extension<StackColors>()!.textDark3,
|
||||
)
|
||||
: STextStyles.w500_14(context).copyWith(
|
||||
color: Theme.of(context).extension<StackColors>()!.textDark3,
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: Util.isDesktop ? 12 : 8,
|
||||
),
|
||||
DropdownButtonHideUnderline(
|
||||
child: DropdownButton2<DNSRecordType>(
|
||||
hint: Text(
|
||||
"Choose a record type",
|
||||
style: STextStyles.fieldLabel(context),
|
||||
),
|
||||
buttonStyleData: ButtonStyleData(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFieldDefaultBG,
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
),
|
||||
),
|
||||
dropdownStyleData: DropdownStyleData(
|
||||
offset: const Offset(0, -10),
|
||||
elevation: 0,
|
||||
maxHeight: Util.isDesktop ? null : 200,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFieldDefaultBG,
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
),
|
||||
),
|
||||
menuItemStyleData: const MenuItemStyleData(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 4,
|
||||
),
|
||||
),
|
||||
isExpanded: true,
|
||||
value: _recordType,
|
||||
onChanged: (value) {
|
||||
if (value is DNSRecordType && _recordType != value) {
|
||||
setState(() {
|
||||
_recordType = value;
|
||||
});
|
||||
}
|
||||
},
|
||||
iconStyleData: IconStyleData(
|
||||
icon: Padding(
|
||||
padding: const EdgeInsets.only(right: 10),
|
||||
child: SvgPicture.asset(
|
||||
Assets.svg.chevronDown,
|
||||
width: 10,
|
||||
height: 5,
|
||||
color: Theme.of(context).extension<StackColors>()!.textDark3,
|
||||
),
|
||||
),
|
||||
),
|
||||
items: [
|
||||
...DNSRecordType.values.map(
|
||||
(e) => DropdownMenuItem<DNSRecordType>(
|
||||
value: e,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: Text(
|
||||
e.name,
|
||||
style: STextStyles.desktopTextExtraExtraSmall(context)
|
||||
.copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textDark,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (_recordType != null)
|
||||
SizedBox(
|
||||
height: Util.isDesktop ? 10 : 6,
|
||||
),
|
||||
if (_recordType != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
_recordType!.info,
|
||||
style: Util.isDesktop
|
||||
? STextStyles.w500_10(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.infoItemLabel,
|
||||
)
|
||||
: STextStyles.w500_8(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.infoItemLabel,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: Util.isDesktop ? 24 : 16,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: SecondaryButton(
|
||||
label: "Cancel",
|
||||
buttonHeight: Util.isDesktop ? ButtonHeight.l : null,
|
||||
onPressed: () {
|
||||
Navigator.of(context, rootNavigator: true).pop();
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 16,
|
||||
),
|
||||
Expanded(
|
||||
child: PrimaryButton(
|
||||
label: "Next",
|
||||
enabled: _recordType != null,
|
||||
onPressed: _next,
|
||||
buttonHeight: Util.isDesktop ? ButtonHeight.l : null,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (Util.isDesktop)
|
||||
const SizedBox(
|
||||
height: 32,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
163
lib/pages/namecoin_names/add_dns_record/add_dns_step_2.dart
Normal file
163
lib/pages/namecoin_names/add_dns_record/add_dns_step_2.dart
Normal file
|
@ -0,0 +1,163 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../models/namecoin_dns/dns_record_type.dart';
|
||||
import '../../../utilities/logger.dart';
|
||||
import '../../../utilities/text_styles.dart';
|
||||
import '../../../utilities/util.dart';
|
||||
import '../../../widgets/desktop/primary_button.dart';
|
||||
import '../../../widgets/desktop/secondary_button.dart';
|
||||
import '../../../widgets/stack_dialog.dart';
|
||||
import 'name_form_interface.dart';
|
||||
import 'sub_widgets/a_form.dart';
|
||||
import 'sub_widgets/cname_form.dart';
|
||||
import 'sub_widgets/ds_form.dart';
|
||||
import 'sub_widgets/import_form.dart';
|
||||
import 'sub_widgets/ns_form.dart';
|
||||
import 'sub_widgets/srv_form.dart';
|
||||
import 'sub_widgets/ssh_form.dart';
|
||||
import 'sub_widgets/tls_form.dart';
|
||||
import 'sub_widgets/txt_form.dart';
|
||||
|
||||
class AddDnsStep2 extends StatefulWidget {
|
||||
const AddDnsStep2({
|
||||
super.key,
|
||||
required this.recordType,
|
||||
required this.name,
|
||||
});
|
||||
|
||||
final String name;
|
||||
final DNSRecordType recordType;
|
||||
|
||||
@override
|
||||
State<AddDnsStep2> createState() => _AddDnsStep2State();
|
||||
}
|
||||
|
||||
class _AddDnsStep2State extends State<AddDnsStep2> {
|
||||
final GlobalKey<NameFormState> _formStateKey = GlobalKey();
|
||||
|
||||
bool _nextLock = false;
|
||||
void _nextPressed() {
|
||||
if (_nextLock) return;
|
||||
_nextLock = true;
|
||||
try {
|
||||
final record = _formStateKey.currentState!.buildRecord();
|
||||
Navigator.of(context, rootNavigator: true).pop(
|
||||
record,
|
||||
);
|
||||
} catch (e, s) {
|
||||
Logging.instance.e(
|
||||
runtimeType,
|
||||
error: e,
|
||||
stackTrace: s,
|
||||
);
|
||||
|
||||
final String err;
|
||||
switch (e.runtimeType) {
|
||||
case const (ArgumentError):
|
||||
err = e.toString().replaceFirst(
|
||||
"Invalid Arguments(s): ",
|
||||
"",
|
||||
);
|
||||
|
||||
case const (Exception):
|
||||
err = e.toString().replaceFirst(
|
||||
"Exception: ",
|
||||
"",
|
||||
);
|
||||
|
||||
default:
|
||||
err = e.toString();
|
||||
}
|
||||
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
useRootNavigator: true,
|
||||
builder: (context) {
|
||||
return StackOkDialog(
|
||||
desktopPopRootNavigator: true, // mobile as well due to sub nav flow
|
||||
title: "Error",
|
||||
maxWidth: 500,
|
||||
message: err,
|
||||
);
|
||||
},
|
||||
);
|
||||
} finally {
|
||||
_nextLock = false;
|
||||
}
|
||||
}
|
||||
|
||||
NameFormStatefulWidget? _form;
|
||||
NameFormStatefulWidget get form => _form ??= _buildForm();
|
||||
|
||||
NameFormStatefulWidget _buildForm() {
|
||||
switch (widget.recordType) {
|
||||
case DNSRecordType.A:
|
||||
return AForm(key: _formStateKey, name: widget.name);
|
||||
case DNSRecordType.CNAME:
|
||||
return CNAMEForm(key: _formStateKey, name: widget.name);
|
||||
case DNSRecordType.NS:
|
||||
return NSForm(key: _formStateKey, name: widget.name);
|
||||
case DNSRecordType.DS:
|
||||
return DSForm(key: _formStateKey, name: widget.name);
|
||||
case DNSRecordType.TLS:
|
||||
return TLSForm(key: _formStateKey, name: widget.name);
|
||||
case DNSRecordType.SRV:
|
||||
return SRVForm(key: _formStateKey, name: widget.name);
|
||||
case DNSRecordType.TXT:
|
||||
return TXTForm(key: _formStateKey, name: widget.name);
|
||||
case DNSRecordType.IMPORT:
|
||||
return IMPORTForm(key: _formStateKey, name: widget.name);
|
||||
case DNSRecordType.SSH:
|
||||
return SSHForm(key: _formStateKey, name: widget.name);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (!Util.isDesktop)
|
||||
Text(
|
||||
"Add DNS record",
|
||||
style: STextStyles.pageTitleH2(context),
|
||||
),
|
||||
SizedBox(
|
||||
height: Util.isDesktop ? 24 : 16,
|
||||
),
|
||||
form,
|
||||
SizedBox(
|
||||
height: Util.isDesktop ? 24 : 16,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: SecondaryButton(
|
||||
label: "Cancel",
|
||||
buttonHeight: Util.isDesktop ? ButtonHeight.l : null,
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 16,
|
||||
),
|
||||
Expanded(
|
||||
child: PrimaryButton(
|
||||
label: "Next",
|
||||
onPressed: _nextPressed,
|
||||
buttonHeight: Util.isDesktop ? ButtonHeight.l : null,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (Util.isDesktop)
|
||||
const SizedBox(
|
||||
height: 32,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../models/namecoin_dns/dns_record.dart';
|
||||
import '../../../themes/stack_colors.dart';
|
||||
import '../../../utilities/constants.dart';
|
||||
import '../../../utilities/text_styles.dart';
|
||||
import '../../../utilities/util.dart';
|
||||
|
||||
abstract class NameFormStatefulWidget extends StatefulWidget {
|
||||
const NameFormStatefulWidget({super.key, required this.name});
|
||||
|
||||
final String name;
|
||||
}
|
||||
|
||||
abstract class NameFormState<T extends NameFormStatefulWidget>
|
||||
extends State<T> {
|
||||
DNSRecord buildRecord();
|
||||
}
|
||||
|
||||
class DNSFieldText extends StatelessWidget {
|
||||
const DNSFieldText(this.text, {super.key});
|
||||
|
||||
final String text;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Text(
|
||||
text,
|
||||
style: Util.isDesktop
|
||||
? STextStyles.w500_12(context).copyWith(
|
||||
color: Theme.of(context).extension<StackColors>()!.textDark3,
|
||||
)
|
||||
: STextStyles.w500_14(context).copyWith(
|
||||
color: Theme.of(context).extension<StackColors>()!.textDark3,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DNSFormField extends StatelessWidget {
|
||||
const DNSFormField({super.key, required this.controller, this.keyboardType});
|
||||
|
||||
final TextEditingController controller;
|
||||
final TextInputType? keyboardType;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
child: TextField(
|
||||
controller: controller,
|
||||
textAlignVertical: TextAlignVertical.center,
|
||||
keyboardType: keyboardType,
|
||||
decoration: InputDecoration(
|
||||
isDense: true,
|
||||
contentPadding: const EdgeInsets.all(16),
|
||||
hintStyle: STextStyles.fieldLabel(context),
|
||||
border: InputBorder.none,
|
||||
enabledBorder: InputBorder.none,
|
||||
focusedBorder: InputBorder.none,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
239
lib/pages/namecoin_names/add_dns_record/sub_widgets/a_form.dart
Normal file
239
lib/pages/namecoin_names/add_dns_record/sub_widgets/a_form.dart
Normal file
|
@ -0,0 +1,239 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:dropdown_button2/dropdown_button2.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
|
||||
import '../../../../models/namecoin_dns/dns_a_record_address_type.dart';
|
||||
import '../../../../models/namecoin_dns/dns_record.dart';
|
||||
import '../../../../models/namecoin_dns/dns_record_type.dart';
|
||||
import '../../../../themes/stack_colors.dart';
|
||||
import '../../../../utilities/assets.dart';
|
||||
import '../../../../utilities/constants.dart';
|
||||
import '../../../../utilities/text_styles.dart';
|
||||
import '../../../../utilities/util.dart';
|
||||
import '../name_form_interface.dart';
|
||||
|
||||
class AForm extends NameFormStatefulWidget {
|
||||
const AForm({super.key, required super.name});
|
||||
|
||||
@override
|
||||
NameFormState<AForm> createState() => _AFormState();
|
||||
}
|
||||
|
||||
class _AFormState extends NameFormState<AForm> {
|
||||
final _addressDataController = TextEditingController();
|
||||
final _addressDataFieldFocus = FocusNode();
|
||||
|
||||
DNSAddressType _addressType = DNSAddressType.IPv4;
|
||||
|
||||
@override
|
||||
DNSRecord buildRecord() {
|
||||
final parts = _addressDataController.text.split(",").map((e) => e.trim());
|
||||
|
||||
final List<String> addresses = [];
|
||||
|
||||
for (final part in parts) {
|
||||
switch (_addressType) {
|
||||
case DNSAddressType.IPv4:
|
||||
final address =
|
||||
InternetAddress(part.trim(), type: InternetAddressType.IPv4);
|
||||
addresses.add(address.address);
|
||||
break;
|
||||
|
||||
case DNSAddressType.IPv6:
|
||||
final address = InternetAddress(part, type: InternetAddressType.IPv6);
|
||||
addresses.add(address.address);
|
||||
break;
|
||||
|
||||
case DNSAddressType.Tor:
|
||||
final regex = RegExp(r'^[a-z2-7]{56}\.onion$');
|
||||
if (regex.hasMatch(part)) {
|
||||
addresses.add(part);
|
||||
} else {
|
||||
throw Exception("Invalid tor address: $part");
|
||||
}
|
||||
|
||||
case DNSAddressType.Freenet:
|
||||
// TODO: verify
|
||||
final regex = RegExp(r'(CHK|SSK|USK)@[a-zA-Z0-9~-]{43,}/?');
|
||||
final kskRegex = RegExp(r'KSK@[\w\-.~]+');
|
||||
if (regex.hasMatch(part) || kskRegex.hasMatch(part)) {
|
||||
addresses.add(part);
|
||||
} else {
|
||||
throw Exception("Invalid freenet address: $part");
|
||||
}
|
||||
|
||||
case DNSAddressType.I2P:
|
||||
// TODO: verify
|
||||
final b32Regex = RegExp(r'^[a-z2-7]{52}\.b32\.i2p$');
|
||||
final b64Regex = RegExp(r'^[A-Za-z0-9+/=]{516,}$');
|
||||
if (b32Regex.hasMatch(part) || b64Regex.hasMatch(part)) {
|
||||
addresses.add(part);
|
||||
} else {
|
||||
throw Exception("Invalid i2p address: $part");
|
||||
}
|
||||
|
||||
case DNSAddressType.ZeroNet:
|
||||
// TODO: verify
|
||||
final regex = RegExp(r'^[13][a-km-zA-HJ-NP-Z1-9]{32,33}$');
|
||||
if (regex.hasMatch(part)) {
|
||||
addresses.add(part);
|
||||
} else {
|
||||
throw Exception("Invalid zeronet address: $part");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final Map<String, dynamic> map;
|
||||
|
||||
if (_addressType == DNSAddressType.Tor) {
|
||||
map = {
|
||||
"map": {
|
||||
"_tor": {
|
||||
"txt": addresses,
|
||||
},
|
||||
},
|
||||
};
|
||||
} else {
|
||||
map = {
|
||||
_addressType!.key: addresses,
|
||||
};
|
||||
}
|
||||
|
||||
return DNSRecord(
|
||||
name: widget.name,
|
||||
type: DNSRecordType.A,
|
||||
data: map,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_addressDataController.dispose();
|
||||
_addressDataFieldFocus.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const DNSFieldText(
|
||||
"Address type",
|
||||
),
|
||||
SizedBox(
|
||||
height: Util.isDesktop ? 10 : 8,
|
||||
),
|
||||
DropdownButtonHideUnderline(
|
||||
child: DropdownButton2<DNSAddressType>(
|
||||
hint: Text(
|
||||
"Choose address type",
|
||||
style: STextStyles.fieldLabel(context),
|
||||
),
|
||||
dropdownStyleData: DropdownStyleData(
|
||||
offset: const Offset(0, -10),
|
||||
elevation: 0,
|
||||
maxHeight: Util.isDesktop ? null : 200,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFieldDefaultBG,
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
),
|
||||
),
|
||||
menuItemStyleData: const MenuItemStyleData(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 4,
|
||||
),
|
||||
),
|
||||
isExpanded: true,
|
||||
value: _addressType,
|
||||
onChanged: (value) {
|
||||
if (value is DNSAddressType && _addressType != value) {
|
||||
setState(() {
|
||||
_addressType = value;
|
||||
});
|
||||
}
|
||||
},
|
||||
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: 10,
|
||||
height: 5,
|
||||
color: Theme.of(context).extension<StackColors>()!.textDark3,
|
||||
),
|
||||
),
|
||||
),
|
||||
items: [
|
||||
...DNSAddressType.values.map(
|
||||
(e) => DropdownMenuItem<DNSAddressType>(
|
||||
value: e,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: Text(
|
||||
e.name,
|
||||
style: STextStyles.desktopTextExtraExtraSmall(context)
|
||||
.copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textDark,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: Util.isDesktop ? 24 : 16,
|
||||
),
|
||||
const DNSFieldText(
|
||||
"Value",
|
||||
),
|
||||
SizedBox(
|
||||
height: Util.isDesktop ? 10 : 8,
|
||||
),
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
child: TextField(
|
||||
focusNode: _addressDataFieldFocus,
|
||||
controller: _addressDataController,
|
||||
textAlignVertical: TextAlignVertical.center,
|
||||
maxLines: 3,
|
||||
decoration: InputDecoration(
|
||||
isDense: true,
|
||||
contentPadding: const EdgeInsets.all(16),
|
||||
hintText: "e.g. 255.255.255.255, "
|
||||
"76f4a520a262c269dcba66bc1f560452e30a44e14ce6b37ce20b8.onion",
|
||||
hintStyle: STextStyles.fieldLabel(context),
|
||||
border: InputBorder.none,
|
||||
enabledBorder: InputBorder.none,
|
||||
focusedBorder: InputBorder.none,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../models/namecoin_dns/dns_record.dart';
|
||||
import '../../../../models/namecoin_dns/dns_record_type.dart';
|
||||
import '../../../../utilities/util.dart';
|
||||
import '../name_form_interface.dart';
|
||||
|
||||
class CNAMEForm extends NameFormStatefulWidget {
|
||||
const CNAMEForm({super.key, required super.name});
|
||||
|
||||
@override
|
||||
NameFormState<CNAMEForm> createState() => _CNAMEFormState();
|
||||
}
|
||||
|
||||
class _CNAMEFormState extends NameFormState<CNAMEForm> {
|
||||
final _aliasController = TextEditingController();
|
||||
|
||||
@override
|
||||
DNSRecord buildRecord() {
|
||||
final address = _aliasController.text.trim();
|
||||
|
||||
return DNSRecord(
|
||||
name: widget.name,
|
||||
type: DNSRecordType.CNAME,
|
||||
data: {"alias": address},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_aliasController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const DNSFieldText(
|
||||
"Alias of",
|
||||
),
|
||||
SizedBox(
|
||||
height: Util.isDesktop ? 10 : 8,
|
||||
),
|
||||
DNSFormField(
|
||||
controller: _aliasController,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
105
lib/pages/namecoin_names/add_dns_record/sub_widgets/ds_form.dart
Normal file
105
lib/pages/namecoin_names/add_dns_record/sub_widgets/ds_form.dart
Normal file
|
@ -0,0 +1,105 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../models/namecoin_dns/dns_record.dart';
|
||||
import '../../../../models/namecoin_dns/dns_record_type.dart';
|
||||
import '../../../../utilities/util.dart';
|
||||
import '../name_form_interface.dart';
|
||||
|
||||
class DSForm extends NameFormStatefulWidget {
|
||||
const DSForm({super.key, required super.name});
|
||||
|
||||
@override
|
||||
NameFormState<DSForm> createState() => _DSFormState();
|
||||
}
|
||||
|
||||
class _DSFormState extends NameFormState<DSForm> {
|
||||
final _keytagController = TextEditingController();
|
||||
final _algoController = TextEditingController();
|
||||
final _typeController = TextEditingController();
|
||||
final _hashController = TextEditingController();
|
||||
|
||||
@override
|
||||
DNSRecord buildRecord() {
|
||||
return DNSRecord(
|
||||
name: widget.name,
|
||||
type: DNSRecordType.DS,
|
||||
data: {
|
||||
"ds": [
|
||||
[
|
||||
int.parse(_keytagController.text.trim()),
|
||||
int.parse(_algoController.text.trim()),
|
||||
int.parse(_typeController.text.trim()),
|
||||
_hashController.text.trim(),
|
||||
],
|
||||
],
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_keytagController.dispose();
|
||||
_algoController.dispose();
|
||||
_typeController.dispose();
|
||||
_hashController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const DNSFieldText(
|
||||
"Keytag",
|
||||
),
|
||||
SizedBox(
|
||||
height: Util.isDesktop ? 10 : 8,
|
||||
),
|
||||
DNSFormField(
|
||||
controller: _keytagController,
|
||||
keyboardType: TextInputType.number,
|
||||
),
|
||||
SizedBox(
|
||||
height: Util.isDesktop ? 24 : 16,
|
||||
),
|
||||
const DNSFieldText(
|
||||
"Algorithm",
|
||||
),
|
||||
SizedBox(
|
||||
height: Util.isDesktop ? 10 : 8,
|
||||
),
|
||||
DNSFormField(
|
||||
controller: _algoController,
|
||||
keyboardType: TextInputType.number,
|
||||
),
|
||||
SizedBox(
|
||||
height: Util.isDesktop ? 24 : 16,
|
||||
),
|
||||
const DNSFieldText(
|
||||
"Hash type",
|
||||
),
|
||||
SizedBox(
|
||||
height: Util.isDesktop ? 10 : 8,
|
||||
),
|
||||
DNSFormField(
|
||||
controller: _typeController,
|
||||
keyboardType: TextInputType.number,
|
||||
),
|
||||
SizedBox(
|
||||
height: Util.isDesktop ? 24 : 16,
|
||||
),
|
||||
const DNSFieldText(
|
||||
"Hash (base64)",
|
||||
),
|
||||
SizedBox(
|
||||
height: Util.isDesktop ? 10 : 8,
|
||||
),
|
||||
DNSFormField(
|
||||
controller: _hashController,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../models/namecoin_dns/dns_record.dart';
|
||||
import '../../../../models/namecoin_dns/dns_record_type.dart';
|
||||
import '../../../../utilities/util.dart';
|
||||
import '../name_form_interface.dart';
|
||||
|
||||
class IMPORTForm extends NameFormStatefulWidget {
|
||||
const IMPORTForm({super.key, required super.name});
|
||||
|
||||
@override
|
||||
NameFormState<IMPORTForm> createState() => _IMPORTFormState();
|
||||
}
|
||||
|
||||
class _IMPORTFormState extends NameFormState<IMPORTForm> {
|
||||
final _nameController = TextEditingController();
|
||||
final _subdomainController = TextEditingController();
|
||||
|
||||
@override
|
||||
DNSRecord buildRecord() {
|
||||
return DNSRecord(
|
||||
name: widget.name,
|
||||
type: DNSRecordType.IMPORT,
|
||||
data: {
|
||||
"import": [
|
||||
[
|
||||
_nameController.text.trim(),
|
||||
if (_subdomainController.text.trim().isNotEmpty)
|
||||
_subdomainController.text.trim(),
|
||||
],
|
||||
],
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_nameController.dispose();
|
||||
_subdomainController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const DNSFieldText(
|
||||
"Namecoin name",
|
||||
),
|
||||
SizedBox(
|
||||
height: Util.isDesktop ? 10 : 8,
|
||||
),
|
||||
DNSFormField(
|
||||
controller: _nameController,
|
||||
),
|
||||
const DNSFieldText(
|
||||
"Subdomain (optional)",
|
||||
),
|
||||
SizedBox(
|
||||
height: Util.isDesktop ? 10 : 8,
|
||||
),
|
||||
DNSFormField(
|
||||
controller: _subdomainController,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../models/namecoin_dns/dns_record.dart';
|
||||
import '../../../../models/namecoin_dns/dns_record_type.dart';
|
||||
import '../../../../utilities/util.dart';
|
||||
import '../name_form_interface.dart';
|
||||
|
||||
class NSForm extends NameFormStatefulWidget {
|
||||
const NSForm({super.key, required super.name});
|
||||
|
||||
@override
|
||||
NameFormState<NSForm> createState() => _NSFormState();
|
||||
}
|
||||
|
||||
class _NSFormState extends NameFormState<NSForm> {
|
||||
final _serverController = TextEditingController();
|
||||
|
||||
@override
|
||||
DNSRecord buildRecord() {
|
||||
final address = _serverController.text.trim();
|
||||
|
||||
return DNSRecord(
|
||||
name: widget.name,
|
||||
type: DNSRecordType.NS,
|
||||
data: {
|
||||
"ns": [address],
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_serverController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const DNSFieldText(
|
||||
"Nameserver",
|
||||
),
|
||||
SizedBox(
|
||||
height: Util.isDesktop ? 10 : 8,
|
||||
),
|
||||
DNSFormField(
|
||||
controller: _serverController,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../models/namecoin_dns/dns_record.dart';
|
||||
import '../../../../models/namecoin_dns/dns_record_type.dart';
|
||||
import '../../../../utilities/util.dart';
|
||||
import '../name_form_interface.dart';
|
||||
|
||||
class SRVForm extends NameFormStatefulWidget {
|
||||
const SRVForm({super.key, required super.name});
|
||||
|
||||
@override
|
||||
NameFormState<SRVForm> createState() => _SRVFormState();
|
||||
}
|
||||
|
||||
class _SRVFormState extends NameFormState<SRVForm> {
|
||||
final _priorityController = TextEditingController();
|
||||
final _weightController = TextEditingController();
|
||||
final _portController = TextEditingController();
|
||||
final _hostController = TextEditingController();
|
||||
|
||||
@override
|
||||
DNSRecord buildRecord() {
|
||||
return DNSRecord(
|
||||
name: widget.name,
|
||||
type: DNSRecordType.SRV,
|
||||
data: {
|
||||
"srv": [
|
||||
[
|
||||
int.parse(_priorityController.text.trim()),
|
||||
int.parse(_weightController.text.trim()),
|
||||
int.parse(_portController.text.trim()),
|
||||
_hostController.text.trim(),
|
||||
],
|
||||
],
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_priorityController.dispose();
|
||||
_weightController.dispose();
|
||||
_portController.dispose();
|
||||
_hostController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const DNSFieldText(
|
||||
"Priority",
|
||||
),
|
||||
SizedBox(
|
||||
height: Util.isDesktop ? 10 : 8,
|
||||
),
|
||||
DNSFormField(
|
||||
controller: _priorityController,
|
||||
keyboardType: TextInputType.number,
|
||||
),
|
||||
SizedBox(
|
||||
height: Util.isDesktop ? 24 : 16,
|
||||
),
|
||||
const DNSFieldText(
|
||||
"Weight",
|
||||
),
|
||||
SizedBox(
|
||||
height: Util.isDesktop ? 10 : 8,
|
||||
),
|
||||
DNSFormField(
|
||||
controller: _weightController,
|
||||
keyboardType: TextInputType.number,
|
||||
),
|
||||
SizedBox(
|
||||
height: Util.isDesktop ? 24 : 16,
|
||||
),
|
||||
const DNSFieldText(
|
||||
"Port",
|
||||
),
|
||||
SizedBox(
|
||||
height: Util.isDesktop ? 10 : 8,
|
||||
),
|
||||
DNSFormField(
|
||||
controller: _portController,
|
||||
keyboardType: TextInputType.number,
|
||||
),
|
||||
SizedBox(
|
||||
height: Util.isDesktop ? 24 : 16,
|
||||
),
|
||||
const DNSFieldText(
|
||||
"Host",
|
||||
),
|
||||
SizedBox(
|
||||
height: Util.isDesktop ? 10 : 8,
|
||||
),
|
||||
DNSFormField(
|
||||
controller: _hostController,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../models/namecoin_dns/dns_record.dart';
|
||||
import '../../../../models/namecoin_dns/dns_record_type.dart';
|
||||
import '../../../../utilities/util.dart';
|
||||
import '../name_form_interface.dart';
|
||||
|
||||
class SSHForm extends NameFormStatefulWidget {
|
||||
const SSHForm({super.key, required super.name});
|
||||
|
||||
@override
|
||||
NameFormState<SSHForm> createState() => _SSHFormState();
|
||||
}
|
||||
|
||||
class _SSHFormState extends NameFormState<SSHForm> {
|
||||
final _algoController = TextEditingController();
|
||||
final _fingerprintTypeController = TextEditingController();
|
||||
final _fingerprintController = TextEditingController();
|
||||
|
||||
@override
|
||||
DNSRecord buildRecord() {
|
||||
return DNSRecord(
|
||||
name: widget.name,
|
||||
type: DNSRecordType.SSH,
|
||||
data: {
|
||||
"sshfp": [
|
||||
[
|
||||
int.parse(_algoController.text.trim()),
|
||||
int.parse(_fingerprintTypeController.text.trim()),
|
||||
_fingerprintController.text.trim(),
|
||||
],
|
||||
],
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_algoController.dispose();
|
||||
_fingerprintTypeController.dispose();
|
||||
_fingerprintController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const DNSFieldText(
|
||||
"Algorithm",
|
||||
),
|
||||
SizedBox(
|
||||
height: Util.isDesktop ? 10 : 8,
|
||||
),
|
||||
DNSFormField(
|
||||
controller: _algoController,
|
||||
keyboardType: TextInputType.number,
|
||||
),
|
||||
SizedBox(
|
||||
height: Util.isDesktop ? 24 : 16,
|
||||
),
|
||||
const DNSFieldText(
|
||||
"Fingerprint type",
|
||||
),
|
||||
SizedBox(
|
||||
height: Util.isDesktop ? 10 : 8,
|
||||
),
|
||||
DNSFormField(
|
||||
controller: _fingerprintTypeController,
|
||||
keyboardType: TextInputType.number,
|
||||
),
|
||||
SizedBox(
|
||||
height: Util.isDesktop ? 24 : 16,
|
||||
),
|
||||
const DNSFieldText(
|
||||
"Fingerprint (base64)",
|
||||
),
|
||||
SizedBox(
|
||||
height: Util.isDesktop ? 10 : 8,
|
||||
),
|
||||
DNSFormField(
|
||||
controller: _fingerprintController,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../models/namecoin_dns/dns_record.dart';
|
||||
import '../../../../models/namecoin_dns/dns_record_type.dart';
|
||||
import '../../../../utilities/util.dart';
|
||||
import '../name_form_interface.dart';
|
||||
|
||||
class TLSForm extends NameFormStatefulWidget {
|
||||
const TLSForm({super.key, required super.name});
|
||||
|
||||
@override
|
||||
NameFormState<TLSForm> createState() => _TLSFormState();
|
||||
}
|
||||
|
||||
class _TLSFormState extends NameFormState<TLSForm> {
|
||||
final _pubkeyController = TextEditingController();
|
||||
|
||||
@override
|
||||
DNSRecord buildRecord() {
|
||||
return DNSRecord(
|
||||
name: widget.name,
|
||||
type: DNSRecordType.TLS,
|
||||
data: {
|
||||
"map": {
|
||||
"*": {
|
||||
"tls": [
|
||||
[
|
||||
2,
|
||||
1,
|
||||
0,
|
||||
_pubkeyController.text.trim(),
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_pubkeyController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const DNSFieldText(
|
||||
"DANE-TA public key (base64)",
|
||||
),
|
||||
SizedBox(
|
||||
height: Util.isDesktop ? 10 : 8,
|
||||
),
|
||||
DNSFormField(
|
||||
controller: _pubkeyController,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../models/namecoin_dns/dns_record.dart';
|
||||
import '../../../../models/namecoin_dns/dns_record_type.dart';
|
||||
import '../../../../utilities/util.dart';
|
||||
import '../name_form_interface.dart';
|
||||
|
||||
class TXTForm extends NameFormStatefulWidget {
|
||||
const TXTForm({super.key, required super.name});
|
||||
|
||||
@override
|
||||
NameFormState<TXTForm> createState() => _TXTFormState();
|
||||
}
|
||||
|
||||
class _TXTFormState extends NameFormState<TXTForm> {
|
||||
final _valueController = TextEditingController();
|
||||
|
||||
@override
|
||||
DNSRecord buildRecord() {
|
||||
return DNSRecord(
|
||||
name: widget.name,
|
||||
type: DNSRecordType.TXT,
|
||||
data: {
|
||||
"txt": [_valueController.text.trim()],
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_valueController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const DNSFieldText(
|
||||
"Value",
|
||||
),
|
||||
SizedBox(
|
||||
height: Util.isDesktop ? 10 : 8,
|
||||
),
|
||||
DNSFormField(
|
||||
controller: _valueController,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
541
lib/pages/namecoin_names/buy_domain_view.dart
Normal file
541
lib/pages/namecoin_names/buy_domain_view.dart
Normal file
|
@ -0,0 +1,541 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:namecoin/namecoin.dart';
|
||||
|
||||
import '../../../providers/providers.dart';
|
||||
import '../../../utilities/amount/amount.dart';
|
||||
import '../../../utilities/logger.dart';
|
||||
import '../../../utilities/util.dart';
|
||||
import '../../../wallets/models/name_op_state.dart';
|
||||
import '../../../wallets/models/tx_data.dart';
|
||||
import '../../../wallets/wallet/impl/namecoin_wallet.dart';
|
||||
import '../../../widgets/desktop/primary_button.dart';
|
||||
import '../../../widgets/desktop/secondary_button.dart';
|
||||
import '../../../widgets/stack_dialog.dart';
|
||||
import '../../models/namecoin_dns/dns_a_record_address_type.dart';
|
||||
import '../../models/namecoin_dns/dns_record.dart';
|
||||
import '../../models/namecoin_dns/dns_record_type.dart';
|
||||
import '../../route_generator.dart';
|
||||
import '../../themes/stack_colors.dart';
|
||||
import '../../utilities/amount/amount_formatter.dart';
|
||||
import '../../utilities/show_loading.dart';
|
||||
import '../../utilities/text_styles.dart';
|
||||
import '../../wallets/isar/providers/wallet_info_provider.dart';
|
||||
import '../../widgets/background.dart';
|
||||
import '../../widgets/conditional_parent.dart';
|
||||
import '../../widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
import '../../widgets/custom_buttons/blue_text_button.dart';
|
||||
import '../../widgets/desktop/desktop_dialog_close_button.dart';
|
||||
import '../../widgets/dialogs/s_dialog.dart';
|
||||
import '../../widgets/rounded_white_container.dart';
|
||||
import 'add_dns_record/add_dns_step_1.dart';
|
||||
import 'confirm_name_transaction_view.dart';
|
||||
|
||||
class BuyDomainView extends ConsumerStatefulWidget {
|
||||
const BuyDomainView({
|
||||
super.key,
|
||||
required this.walletId,
|
||||
required this.domainName,
|
||||
});
|
||||
|
||||
final String walletId;
|
||||
final String domainName;
|
||||
|
||||
static const routeName = "/buyDomainView";
|
||||
|
||||
@override
|
||||
ConsumerState<BuyDomainView> createState() => _BuyDomainWidgetState();
|
||||
}
|
||||
|
||||
class _BuyDomainWidgetState extends ConsumerState<BuyDomainView> {
|
||||
bool _settingsHidden = true;
|
||||
final List<DNSRecord> _dnsRecords = [];
|
||||
|
||||
String _getFormattedDNSRecords() {
|
||||
if (_dnsRecords.isEmpty) return "";
|
||||
|
||||
return DNSRecord.merge(_dnsRecords);
|
||||
}
|
||||
|
||||
String _getNameFormattedForInternal() {
|
||||
String formattedName = widget.domainName;
|
||||
if (!formattedName.startsWith("d/")) {
|
||||
formattedName = "d/$formattedName";
|
||||
}
|
||||
if (formattedName.endsWith(".bit")) {
|
||||
formattedName.split(".bit").first;
|
||||
}
|
||||
return formattedName;
|
||||
}
|
||||
|
||||
Future<TxData> _preRegFuture() async {
|
||||
final wallet =
|
||||
ref.read(pWallets).getWallet(widget.walletId) as NamecoinWallet;
|
||||
final myAddress = await wallet.getCurrentReceivingAddress();
|
||||
if (myAddress == null) {
|
||||
throw Exception("No receiving address found");
|
||||
}
|
||||
|
||||
final value = _getFormattedDNSRecords();
|
||||
|
||||
Logging.instance.t("Formatted namecoin name value: $value");
|
||||
|
||||
// get address private key for deterministic salt
|
||||
final pk = await wallet.getPrivateKey(myAddress);
|
||||
|
||||
final formattedName = _getNameFormattedForInternal();
|
||||
|
||||
final data = await compute(_computeScriptNameNew, (formattedName, pk.data));
|
||||
|
||||
TxData txData = TxData(
|
||||
opNameState: NameOpState(
|
||||
name: formattedName,
|
||||
saltHex: data.$2,
|
||||
commitment: data.$3,
|
||||
value: value,
|
||||
nameScriptHex: data.$1,
|
||||
type: OpName.nameNew,
|
||||
outputPosition: -1, //currently unknown, updated later
|
||||
),
|
||||
note: "Reserve ${widget.domainName.substring(2)}.bit",
|
||||
feeRateType: kNameTxDefaultFeeRate, // TODO: make configurable?
|
||||
recipients: [
|
||||
(
|
||||
address: myAddress.value,
|
||||
isChange: false,
|
||||
amount: Amount(
|
||||
rawValue: BigInt.from(kNameNewAmountSats),
|
||||
fractionDigits: wallet.cryptoCurrency.fractionDigits,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
txData = await wallet.prepareNameSend(txData: txData);
|
||||
return txData;
|
||||
}
|
||||
|
||||
bool _preRegLock = false;
|
||||
Future<void> _preRegister() async {
|
||||
if (_preRegLock) return;
|
||||
_preRegLock = true;
|
||||
try {
|
||||
final txData = (await showLoading(
|
||||
whileFuture: _preRegFuture(),
|
||||
context: context,
|
||||
message: "Preparing transaction...",
|
||||
onException: (e) {
|
||||
throw e;
|
||||
},
|
||||
))!;
|
||||
|
||||
if (mounted) {
|
||||
if (Util.isDesktop) {
|
||||
await showDialog<void>(
|
||||
context: context,
|
||||
builder: (context) => SDialog(
|
||||
child: SizedBox(
|
||||
width: 580,
|
||||
child: ConfirmNameTransactionView(
|
||||
txData: txData,
|
||||
walletId: widget.walletId,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
await Navigator.of(context).pushNamed(
|
||||
ConfirmNameTransactionView.routeName,
|
||||
arguments: (txData, widget.walletId),
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (e, s) {
|
||||
Logging.instance.e("_preRegister failed", error: e, stackTrace: s);
|
||||
|
||||
if (mounted) {
|
||||
String err = e.toString();
|
||||
if (err.startsWith("Exception: ")) {
|
||||
err = err.replaceFirst("Exception: ", "");
|
||||
}
|
||||
|
||||
await showDialog<void>(
|
||||
context: context,
|
||||
builder: (_) => StackOkDialog(
|
||||
title: "Error",
|
||||
message: err,
|
||||
desktopPopRootNavigator: Util.isDesktop,
|
||||
maxWidth: Util.isDesktop ? 600 : null,
|
||||
),
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
_preRegLock = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool _addLock = false;
|
||||
Future<void> _addRecord() async {
|
||||
if (_addLock) return;
|
||||
_addLock = true;
|
||||
try {
|
||||
final value = await showDialog<DNSRecord?>(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (context) {
|
||||
return Navigator(
|
||||
onGenerateRoute: (settings) {
|
||||
return RouteGenerator.getRoute(
|
||||
builder: (context) {
|
||||
return Util.isDesktop
|
||||
? SDialog(
|
||||
child: SizedBox(
|
||||
width: 580,
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 32,
|
||||
),
|
||||
child: Text(
|
||||
"Add DNS record",
|
||||
style: STextStyles.desktopH3(context),
|
||||
),
|
||||
),
|
||||
DesktopDialogCloseButton(
|
||||
onPressedOverride: () {
|
||||
Navigator.of(
|
||||
context,
|
||||
rootNavigator: true,
|
||||
).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 32,
|
||||
),
|
||||
child: AddDnsStep1(
|
||||
name: _getNameFormattedForInternal(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
: StackDialogBase(
|
||||
child: AddDnsStep1(
|
||||
name: _getNameFormattedForInternal(),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
if (mounted && value != null) {
|
||||
setState(() {
|
||||
_dnsRecords.add(value);
|
||||
});
|
||||
}
|
||||
} catch (e, s) {
|
||||
Logging.instance.e("Add DNS record failed", error: e, stackTrace: s);
|
||||
|
||||
if (mounted) {
|
||||
await showDialog<void>(
|
||||
context: context,
|
||||
builder: (_) => StackOkDialog(
|
||||
title: "Add DNS record failed",
|
||||
desktopPopRootNavigator: Util.isDesktop,
|
||||
maxWidth: Util.isDesktop ? 600 : null,
|
||||
),
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
_addLock = false;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final coin = ref.watch(pWalletCoin(widget.walletId));
|
||||
return ConditionalParent(
|
||||
condition: !Util.isDesktop,
|
||||
builder: (child) {
|
||||
return Background(
|
||||
child: Scaffold(
|
||||
backgroundColor: Colors.transparent,
|
||||
appBar: AppBar(
|
||||
leading: const AppBarBackButton(),
|
||||
titleSpacing: 0,
|
||||
title: Text(
|
||||
"Buy domain",
|
||||
style: STextStyles.navBarTitle(context),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
body: SafeArea(
|
||||
child: LayoutBuilder(
|
||||
builder: (ctx, constraints) {
|
||||
return SingleChildScrollView(
|
||||
child: ConstrainedBox(
|
||||
constraints:
|
||||
BoxConstraints(minHeight: constraints.maxHeight),
|
||||
child: IntrinsicHeight(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Column(
|
||||
crossAxisAlignment: Util.isDesktop
|
||||
? CrossAxisAlignment.start
|
||||
: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
if (!Util.isDesktop)
|
||||
Text(
|
||||
"Buy domain",
|
||||
style: Util.isDesktop
|
||||
? STextStyles.desktopH3(context)
|
||||
: STextStyles.pageTitleH2(context),
|
||||
),
|
||||
SizedBox(
|
||||
height: Util.isDesktop ? 24 : 16,
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: Util.isDesktop
|
||||
? MainAxisAlignment.center
|
||||
: MainAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Name registration will take approximately 2 to 4 hours.",
|
||||
style: Util.isDesktop
|
||||
? STextStyles.w500_14(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textDark3,
|
||||
)
|
||||
: STextStyles.w500_12(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textDark3,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(
|
||||
height: Util.isDesktop ? 24 : 16,
|
||||
),
|
||||
RoundedWhiteContainer(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
"Domain name",
|
||||
style: Util.isDesktop
|
||||
? STextStyles.w500_14(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.infoItemLabel,
|
||||
)
|
||||
: STextStyles.w500_12(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.infoItemLabel,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
"${widget.domainName.substring(2)}.bit",
|
||||
style: Util.isDesktop
|
||||
? STextStyles.w500_14(context)
|
||||
: STextStyles.w500_12(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: Util.isDesktop ? 16 : 8,
|
||||
),
|
||||
RoundedWhiteContainer(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
"Amount",
|
||||
style: Util.isDesktop
|
||||
? STextStyles.w500_14(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.infoItemLabel,
|
||||
)
|
||||
: STextStyles.w500_12(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.infoItemLabel,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
ref.watch(pAmountFormatter(coin)).format(
|
||||
Amount(
|
||||
rawValue: BigInt.from(kNameNewAmountSats),
|
||||
fractionDigits: coin.fractionDigits,
|
||||
),
|
||||
),
|
||||
style: Util.isDesktop
|
||||
? STextStyles.w500_14(context)
|
||||
: STextStyles.w500_12(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: Util.isDesktop ? 24 : 16,
|
||||
),
|
||||
ConditionalParent(
|
||||
condition: !Util.isDesktop,
|
||||
builder: (child) => Row(
|
||||
children: [child],
|
||||
),
|
||||
child: CustomTextButton(
|
||||
text: _settingsHidden ? "More settings" : "Hide settings",
|
||||
onTap: () {
|
||||
setState(() {
|
||||
_settingsHidden = !_settingsHidden;
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
if (!_settingsHidden)
|
||||
SizedBox(
|
||||
height: Util.isDesktop ? 24 : 16,
|
||||
),
|
||||
if (!_settingsHidden)
|
||||
if (_dnsRecords.isEmpty)
|
||||
RoundedWhiteContainer(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
"Add DNS records to your domain name",
|
||||
style: STextStyles.w500_12(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textSubtitle1,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (!_settingsHidden)
|
||||
ConditionalParent(
|
||||
condition: !Util.isDesktop,
|
||||
builder: (child) => Expanded(child: child),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
..._dnsRecords.map(
|
||||
(e) => DNSRecordCard(
|
||||
key: ValueKey(e),
|
||||
record: e,
|
||||
onRemoveTapped: () => setState(() {
|
||||
_dnsRecords.remove(e);
|
||||
}),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: Util.isDesktop ? 16 : 8,
|
||||
),
|
||||
SecondaryButton(
|
||||
label: _dnsRecords.isEmpty
|
||||
? "Add DNS record"
|
||||
: "Add another DNS record",
|
||||
buttonHeight: Util.isDesktop ? ButtonHeight.l : null,
|
||||
onPressed: _addRecord,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: Util.isDesktop ? 24 : 16,
|
||||
),
|
||||
if (!Util.isDesktop && _settingsHidden) const Spacer(),
|
||||
PrimaryButton(
|
||||
label: "Buy",
|
||||
// width: Util.isDesktop ? 160 : double.infinity,
|
||||
buttonHeight: Util.isDesktop ? ButtonHeight.l : null,
|
||||
onPressed: _preRegister,
|
||||
),
|
||||
SizedBox(
|
||||
height: Util.isDesktop ? 32 : 16,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
(String, String, String) _computeScriptNameNew((String, Uint8List) args) {
|
||||
return scriptNameNew(args.$1, args.$2);
|
||||
}
|
||||
|
||||
class DNSRecordCard extends StatelessWidget {
|
||||
const DNSRecordCard({
|
||||
super.key,
|
||||
required this.record,
|
||||
required this.onRemoveTapped,
|
||||
});
|
||||
|
||||
final DNSRecord record;
|
||||
final VoidCallback onRemoveTapped;
|
||||
|
||||
String get _extraInfo {
|
||||
if (record.type == DNSRecordType.A) {
|
||||
// TODO error handling
|
||||
return " - ${DNSAddressType.values.firstWhere((e) => e.key == record.data.keys.first).name}";
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return RoundedWhiteContainer(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
"${record.type.name}$_extraInfo",
|
||||
),
|
||||
CustomTextButton(
|
||||
text: "Remove",
|
||||
onTap: onRemoveTapped,
|
||||
),
|
||||
],
|
||||
),
|
||||
Text(record.getValueString()),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
1081
lib/pages/namecoin_names/confirm_name_transaction_view.dart
Normal file
1081
lib/pages/namecoin_names/confirm_name_transaction_view.dart
Normal file
File diff suppressed because it is too large
Load diff
102
lib/pages/namecoin_names/manage_domain_view.dart
Normal file
102
lib/pages/namecoin_names/manage_domain_view.dart
Normal file
|
@ -0,0 +1,102 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../models/isar/models/blockchain_data/utxo.dart';
|
||||
import '../../themes/stack_colors.dart';
|
||||
import '../../utilities/constants.dart';
|
||||
import '../../utilities/text_styles.dart';
|
||||
import '../../widgets/background.dart';
|
||||
import '../../widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
import '../../widgets/toggle.dart';
|
||||
import 'sub_widgets/transfer_option_widget.dart';
|
||||
import 'sub_widgets/update_option_widget.dart';
|
||||
|
||||
class ManageDomainView extends StatefulWidget {
|
||||
const ManageDomainView({
|
||||
super.key,
|
||||
required this.walletId,
|
||||
required this.utxo,
|
||||
});
|
||||
|
||||
final String walletId;
|
||||
final UTXO utxo;
|
||||
|
||||
static const routeName = "/manageDomainView";
|
||||
|
||||
@override
|
||||
State<ManageDomainView> createState() => _ManageDomainViewState();
|
||||
}
|
||||
|
||||
class _ManageDomainViewState extends State<ManageDomainView> {
|
||||
bool _onTransfer = true;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Background(
|
||||
child: Scaffold(
|
||||
backgroundColor: Colors.transparent,
|
||||
appBar: AppBar(
|
||||
leading: const AppBarBackButton(),
|
||||
titleSpacing: 0,
|
||||
title: Text(
|
||||
"Manage domain",
|
||||
style: STextStyles.navBarTitle(context),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
body: SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 48,
|
||||
child: Toggle(
|
||||
key: UniqueKey(),
|
||||
onColor:
|
||||
Theme.of(context).extension<StackColors>()!.popupBG,
|
||||
offColor: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFieldDefaultBG,
|
||||
onText: "Transfer",
|
||||
offText: "Update",
|
||||
isOn: !_onTransfer,
|
||||
onValueChanged: (value) {
|
||||
FocusManager.instance.primaryFocus?.unfocus();
|
||||
setState(() {
|
||||
_onTransfer = !value;
|
||||
});
|
||||
},
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
Expanded(
|
||||
child: IndexedStack(
|
||||
index: _onTransfer ? 0 : 1,
|
||||
children: [
|
||||
TransferOptionWidget(
|
||||
walletId: widget.walletId,
|
||||
utxo: widget.utxo,
|
||||
),
|
||||
UpdateOptionWidget(
|
||||
walletId: widget.walletId,
|
||||
utxo: widget.utxo,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
251
lib/pages/namecoin_names/namecoin_names_home_view.dart
Normal file
251
lib/pages/namecoin_names/namecoin_names_home_view.dart
Normal file
|
@ -0,0 +1,251 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
|
||||
import '../../themes/stack_colors.dart';
|
||||
import '../../utilities/assets.dart';
|
||||
import '../../utilities/constants.dart';
|
||||
import '../../utilities/text_styles.dart';
|
||||
import '../../utilities/util.dart';
|
||||
import '../../widgets/conditional_parent.dart';
|
||||
import '../../widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
import '../../widgets/desktop/desktop_app_bar.dart';
|
||||
import '../../widgets/desktop/desktop_scaffold.dart';
|
||||
import '../../widgets/toggle.dart';
|
||||
import 'sub_widgets/buy_domain_option_widget.dart';
|
||||
import 'sub_widgets/manage_domains_option_widget.dart';
|
||||
|
||||
class NamecoinNamesHomeView extends ConsumerStatefulWidget {
|
||||
const NamecoinNamesHomeView({
|
||||
super.key,
|
||||
required this.walletId,
|
||||
});
|
||||
|
||||
final String walletId;
|
||||
|
||||
static const String routeName = "/namecoinNamesHomeView";
|
||||
|
||||
@override
|
||||
ConsumerState<NamecoinNamesHomeView> createState() =>
|
||||
_NamecoinNamesHomeViewState();
|
||||
}
|
||||
|
||||
class _NamecoinNamesHomeViewState extends ConsumerState<NamecoinNamesHomeView> {
|
||||
bool _onManage = true;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
debugPrint("BUILD: $runtimeType");
|
||||
final isDesktop = Util.isDesktop;
|
||||
|
||||
return MasterScaffold(
|
||||
isDesktop: isDesktop,
|
||||
appBar: isDesktop
|
||||
? DesktopAppBar(
|
||||
isCompactHeight: true,
|
||||
background: Theme.of(context).extension<StackColors>()!.popupBG,
|
||||
leading: Row(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 24,
|
||||
right: 20,
|
||||
),
|
||||
child: AppBarIconButton(
|
||||
size: 32,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFieldDefaultBG,
|
||||
shadows: const [],
|
||||
icon: SvgPicture.asset(
|
||||
Assets.svg.arrowLeft,
|
||||
width: 18,
|
||||
height: 18,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.topNavIconPrimary,
|
||||
),
|
||||
onPressed: Navigator.of(context).pop,
|
||||
),
|
||||
),
|
||||
SvgPicture.asset(
|
||||
Assets.svg.robotHead,
|
||||
width: 32,
|
||||
height: 32,
|
||||
color: Theme.of(context).extension<StackColors>()!.textDark,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
Text(
|
||||
"Domains",
|
||||
style: STextStyles.desktopH3(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: AppBar(
|
||||
leading: AppBarBackButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
titleSpacing: 0,
|
||||
title: Text(
|
||||
"Domains",
|
||||
style: STextStyles.navBarTitle(context),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
body: ConditionalParent(
|
||||
condition: !isDesktop,
|
||||
builder: (child) => SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 16,
|
||||
left: 16,
|
||||
right: 16,
|
||||
),
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
child: Util.isDesktop
|
||||
? Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 24,
|
||||
left: 24,
|
||||
right: 24,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 460,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
"Buy domain",
|
||||
style:
|
||||
STextStyles.desktopTextExtraSmall(context)
|
||||
.copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFieldActiveSearchIconLeft,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 14,
|
||||
),
|
||||
Flexible(
|
||||
child: BuyDomainOptionWidget(
|
||||
walletId: widget.walletId,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 24,
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
"Manage domains",
|
||||
style:
|
||||
STextStyles.desktopTextExtraSmall(context)
|
||||
.copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFieldActiveSearchIconLeft,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 14,
|
||||
),
|
||||
Flexible(
|
||||
child: SingleChildScrollView(
|
||||
child: ManageDomainsOptionWidget(
|
||||
walletId: widget.walletId,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 48,
|
||||
child: Toggle(
|
||||
key: UniqueKey(),
|
||||
onColor:
|
||||
Theme.of(context).extension<StackColors>()!.popupBG,
|
||||
offColor: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFieldDefaultBG,
|
||||
onText: "Buy domain",
|
||||
offText: "Manage domains",
|
||||
isOn: !_onManage,
|
||||
onValueChanged: (value) {
|
||||
FocusManager.instance.primaryFocus?.unfocus();
|
||||
setState(() {
|
||||
_onManage = !value;
|
||||
});
|
||||
},
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
Expanded(
|
||||
child: IndexedStack(
|
||||
index: _onManage ? 0 : 1,
|
||||
children: [
|
||||
BuyDomainOptionWidget(
|
||||
walletId: widget.walletId,
|
||||
),
|
||||
LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
return ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
minHeight: constraints.maxHeight,
|
||||
),
|
||||
child: SingleChildScrollView(
|
||||
child: IntrinsicHeight(
|
||||
child: ManageDomainsOptionWidget(
|
||||
walletId: widget.walletId,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,396 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:namecoin/namecoin.dart';
|
||||
|
||||
import '../../../providers/providers.dart';
|
||||
import '../../../themes/stack_colors.dart';
|
||||
import '../../../utilities/assets.dart';
|
||||
import '../../../utilities/constants.dart';
|
||||
import '../../../utilities/extensions/impl/string.dart';
|
||||
import '../../../utilities/logger.dart';
|
||||
import '../../../utilities/show_loading.dart';
|
||||
import '../../../utilities/text_formatters.dart';
|
||||
import '../../../utilities/text_styles.dart';
|
||||
import '../../../utilities/util.dart';
|
||||
import '../../../wallets/wallet/impl/namecoin_wallet.dart';
|
||||
import '../../../widgets/desktop/desktop_dialog_close_button.dart';
|
||||
import '../../../widgets/desktop/primary_button.dart';
|
||||
import '../../../widgets/desktop/secondary_button.dart';
|
||||
import '../../../widgets/dialogs/s_dialog.dart';
|
||||
import '../../../widgets/rounded_white_container.dart';
|
||||
import '../../../widgets/stack_dialog.dart';
|
||||
import '../buy_domain_view.dart';
|
||||
|
||||
class BuyDomainOptionWidget extends ConsumerStatefulWidget {
|
||||
const BuyDomainOptionWidget({super.key, required this.walletId});
|
||||
|
||||
final String walletId;
|
||||
|
||||
@override
|
||||
ConsumerState<BuyDomainOptionWidget> createState() => _BuyDomainWidgetState();
|
||||
}
|
||||
|
||||
class _BuyDomainWidgetState extends ConsumerState<BuyDomainOptionWidget> {
|
||||
static const kMaxByteLength = nameMaxLength - 2; // subtract length of "d/"
|
||||
|
||||
final _nameController = TextEditingController();
|
||||
final _nameFieldFocus = FocusNode();
|
||||
|
||||
String? get formattedNameInField {
|
||||
if (_nameController.text.isNotEmpty) {
|
||||
if (_nameController.text.startsWith("d/")) {
|
||||
return _nameController.text;
|
||||
} else {
|
||||
return "d/${_nameController.text}";
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
bool _isAvailable = false;
|
||||
String? _lastLookedUpName;
|
||||
|
||||
bool _lookupLock = false;
|
||||
Future<void> _lookup() async {
|
||||
if (_lookupLock) return;
|
||||
_lookupLock = true;
|
||||
try {
|
||||
_isAvailable = false;
|
||||
|
||||
_lastLookedUpName = formattedNameInField;
|
||||
final result = await showLoading(
|
||||
whileFuture:
|
||||
(ref.read(pWallets).getWallet(widget.walletId) as NamecoinWallet)
|
||||
.lookupName(_lastLookedUpName!),
|
||||
context: context,
|
||||
message: "Searching...",
|
||||
onException: (e) => throw e,
|
||||
rootNavigator: Util.isDesktop,
|
||||
delay: const Duration(seconds: 2),
|
||||
);
|
||||
|
||||
_isAvailable = result?.nameState == NameState.available;
|
||||
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
Logging.instance.i("LOOKUP RESULT: $result");
|
||||
} catch (e, s) {
|
||||
Logging.instance.e("_lookup failed", error: e, stackTrace: s);
|
||||
|
||||
String? err;
|
||||
if (e.toString().contains("Contains invalid characters")) {
|
||||
err = "Contains invalid characters";
|
||||
}
|
||||
|
||||
if (mounted) {
|
||||
await showDialog<void>(
|
||||
context: context,
|
||||
builder: (_) => StackOkDialog(
|
||||
title: "Name lookup failed",
|
||||
message: err,
|
||||
desktopPopRootNavigator: Util.isDesktop,
|
||||
maxWidth: Util.isDesktop ? 600 : null,
|
||||
),
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
_lookupLock = false;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (mounted) {
|
||||
_nameFieldFocus.requestFocus();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_nameController.dispose();
|
||||
_nameFieldFocus.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final double dotBitBoxLength = Util.isDesktop ? 100 : 74;
|
||||
return Column(
|
||||
crossAxisAlignment:
|
||||
Util.isDesktop ? CrossAxisAlignment.start : CrossAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 48,
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Container(
|
||||
height: 48,
|
||||
width: 100,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFieldDefaultBG,
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
), // Adjust radius as needed
|
||||
bottomLeft:
|
||||
Radius.circular(Constants.size.circularBorderRadius),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextField(
|
||||
inputFormatters: [
|
||||
Utf8ByteLengthLimitingTextInputFormatter(
|
||||
kMaxByteLength,
|
||||
),
|
||||
],
|
||||
textInputAction: TextInputAction.search,
|
||||
focusNode: _nameFieldFocus,
|
||||
controller: _nameController,
|
||||
textAlignVertical: TextAlignVertical.center,
|
||||
decoration: InputDecoration(
|
||||
isDense: true,
|
||||
contentPadding: EdgeInsets.zero,
|
||||
prefixIcon: Padding(
|
||||
padding: const EdgeInsets.all(14),
|
||||
child: SvgPicture.asset(
|
||||
Assets.svg.search,
|
||||
width: 20,
|
||||
height: 20,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFieldDefaultSearchIconLeft,
|
||||
),
|
||||
),
|
||||
fillColor: Colors.transparent,
|
||||
hintText: "Find a domain name",
|
||||
hintStyle: STextStyles.fieldLabel(context),
|
||||
border: InputBorder.none,
|
||||
enabledBorder: InputBorder.none,
|
||||
focusedBorder: InputBorder.none,
|
||||
),
|
||||
onSubmitted: (_) {
|
||||
if (_nameController.text.isNotEmpty) {
|
||||
_lookup();
|
||||
}
|
||||
},
|
||||
onChanged: (value) {
|
||||
// trigger look up button enabled/disabled state change
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
height: 48,
|
||||
width: dotBitBoxLength,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.buttonBackPrimary,
|
||||
borderRadius: BorderRadius.only(
|
||||
topRight: Radius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
), // Adjust radius as needed
|
||||
bottomRight:
|
||||
Radius.circular(Constants.size.circularBorderRadius),
|
||||
),
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
".bit",
|
||||
style: STextStyles.w600_14(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.buttonTextPrimary,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 4,
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.only(right: dotBitBoxLength),
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
final length =
|
||||
_nameController.text.toUint8ListFromUtf8.lengthInBytes;
|
||||
return Text(
|
||||
"$length/$kMaxByteLength",
|
||||
style: STextStyles.w500_10(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textSubtitle2,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(
|
||||
height: Util.isDesktop ? 24 : 16,
|
||||
),
|
||||
SecondaryButton(
|
||||
label: "Lookup",
|
||||
enabled: _nameController.text.isNotEmpty,
|
||||
// width: Util.isDesktop ? 160 : double.infinity,
|
||||
buttonHeight: Util.isDesktop ? ButtonHeight.l : null,
|
||||
onPressed: _lookup,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 32,
|
||||
),
|
||||
if (_lastLookedUpName != null)
|
||||
_NameCard(
|
||||
walletId: widget.walletId,
|
||||
isAvailable: _isAvailable,
|
||||
formattedName: _lastLookedUpName!,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _NameCard extends ConsumerWidget {
|
||||
const _NameCard({
|
||||
super.key,
|
||||
required this.walletId,
|
||||
required this.isAvailable,
|
||||
required this.formattedName,
|
||||
});
|
||||
|
||||
final String walletId;
|
||||
final bool isAvailable;
|
||||
final String formattedName;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final availability = isAvailable ? "Available" : "Unavailable";
|
||||
final color = isAvailable
|
||||
? Theme.of(context).extension<StackColors>()!.accentColorGreen
|
||||
: Theme.of(context).extension<StackColors>()!.accentColorRed;
|
||||
|
||||
final style = (Util.isDesktop
|
||||
? STextStyles.w500_16(context)
|
||||
: STextStyles.w500_12(context));
|
||||
|
||||
return RoundedWhiteContainer(
|
||||
padding: EdgeInsets.all(Util.isDesktop ? 24 : 16),
|
||||
child: IntrinsicHeight(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
"${formattedName.substring(2)}.bit",
|
||||
style: style,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 4,
|
||||
),
|
||||
Text(
|
||||
availability,
|
||||
style: style.copyWith(
|
||||
color: color,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Column(
|
||||
children: [
|
||||
PrimaryButton(
|
||||
label: "Buy domain",
|
||||
enabled: isAvailable,
|
||||
buttonHeight:
|
||||
Util.isDesktop ? ButtonHeight.m : ButtonHeight.l,
|
||||
width: Util.isDesktop ? 140 : 120,
|
||||
onPressed: () async {
|
||||
if (context.mounted) {
|
||||
if (Util.isDesktop) {
|
||||
await showDialog<void>(
|
||||
context: context,
|
||||
builder: (context) => SDialog(
|
||||
child: SizedBox(
|
||||
width: 580,
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 32,
|
||||
),
|
||||
child: Text(
|
||||
"Buy domain",
|
||||
style: STextStyles.desktopH3(context),
|
||||
),
|
||||
),
|
||||
const DesktopDialogCloseButton(),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 32,
|
||||
),
|
||||
child: BuyDomainView(
|
||||
walletId: walletId,
|
||||
domainName: formattedName,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
await Navigator.of(context).pushNamed(
|
||||
BuyDomainView.routeName,
|
||||
arguments: (
|
||||
walletId: walletId,
|
||||
domainName: formattedName
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:namecoin/namecoin.dart';
|
||||
|
||||
import '../../../models/isar/models/blockchain_data/utxo.dart';
|
||||
import '../../../providers/db/main_db_provider.dart';
|
||||
import '../../../utilities/util.dart';
|
||||
import '../../../wallets/isar/providers/wallet_info_provider.dart';
|
||||
import 'owned_name_card.dart';
|
||||
|
||||
class ManageDomainsOptionWidget extends ConsumerStatefulWidget {
|
||||
const ManageDomainsOptionWidget({
|
||||
super.key,
|
||||
required this.walletId,
|
||||
});
|
||||
|
||||
final String walletId;
|
||||
|
||||
@override
|
||||
ConsumerState<ManageDomainsOptionWidget> createState() =>
|
||||
_ManageDomainsWidgetState();
|
||||
}
|
||||
|
||||
class _ManageDomainsWidgetState
|
||||
extends ConsumerState<ManageDomainsOptionWidget> {
|
||||
double _tempWidth = 0;
|
||||
double? _width;
|
||||
int _count = 0;
|
||||
|
||||
void _sillyHack(double value, int length) {
|
||||
if (value > _tempWidth) _tempWidth = value;
|
||||
_count++;
|
||||
if (_count == length) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_width = _tempWidth;
|
||||
_tempWidth = 0;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final height = ref.watch(pWalletChainHeight(widget.walletId));
|
||||
return StreamBuilder(
|
||||
stream: ref.watch(
|
||||
mainDBProvider.select(
|
||||
(s) => s.isar.utxos
|
||||
.where()
|
||||
.walletIdEqualTo(widget.walletId)
|
||||
.filter()
|
||||
.otherDataIsNotNull()
|
||||
.watch(fireImmediately: true),
|
||||
),
|
||||
),
|
||||
builder: (context, snapshot) {
|
||||
List<(UTXO, OpNameData)> list = [];
|
||||
if (snapshot.hasData) {
|
||||
list = snapshot.data!.map((utxo) {
|
||||
final data = jsonDecode(utxo.otherData!) as Map;
|
||||
|
||||
final nameData = jsonDecode(data["nameOpData"] as String) as Map;
|
||||
|
||||
return (
|
||||
utxo,
|
||||
OpNameData(nameData.cast(), utxo.blockHeight ?? height)
|
||||
);
|
||||
}).toList(growable: false);
|
||||
}
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
...list.map(
|
||||
(e) => Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
bottom: 10,
|
||||
),
|
||||
child: OwnedNameCard(
|
||||
key: ValueKey(e),
|
||||
utxo: e.$1,
|
||||
opNameData: e.$2,
|
||||
firstColWidth: _width,
|
||||
calculatedFirstColWidth: (value) => _sillyHack(
|
||||
value,
|
||||
list.length,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: Util.isDesktop ? 14 : 6,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
731
lib/pages/namecoin_names/sub_widgets/name_details.dart
Normal file
731
lib/pages/namecoin_names/sub_widgets/name_details.dart
Normal file
|
@ -0,0 +1,731 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:namecoin/namecoin.dart';
|
||||
|
||||
import '../../../models/isar/models/isar_models.dart';
|
||||
import '../../../providers/db/main_db_provider.dart';
|
||||
import '../../../providers/global/secure_store_provider.dart';
|
||||
import '../../../providers/global/wallets_provider.dart';
|
||||
import '../../../themes/stack_colors.dart';
|
||||
import '../../../utilities/text_styles.dart';
|
||||
import '../../../utilities/util.dart';
|
||||
import '../../../wallets/isar/providers/wallet_info_provider.dart';
|
||||
import '../../../wallets/wallet/impl/namecoin_wallet.dart';
|
||||
import '../../../widgets/background.dart';
|
||||
import '../../../widgets/conditional_parent.dart';
|
||||
import '../../../widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
import '../../../widgets/custom_buttons/blue_text_button.dart';
|
||||
import '../../../widgets/custom_buttons/simple_copy_button.dart';
|
||||
import '../../../widgets/desktop/desktop_dialog_close_button.dart';
|
||||
import '../../../widgets/desktop/secondary_button.dart';
|
||||
import '../../../widgets/dialogs/s_dialog.dart';
|
||||
import '../../../widgets/rounded_container.dart';
|
||||
import '../../wallet_view/transaction_views/transaction_details_view.dart';
|
||||
import '../manage_domain_view.dart';
|
||||
import 'transfer_option_widget.dart';
|
||||
import 'update_option_widget.dart';
|
||||
|
||||
class NameDetailsView extends ConsumerStatefulWidget {
|
||||
const NameDetailsView({
|
||||
super.key,
|
||||
required this.utxoId,
|
||||
required this.walletId,
|
||||
});
|
||||
|
||||
static const routeName = "/namecoinNameDetails";
|
||||
|
||||
final Id utxoId;
|
||||
final String walletId;
|
||||
|
||||
@override
|
||||
ConsumerState<NameDetailsView> createState() => _ManageDomainsWidgetState();
|
||||
}
|
||||
|
||||
class _ManageDomainsWidgetState extends ConsumerState<NameDetailsView> {
|
||||
late Stream<UTXO?> streamUTXO;
|
||||
UTXO? utxo;
|
||||
OpNameData? opNameData;
|
||||
|
||||
String? constructedName, value;
|
||||
|
||||
Stream<AddressLabel?>? streamLabel;
|
||||
AddressLabel? label;
|
||||
|
||||
void setUtxo(UTXO? utxo, int currentHeight) {
|
||||
if (utxo != null) {
|
||||
this.utxo = utxo;
|
||||
final data = jsonDecode(utxo.otherData!) as Map;
|
||||
|
||||
final nameData = jsonDecode(data["nameOpData"] as String) as Map;
|
||||
opNameData =
|
||||
OpNameData(nameData.cast(), utxo.blockHeight ?? currentHeight);
|
||||
|
||||
_setName();
|
||||
}
|
||||
}
|
||||
|
||||
void _setName() {
|
||||
try {
|
||||
constructedName = opNameData!.constructedName;
|
||||
value = opNameData!.value;
|
||||
} catch (_) {
|
||||
if (opNameData?.op == OpName.nameNew) {
|
||||
ref
|
||||
.read(secureStoreProvider)
|
||||
.read(
|
||||
key: nameSaltKeyBuilder(
|
||||
utxo!.txid,
|
||||
widget.walletId,
|
||||
utxo!.vout,
|
||||
),
|
||||
)
|
||||
.then((onValue) {
|
||||
if (onValue != null) {
|
||||
final data = (jsonDecode(onValue) as Map).cast<String, String>();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
constructedName = data["name"]!;
|
||||
value = data["value"]!;
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
constructedName = "UNKNOWN";
|
||||
value = "";
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(String, Color) _getExpiry(int currentChainHeight, StackColors theme) {
|
||||
final String message;
|
||||
final Color color;
|
||||
|
||||
if (utxo?.blockHash == null) {
|
||||
message = "Expires in $blocksNameExpiration+ blocks";
|
||||
color = theme.accentColorGreen;
|
||||
} else {
|
||||
final remaining = opNameData?.expiredBlockLeft(
|
||||
currentChainHeight,
|
||||
false,
|
||||
);
|
||||
final semiRemaining = opNameData?.expiredBlockLeft(
|
||||
currentChainHeight,
|
||||
true,
|
||||
);
|
||||
|
||||
if (remaining == null) {
|
||||
color = theme.accentColorRed;
|
||||
message = "Expired";
|
||||
} else {
|
||||
message = "Expires in $remaining blocks";
|
||||
if (semiRemaining == null) {
|
||||
color = theme.accentColorYellow;
|
||||
} else {
|
||||
color = theme.accentColorGreen;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (message, color);
|
||||
}
|
||||
|
||||
bool _checkConfirmedUtxo(int currentHeight) {
|
||||
return (ref.read(pWallets).getWallet(widget.walletId) as NamecoinWallet)
|
||||
.checkUtxoConfirmed(
|
||||
utxo!,
|
||||
currentHeight,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
setUtxo(
|
||||
ref
|
||||
.read(mainDBProvider)
|
||||
.isar
|
||||
.utxos
|
||||
.where()
|
||||
.idEqualTo(widget.utxoId)
|
||||
.findFirstSync(),
|
||||
ref.read(pWalletChainHeight(widget.walletId)),
|
||||
);
|
||||
|
||||
_setName();
|
||||
|
||||
if (utxo?.address != null) {
|
||||
label = ref.read(mainDBProvider).getAddressLabelSync(
|
||||
widget.walletId,
|
||||
utxo!.address!,
|
||||
);
|
||||
|
||||
if (label != null) {
|
||||
streamLabel = ref.read(mainDBProvider).watchAddressLabel(id: label!.id);
|
||||
}
|
||||
}
|
||||
|
||||
streamUTXO = ref.read(mainDBProvider).watchUTXO(id: widget.utxoId);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final currentHeight = ref.watch(pWalletChainHeight(widget.walletId));
|
||||
|
||||
final (message, color) = _getExpiry(
|
||||
currentHeight,
|
||||
Theme.of(context).extension<StackColors>()!,
|
||||
);
|
||||
|
||||
final canManage = utxo != null &&
|
||||
_checkConfirmedUtxo(currentHeight) &&
|
||||
(opNameData?.op == OpName.nameUpdate ||
|
||||
opNameData?.op == OpName.nameFirstUpdate);
|
||||
|
||||
return ConditionalParent(
|
||||
condition: !Util.isDesktop,
|
||||
builder: (child) => Background(
|
||||
child: Scaffold(
|
||||
backgroundColor: Colors.transparent,
|
||||
appBar: AppBar(
|
||||
backgroundColor: Colors.transparent,
|
||||
// Theme.of(context).extension<StackColors>()!.background,
|
||||
leading: const AppBarBackButton(),
|
||||
title: Text(
|
||||
"Domain details",
|
||||
style: STextStyles.navBarTitle(context),
|
||||
),
|
||||
actions: canManage
|
||||
? [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 10,
|
||||
bottom: 10,
|
||||
right: 10,
|
||||
),
|
||||
child: CustomTextButton(
|
||||
key: const Key("addAddressBookEntryFavoriteButtonKey"),
|
||||
text: "Manage",
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamed(
|
||||
ManageDomainView.routeName,
|
||||
arguments: (walletId: widget.walletId, utxo: utxo!),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
]
|
||||
: null,
|
||||
),
|
||||
body: SafeArea(
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: SingleChildScrollView(
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
minHeight: constraints.maxHeight,
|
||||
),
|
||||
child: IntrinsicHeight(
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
child: ConditionalParent(
|
||||
condition: Util.isDesktop,
|
||||
builder: (child) {
|
||||
return SizedBox(
|
||||
width: 641,
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 32),
|
||||
child: Text(
|
||||
"Domain details",
|
||||
style: STextStyles.desktopH3(context),
|
||||
),
|
||||
),
|
||||
const DesktopDialogCloseButton(),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 32,
|
||||
right: 32,
|
||||
bottom: 32,
|
||||
top: 10,
|
||||
),
|
||||
child: RoundedContainer(
|
||||
padding: EdgeInsets.zero,
|
||||
color: Colors.transparent,
|
||||
borderColor: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFieldDefaultBG,
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
if (canManage)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 32),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: SecondaryButton(
|
||||
label: "Transfer",
|
||||
buttonHeight: ButtonHeight.l,
|
||||
onPressed: () {
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return SDialog(
|
||||
child: SizedBox(
|
||||
width: 641,
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 32,
|
||||
),
|
||||
child: Text(
|
||||
"Transfer domain",
|
||||
style: STextStyles.desktopH3(
|
||||
context,
|
||||
),
|
||||
),
|
||||
),
|
||||
const DesktopDialogCloseButton(),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 32,
|
||||
right: 32,
|
||||
bottom: 32,
|
||||
top: 16,
|
||||
),
|
||||
child: TransferOptionWidget(
|
||||
walletId: widget.walletId,
|
||||
utxo: utxo!,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 32,
|
||||
),
|
||||
Expanded(
|
||||
child: SecondaryButton(
|
||||
label: "Update",
|
||||
buttonHeight: ButtonHeight.l,
|
||||
onPressed: () {
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return SDialog(
|
||||
child: SizedBox(
|
||||
width: 641,
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 32,
|
||||
),
|
||||
child: Text(
|
||||
"Update domain",
|
||||
style: STextStyles.desktopH3(
|
||||
context,
|
||||
),
|
||||
),
|
||||
),
|
||||
const DesktopDialogCloseButton(),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 32,
|
||||
right: 32,
|
||||
bottom: 32,
|
||||
),
|
||||
child: UpdateOptionWidget(
|
||||
walletId: widget.walletId,
|
||||
utxo: utxo!,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (canManage)
|
||||
const SizedBox(
|
||||
height: 32,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
child: StreamBuilder(
|
||||
stream: streamUTXO,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
setUtxo(snapshot.data!, currentHeight);
|
||||
}
|
||||
|
||||
return utxo == null
|
||||
? Center(
|
||||
child: Text(
|
||||
"Missing output. Was it used recently?",
|
||||
style: STextStyles.w500_14(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.accentColorRed,
|
||||
),
|
||||
),
|
||||
)
|
||||
: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
// if (!isDesktop)
|
||||
// const SizedBox(
|
||||
// height: 10,
|
||||
// ),
|
||||
RoundedContainer(
|
||||
padding: const EdgeInsets.all(12),
|
||||
color: Util.isDesktop
|
||||
? Colors.transparent
|
||||
: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.popupBG,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SelectableText(
|
||||
constructedName ?? "",
|
||||
style: STextStyles.pageTitleH2(context),
|
||||
),
|
||||
if (Util.isDesktop)
|
||||
SelectableText(
|
||||
opNameData!.op.name,
|
||||
style: STextStyles.w500_14(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (!Util.isDesktop)
|
||||
SelectableText(
|
||||
opNameData!.op.name,
|
||||
style: STextStyles.w500_14(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const _Div(),
|
||||
RoundedContainer(
|
||||
padding: Util.isDesktop
|
||||
? const EdgeInsets.all(16)
|
||||
: const EdgeInsets.all(12),
|
||||
color: Util.isDesktop
|
||||
? Colors.transparent
|
||||
: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.popupBG,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
"Value",
|
||||
style: STextStyles.w500_14(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textSubtitle1,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 4,
|
||||
),
|
||||
SelectableText(
|
||||
value ?? "",
|
||||
style: STextStyles.w500_14(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const _Div(),
|
||||
RoundedContainer(
|
||||
padding: Util.isDesktop
|
||||
? const EdgeInsets.all(16)
|
||||
: const EdgeInsets.all(12),
|
||||
color: Util.isDesktop
|
||||
? Colors.transparent
|
||||
: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.popupBG,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
"Address",
|
||||
style: STextStyles.w500_14(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textSubtitle1,
|
||||
),
|
||||
),
|
||||
Util.isDesktop
|
||||
? IconCopyButton(
|
||||
data: utxo!.address!,
|
||||
)
|
||||
: SimpleCopyButton(
|
||||
data: utxo!.address!,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 4,
|
||||
),
|
||||
SelectableText(
|
||||
utxo!.address!,
|
||||
style: STextStyles.w500_14(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (label != null && label!.value.isNotEmpty)
|
||||
const _Div(),
|
||||
if (label != null && label!.value.isNotEmpty)
|
||||
RoundedContainer(
|
||||
padding: Util.isDesktop
|
||||
? const EdgeInsets.all(16)
|
||||
: const EdgeInsets.all(12),
|
||||
color: Util.isDesktop
|
||||
? Colors.transparent
|
||||
: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.popupBG,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
"Address label",
|
||||
style:
|
||||
STextStyles.w500_14(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textSubtitle1,
|
||||
),
|
||||
),
|
||||
Util.isDesktop
|
||||
? IconCopyButton(
|
||||
data: label!.value,
|
||||
)
|
||||
: SimpleCopyButton(
|
||||
data: label!.value,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 4,
|
||||
),
|
||||
SelectableText(
|
||||
label!.value,
|
||||
style: STextStyles.w500_14(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const _Div(),
|
||||
RoundedContainer(
|
||||
padding: Util.isDesktop
|
||||
? const EdgeInsets.all(16)
|
||||
: const EdgeInsets.all(12),
|
||||
color: Util.isDesktop
|
||||
? Colors.transparent
|
||||
: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.popupBG,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
"Transaction ID",
|
||||
style: STextStyles.w500_14(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textSubtitle1,
|
||||
),
|
||||
),
|
||||
Util.isDesktop
|
||||
? IconCopyButton(
|
||||
data: utxo!.txid,
|
||||
)
|
||||
: SimpleCopyButton(
|
||||
data: utxo!.txid,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 4,
|
||||
),
|
||||
SelectableText(
|
||||
utxo!.txid,
|
||||
style: STextStyles.w500_14(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const _Div(),
|
||||
RoundedContainer(
|
||||
padding: Util.isDesktop
|
||||
? const EdgeInsets.all(16)
|
||||
: const EdgeInsets.all(12),
|
||||
color: Util.isDesktop
|
||||
? Colors.transparent
|
||||
: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.popupBG,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Expiry",
|
||||
style: STextStyles.w500_14(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textSubtitle1,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 4,
|
||||
),
|
||||
SelectableText(
|
||||
message,
|
||||
style: STextStyles.w500_14(context).copyWith(
|
||||
color: color,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const _Div(),
|
||||
RoundedContainer(
|
||||
padding: Util.isDesktop
|
||||
? const EdgeInsets.all(16)
|
||||
: const EdgeInsets.all(12),
|
||||
color: Util.isDesktop
|
||||
? Colors.transparent
|
||||
: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.popupBG,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Confirmations",
|
||||
style: STextStyles.w500_14(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textSubtitle1,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 4,
|
||||
),
|
||||
SelectableText(
|
||||
"${utxo!.getConfirmations(currentHeight)}",
|
||||
style: STextStyles.w500_14(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _Div extends StatelessWidget {
|
||||
const _Div({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (Util.isDesktop) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
height: 1.0,
|
||||
color: Theme.of(context).extension<StackColors>()!.textFieldDefaultBG,
|
||||
);
|
||||
} else {
|
||||
return const SizedBox(
|
||||
height: 12,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
227
lib/pages/namecoin_names/sub_widgets/owned_name_card.dart
Normal file
227
lib/pages/namecoin_names/sub_widgets/owned_name_card.dart
Normal file
|
@ -0,0 +1,227 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:namecoin/namecoin.dart';
|
||||
|
||||
import '../../../models/isar/models/isar_models.dart';
|
||||
import '../../../providers/global/secure_store_provider.dart';
|
||||
import '../../../themes/stack_colors.dart';
|
||||
import '../../../utilities/text_styles.dart';
|
||||
import '../../../utilities/util.dart';
|
||||
import '../../../wallets/isar/providers/wallet_info_provider.dart';
|
||||
import '../../../wallets/wallet/impl/namecoin_wallet.dart';
|
||||
import '../../../widgets/conditional_parent.dart';
|
||||
import '../../../widgets/desktop/primary_button.dart';
|
||||
import '../../../widgets/dialogs/s_dialog.dart';
|
||||
import '../../../widgets/rounded_white_container.dart';
|
||||
import 'name_details.dart';
|
||||
|
||||
class OwnedNameCard extends ConsumerStatefulWidget {
|
||||
const OwnedNameCard({
|
||||
super.key,
|
||||
required this.opNameData,
|
||||
required this.utxo,
|
||||
this.firstColWidth,
|
||||
this.calculatedFirstColWidth,
|
||||
});
|
||||
|
||||
final OpNameData opNameData;
|
||||
final UTXO utxo;
|
||||
|
||||
final double? firstColWidth;
|
||||
final void Function(double)? calculatedFirstColWidth;
|
||||
|
||||
@override
|
||||
ConsumerState<OwnedNameCard> createState() => _OwnedNameCardState();
|
||||
}
|
||||
|
||||
class _OwnedNameCardState extends ConsumerState<OwnedNameCard> {
|
||||
String? constructedName, value;
|
||||
|
||||
(String, Color) _getExpiry(int currentChainHeight, StackColors theme) {
|
||||
final String message;
|
||||
final Color color;
|
||||
|
||||
if (widget.utxo.blockHash == null) {
|
||||
message = "Expires in $blocksNameExpiration+ blocks";
|
||||
color = theme.accentColorGreen;
|
||||
} else {
|
||||
final remaining = widget.opNameData.expiredBlockLeft(
|
||||
currentChainHeight,
|
||||
false,
|
||||
);
|
||||
final semiRemaining = widget.opNameData.expiredBlockLeft(
|
||||
currentChainHeight,
|
||||
true,
|
||||
);
|
||||
|
||||
if (remaining == null) {
|
||||
color = theme.accentColorRed;
|
||||
message = "Expired";
|
||||
} else {
|
||||
message = "Expires in $remaining blocks";
|
||||
if (semiRemaining == null) {
|
||||
color = theme.accentColorYellow;
|
||||
} else {
|
||||
color = theme.accentColorGreen;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (message, color);
|
||||
}
|
||||
|
||||
bool _lock = false;
|
||||
|
||||
Future<void> _showDetails() async {
|
||||
if (_lock) return;
|
||||
_lock = true;
|
||||
try {
|
||||
if (Util.isDesktop) {
|
||||
await showDialog<void>(
|
||||
context: context,
|
||||
builder: (context) => SDialog(
|
||||
child: NameDetailsView(
|
||||
utxoId: widget.utxo.id,
|
||||
walletId: widget.utxo.walletId,
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
await Navigator.of(context).pushNamed(
|
||||
NameDetailsView.routeName,
|
||||
arguments: (
|
||||
widget.utxo.id,
|
||||
widget.utxo.walletId,
|
||||
),
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
_lock = false;
|
||||
}
|
||||
}
|
||||
|
||||
void _setName() {
|
||||
try {
|
||||
constructedName = widget.opNameData.constructedName;
|
||||
value = widget.opNameData.value;
|
||||
} catch (_) {
|
||||
if (widget.opNameData.op == OpName.nameNew) {
|
||||
ref
|
||||
.read(secureStoreProvider)
|
||||
.read(
|
||||
key: nameSaltKeyBuilder(
|
||||
widget.utxo.txid,
|
||||
widget.utxo.walletId,
|
||||
widget.utxo.vout,
|
||||
),
|
||||
)
|
||||
.then((onValue) {
|
||||
if (onValue != null) {
|
||||
final data = (jsonDecode(onValue) as Map).cast<String, String>();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
constructedName = data["name"]!;
|
||||
value = data["value"]!;
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
constructedName = "UNKNOWN";
|
||||
value = "";
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_setName();
|
||||
}
|
||||
|
||||
double _callbackWidth = 0;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
debugPrint("BUILD: $runtimeType");
|
||||
|
||||
final (message, color) = _getExpiry(
|
||||
ref.watch(pWalletChainHeight(widget.utxo.walletId)),
|
||||
Theme.of(context).extension<StackColors>()!,
|
||||
);
|
||||
|
||||
return RoundedWhiteContainer(
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
ConditionalParent(
|
||||
condition: widget.firstColWidth != null && Util.isDesktop,
|
||||
builder: (child) => ConstrainedBox(
|
||||
constraints: BoxConstraints(maxWidth: widget.firstColWidth!),
|
||||
child: child,
|
||||
),
|
||||
child: ConditionalParent(
|
||||
condition: widget.firstColWidth == null && Util.isDesktop,
|
||||
builder: (child) => LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
if (widget.firstColWidth == null &&
|
||||
_callbackWidth != constraints.maxWidth) {
|
||||
_callbackWidth = constraints.maxWidth;
|
||||
widget.calculatedFirstColWidth?.call(_callbackWidth);
|
||||
}
|
||||
return ConstrainedBox(
|
||||
constraints: BoxConstraints(maxWidth: constraints.maxWidth),
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(right: 12),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SelectableText(constructedName ?? ""),
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
SelectableText(
|
||||
message,
|
||||
style: STextStyles.w500_12(context).copyWith(
|
||||
color: color,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (Util.isDesktop)
|
||||
Expanded(
|
||||
child: SelectableText(
|
||||
value ?? "",
|
||||
style: STextStyles.w500_12(context),
|
||||
),
|
||||
),
|
||||
if (Util.isDesktop)
|
||||
const SizedBox(
|
||||
width: 12,
|
||||
),
|
||||
PrimaryButton(
|
||||
label: "Details",
|
||||
buttonHeight: Util.isDesktop ? ButtonHeight.xs : ButtonHeight.l,
|
||||
onPressed: _showDetails,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue