mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2024-12-22 19:39:22 +00:00
Merge remote-tracking branch 'origin/staging' into tor
This commit is contained in:
commit
169e419c6d
77 changed files with 3953 additions and 2012 deletions
64
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
64
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
|
@ -0,0 +1,64 @@
|
|||
name: 🐞 Bug Report
|
||||
description: File a new bug report
|
||||
title: 'Bug: <title>'
|
||||
labels: [Bug]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: ':stop_sign: _For support questions, please visit the [Discord](https://discord.gg/mRPZuXx3At), [Telegram](https://t.me/stackwallet) or [Reddit](https://www.reddit.com/r/stackwallet/) instead._'
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: 'Is there an existing issue for this?'
|
||||
description: 'Please [search :mag: the issues](https://github.com/cypherstack/stack_wallet/issues) to check if this bug has already been reported.'
|
||||
options:
|
||||
- label: 'I have searched the existing issues'
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: 'Current Behavior'
|
||||
description: 'Describe the problem you are experiencing. **Please do not paste your logs here.** Screenshots are welcome.'
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: 'Expected Behavior'
|
||||
description: 'Describe what you expect to happen instead.'
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: 'Reproduce Steps'
|
||||
description: |
|
||||
Please provide a the _smallest, complete steps_ that Stack Wallet's maintainers can run to reproduce the issue ([read more about what this entails](https://stackoverflow.com/help/minimal-reproducible-example)). Failing this, any sort of reproduction steps are better than nothing!
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: 'Environment'
|
||||
description: 'Please provide the following information about your environment.'
|
||||
value: |
|
||||
- Operating system and version:
|
||||
- Device platform and version:
|
||||
- Real device or emulator/simulator:
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: 'Logs'
|
||||
description: |
|
||||
Create a [Gist](https://gist.github.com) which contains your _full_ Stack Wallet logs and link it here.
|
||||
|
||||
:warning: _Remember to redact or remove any sensitive information!_
|
||||
placeholder: 'https://gist.github.com/...'
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: 'Further Information'
|
||||
description: |
|
||||
Links? References? Anything that will give us more context about the issue you are encountering!
|
||||
validations:
|
||||
required: false
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: ':stop_sign: _For support questions, please visit the [Discord](https://discord.gg/mRPZuXx3At), [Telegram](https://t.me/stackwallet) or [Reddit](https://www.reddit.com/r/stackwallet/) instead._'
|
37
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
37
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
|
@ -0,0 +1,37 @@
|
|||
name: Feature request
|
||||
description: Suggest an idea for this project
|
||||
title: 'FR: <title>'
|
||||
labels: [Feature Request]
|
||||
body:
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: 'Is Problem'
|
||||
description: 'Is your feature request related to a problem? Please describe.'
|
||||
value: |
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: 'Solution'
|
||||
description: 'Describe the solution you'd like.'
|
||||
value: |
|
||||
A clear and concise description of what you want to happen.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: 'Alternatives'
|
||||
description: 'Describe alternatives you've considered.'
|
||||
value: |
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: 'Context'
|
||||
description: 'Additional context.'
|
||||
value: |
|
||||
Add any other context or screenshots about the feature request here.
|
||||
validations:
|
||||
required: false
|
File diff suppressed because one or more lines are too long
3
assets/svg/exchange_icons/trocador.svg
Normal file
3
assets/svg/exchange_icons/trocador.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M20.7254 15.0245C21.0915 15.3907 21.0915 15.9843 20.7254 16.3504L17.6004 19.4747C17.0141 20.0611 16 19.6501 16 18.8118V17.25H13.7037C13.6394 17.25 13.5758 17.2367 13.5169 17.2111C13.4579 17.1855 13.4049 17.1481 13.361 17.1011L10.6049 14.1481L12.6882 11.916L14.75 14.125H16V12.5632C16 11.7258 17.0134 11.3133 17.6004 11.9003L20.7254 15.0245ZM1.46875 7.87499H4.74999L6.81175 10.084L8.89507 7.85187L6.13898 4.8989C6.09512 4.85191 6.04206 4.81444 5.98311 4.78882C5.92415 4.76321 5.86056 4.74999 5.79628 4.74999H1.46875C1.20988 4.74999 1 4.95988 1 5.21874V7.40624C1 7.66511 1.20988 7.87499 1.46875 7.87499ZM16 7.87499V9.43686C16 10.2751 17.0141 10.6861 17.6004 10.0998L20.7254 6.97542C21.0915 6.60929 21.0915 6.0157 20.7254 5.6496L17.6004 2.52535C17.0134 1.93832 16 2.35086 16 3.18824V4.74999H13.7037C13.6394 4.75 13.5758 4.76322 13.5169 4.78884C13.4579 4.81445 13.4049 4.85192 13.361 4.8989L4.74999 14.125H1.46875C1.20988 14.125 1 14.3349 1 14.5937V16.7812C1 17.0401 1.20988 17.25 1.46875 17.25H5.79628C5.9262 17.25 6.05031 17.196 6.13898 17.1011L14.75 7.87499H16Z" fill="#FF3C00" stroke="#FF3C00" stroke-width="0.0585938"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
10
assets/svg/trocador_rating_a.svg
Normal file
10
assets/svg/trocador_rating_a.svg
Normal file
|
@ -0,0 +1,10 @@
|
|||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_7973_141939)">
|
||||
<path d="M10 0C4.47656 0 0 4.47656 0 10C0 15.5234 4.47656 20 10 20C15.5234 20 20 15.5234 20 10C20 4.47656 15.5234 0 10 0ZM14.1445 14.2891C14.017 14.3479 13.8833 14.3761 13.752 14.3761C13.3973 14.3761 13.0586 14.1738 12.9 13.8293L12.4301 12.8066H7.57031L7.10039 13.8293C6.88258 14.3015 6.32852 14.5074 5.85586 14.289C5.38594 14.0719 5.18086 13.5113 5.39766 13.0394L9.14727 4.91051C9.45359 4.24332 10.5438 4.24332 10.85 4.91051L14.5996 13.0394C14.8164 13.5117 14.6133 14.0703 14.1445 14.2891ZM8.40234 10.9219H11.5293L10 7.51953L8.40234 10.9219Z" fill="#00A578" stroke="#00A578" stroke-width="0.0371094"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_7973_141939">
|
||||
<rect width="20" height="20" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 856 B |
10
assets/svg/trocador_rating_b.svg
Normal file
10
assets/svg/trocador_rating_b.svg
Normal file
|
@ -0,0 +1,10 @@
|
|||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_7973_141943)">
|
||||
<path d="M11.4062 10.9375H8.125V13.125H11.4062C12.0078 13.125 12.5 12.6328 12.5 12.0312C12.5 11.4297 12.0078 10.9375 11.4062 10.9375ZM11.875 7.96875C11.875 7.36562 11.3844 6.875 10.7812 6.875H8.125V9.0625H10.7812C11.3828 9.0625 11.875 8.57031 11.875 7.96875ZM10 0C4.47656 0 0 4.47656 0 10C0 15.5234 4.47656 20 10 20C15.5234 20 20 15.5234 20 10C20 4.47656 15.5234 0 10 0ZM11.4062 15H7.1875C6.67188 15 6.25 14.582 6.25 14.0625V5.9375C6.25 5.42188 6.67188 5 7.1875 5H10.7812C12.418 5 13.75 6.33203 13.75 7.96875C13.75 8.60742 13.5429 9.1957 13.1984 9.68047C13.9102 10.2227 14.375 11.0703 14.375 12.0312C14.375 13.668 13.043 15 11.4062 15Z" fill="#7AA500" stroke="#7AA500" stroke-width="0.0371094"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_7973_141943">
|
||||
<rect width="20" height="20" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 949 B |
10
assets/svg/trocador_rating_c.svg
Normal file
10
assets/svg/trocador_rating_c.svg
Normal file
|
@ -0,0 +1,10 @@
|
|||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_7973_141958)">
|
||||
<path d="M10 0C4.47656 0 0 4.47656 0 10C0 15.5234 4.47656 20 10 20C15.5234 20 20 15.5234 20 10C20 4.47656 15.5234 0 10 0ZM9.96484 13.125C10.7887 13.125 11.5633 12.8015 12.1469 12.2117C12.5112 11.8455 13.1059 11.8406 13.4727 12.2068C13.8407 12.5706 13.8431 13.1639 13.4788 13.5326C12.5391 14.4805 11.293 15 9.96484 15C6.88086 15 5.00391 12.409 5.00391 10C5.0036 7.61289 6.87383 5 9.96484 5C11.2922 5 12.541 5.52109 13.4789 6.46719C13.8438 6.83594 13.8398 7.42969 13.4727 7.79297C13.1058 8.15918 12.5113 8.15398 12.1469 7.78778C11.5625 7.19922 10.7891 6.875 9.96484 6.875C8.03516 6.875 6.875 8.51172 6.875 10C6.875 11.4805 8.03125 13.125 9.96484 13.125Z" fill="#F4C517" stroke="#F4C517" stroke-width="0.0371094"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_7973_141958">
|
||||
<rect width="20" height="20" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 965 B |
10
assets/svg/trocador_rating_d.svg
Normal file
10
assets/svg/trocador_rating_d.svg
Normal file
|
@ -0,0 +1,10 @@
|
|||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_7973_141964)">
|
||||
<path d="M9.9375 6.875H8.125V13.125H9.93789C11.6953 13.125 13.125 11.7227 13.125 10C13.125 8.27734 11.6953 6.875 9.9375 6.875ZM10 0C4.47656 0 0 4.47656 0 10C0 15.5234 4.47656 20 10 20C15.5234 20 20 15.5234 20 10C20 4.47656 15.5234 0 10 0ZM9.9375 15H7.1875C6.67188 15 6.25 14.582 6.25 14.0625V5.9375C6.25 5.42188 6.67188 5 7.1875 5H9.93789C12.7305 5 15 7.24219 15 10C15 12.7578 12.7305 15 9.9375 15Z" fill="#F37B58" stroke="#F37B58" stroke-width="0.0371094"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_7973_141964">
|
||||
<rect width="20" height="20" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 712 B |
|
@ -1 +1 @@
|
|||
Subproject commit b8222a95ad31885258ea341fabd9bc3a2608efce
|
||||
Subproject commit 398077d745bfb8e27c6fcb3fae971908566b2222
|
|
@ -2,16 +2,36 @@
|
|||
|
||||
Here you will find instructions on how to install the necessary tools for building and running the app.
|
||||
|
||||
### Prerequisites
|
||||
## Prerequisites
|
||||
|
||||
- The OS'es supported for building is Ubuntu (20.04) and Fedora (37 - Work In Progress)
|
||||
- A machine with at least 100 GB of Storage
|
||||
|
||||
The following prerequisites can be installed with the setup script [`scripts/setup.sh`](./../scripts/setup.sh) or manually as described below:
|
||||
|
||||
- Flutter 3.7.11 [(install manually or with git, do not install with snap)](https://docs.flutter.dev/get-started/install)
|
||||
- Dart SDK Requirement (>=2.19.0, up until <3.0.0) (normally included with a flutter install)
|
||||
- The only OS supported for building is Ubuntu 20.04. Advanced users may also be able to build on other Debian-based distributions like Linux Mint.
|
||||
- Android setup ([Android Studio](https://developer.android.com/studio) and subsequent dependencies)
|
||||
- 100 GB of storage
|
||||
|
||||
Install Android Studio following the instructions below before proceeding, then the following prerequisites can be installed with the setup script [`scripts/setup.sh`](./../scripts/setup.sh) or manually as described below:
|
||||
|
||||
- Flutter 3.7.12 [(install manually or with git, do not install with snap)](https://docs.flutter.dev/get-started/install)
|
||||
- Dart SDK Requirement (>=2.19.0, up until <3.0.0) (normally included with a flutter install)
|
||||
|
||||
### Android Studio
|
||||
Android Studio is the recommended IDE for development, not just for launching on Android devices and emulators but also for Linux desktop development.
|
||||
|
||||
Follow instructions here [https://developer.android.com/studio/install#linux](https://developer.android.com/studio/install#linux) or install via snap:
|
||||
```
|
||||
# setup android studio
|
||||
sudo apt install -y openjdk-11-jdk
|
||||
sudo snap install android-studio --classic
|
||||
```
|
||||
|
||||
Use Tools > SDK Manager to install:
|
||||
- SDK Tools > Android SDK (API 30)
|
||||
- SDK Tools > NDK
|
||||
- SDK Tools > Android SDK command line tools
|
||||
- SDK Tools > CMake
|
||||
|
||||
Then in File > Settings > Plugins, install the Flutter plugin and restart the IDE. In File > Settings > Languages & Frameworks > Flutter > Editor, enable auto format on save to match the project's code style. If you have problems with the Dart SDK, make sure to run `flutter` in a terminal to download it (use `source ~/.bashrc` to update your environment variables if you're still using the same terminal from which you ran `setup.sh`)
|
||||
|
||||
Make a Pixel 4 (API 30) x86_64 emulator with 2GB of storage space for emulation
|
||||
|
||||
### Scripted setup
|
||||
|
||||
|
@ -19,14 +39,7 @@ The following prerequisites can be installed with the setup script [`scripts/set
|
|||
|
||||
### Manual setup
|
||||
|
||||
> If you have installed with script, skip to [running](#running)
|
||||
|
||||
Please go to your Linux distribution's title below for instructions on how to manually setup:
|
||||
|
||||
- [Ubuntu (20.04)](#ubuntu-2004)
|
||||
- [Fedora (37) (Work In Progress)](#fedora-37-work-in-progress)
|
||||
|
||||
#### Ubuntu (20.04)
|
||||
> If you used the `setup.sh` script, skip to [running](#running)
|
||||
|
||||
Install basic dependencies
|
||||
```
|
||||
|
@ -41,6 +54,7 @@ sudo apt-get install libc6:i386 libncurses5:i386 libstdc++6:i386 lib32z1 libbz2-
|
|||
Install [Rust](https://www.rust-lang.org/tools/install) with command:
|
||||
```
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||
source ~/.bashrc
|
||||
rustup install 1.67.1
|
||||
rustup default 1.67.1
|
||||
```
|
||||
|
@ -105,10 +119,6 @@ cd scripts/linux/
|
|||
cd ../..
|
||||
```
|
||||
|
||||
#### Fedora (37) (Work In Progress)
|
||||
|
||||
This is a work in progress, please use Ubuntu for now.
|
||||
|
||||
## Running
|
||||
### Android
|
||||
Plug in your android device or use the emulator available via Android Studio and then run the following commands:
|
||||
|
@ -125,19 +135,3 @@ Plug in your android device or use the emulator available via Android Studio and
|
|||
flutter pub get Linux
|
||||
flutter run linux
|
||||
```
|
||||
|
||||
## Android Studio
|
||||
Android Studio is the recommended IDE for development, not just for launching on Android devices and emulators but also for Linux desktop development.
|
||||
|
||||
Follow instructions here [https://developer.android.com/studio/install#linux](https://developer.android.com/studio/install#linux) or install via snap:
|
||||
```
|
||||
# setup android studio
|
||||
sudo apt install -y openjdk-11-jdk
|
||||
sudo snap install android-studio --classic
|
||||
```
|
||||
|
||||
Use Tools > SDK Manager to install the SDK Tools > Android SDK (API 30), SDK Tools > NDK, SDK Tools > Android SDK command line tools, and SDK Tools > CMake
|
||||
|
||||
Then install the Flutter plugin and restart the IDE. In Android Studio's options for the Flutter language, enable auto format on save to match the project's code style. If you have problems with the Dart SDK, make sure to run `flutter` in a terminal to download it (use `source ~/.bashrc` to update your environment variables if you're still using the same terminal from which you ran `setup.sh`)
|
||||
|
||||
Make a Pixel 4 (API 30) x86_64 emulator with 2GB of storage space for emulation
|
||||
|
|
|
@ -458,7 +458,7 @@
|
|||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 102;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
DEVELOPMENT_TEAM = 4DQKUWSG6C;
|
||||
ENABLE_BITCODE = NO;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
|
@ -512,7 +512,7 @@
|
|||
"$(PROJECT_DIR)/../crypto_plugins/flutter_libmonero/cw_shared_external/ios/External/ios/**",
|
||||
"$(PROJECT_DIR)/../crypto_plugins/flutter_libepiccash/ios/libs",
|
||||
);
|
||||
MARKETING_VERSION = 1.5.28;
|
||||
MARKETING_VERSION = "$(FLUTTER_BUILD_NAME)";
|
||||
ONLY_ACTIVE_ARCH = NO;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.cypherstack.stackwallet;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
|
@ -645,7 +645,7 @@
|
|||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 102;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
DEVELOPMENT_TEAM = 4DQKUWSG6C;
|
||||
ENABLE_BITCODE = NO;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
|
@ -699,7 +699,7 @@
|
|||
"$(PROJECT_DIR)/../crypto_plugins/flutter_libmonero/cw_shared_external/ios/External/ios/**",
|
||||
"$(PROJECT_DIR)/../crypto_plugins/flutter_libepiccash/ios/libs",
|
||||
);
|
||||
MARKETING_VERSION = 1.5.28;
|
||||
MARKETING_VERSION = "$(FLUTTER_BUILD_NAME)";
|
||||
ONLY_ACTIVE_ARCH = NO;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.cypherstack.stackwallet;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
|
@ -724,7 +724,7 @@
|
|||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 102;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
DEVELOPMENT_TEAM = 4DQKUWSG6C;
|
||||
ENABLE_BITCODE = NO;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
|
@ -778,7 +778,7 @@
|
|||
"$(PROJECT_DIR)/../crypto_plugins/flutter_libmonero/cw_shared_external/ios/External/ios/**",
|
||||
"$(PROJECT_DIR)/../crypto_plugins/flutter_libepiccash/ios/libs",
|
||||
);
|
||||
MARKETING_VERSION = 1.5.28;
|
||||
MARKETING_VERSION = "$(FLUTTER_BUILD_NAME)";
|
||||
ONLY_ACTIVE_ARCH = NO;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.cypherstack.stackwallet;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
|
|
|
@ -2,6 +2,7 @@ import 'package:decimal/decimal.dart';
|
|||
import 'package:flutter_native_splash/cli_commands.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:stackwallet/exceptions/main_db/main_db_exception.dart';
|
||||
import 'package:stackwallet/models/isar/models/block_explorer.dart';
|
||||
import 'package:stackwallet/models/isar/models/isar_models.dart';
|
||||
import 'package:stackwallet/utilities/amount/amount.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
|
@ -33,6 +34,7 @@ class MainDB {
|
|||
AddressSchema,
|
||||
AddressLabelSchema,
|
||||
EthContractSchema,
|
||||
TransactionBlockExplorerSchema,
|
||||
],
|
||||
directory: (await StackFileSystem.applicationIsarDirectory()).path,
|
||||
// inspector: kDebugMode,
|
||||
|
@ -43,6 +45,25 @@ class MainDB {
|
|||
return true;
|
||||
}
|
||||
|
||||
// tx block explorers
|
||||
TransactionBlockExplorer? getTransactionBlockExplorer({required Coin coin}) {
|
||||
return isar.transactionBlockExplorers
|
||||
.where()
|
||||
.tickerEqualTo(coin.ticker)
|
||||
.findFirstSync();
|
||||
}
|
||||
|
||||
Future<int> putTransactionBlockExplorer(
|
||||
TransactionBlockExplorer explorer) async {
|
||||
try {
|
||||
return await isar.writeTxn(() async {
|
||||
return await isar.transactionBlockExplorers.put(explorer);
|
||||
});
|
||||
} catch (e) {
|
||||
throw MainDBException("failed putTransactionBlockExplorer: $explorer", e);
|
||||
}
|
||||
}
|
||||
|
||||
// addresses
|
||||
QueryBuilder<Address, Address, QAfterWhereClause> getAddresses(
|
||||
String walletId) =>
|
||||
|
|
|
@ -299,7 +299,8 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
|
|||
await ref.read(prefsChangeNotifierProvider).isExternalCallsSet()) {
|
||||
if (Constants.enableExchange) {
|
||||
await ExchangeDataLoadingService.instance.setCurrenciesIfEmpty(
|
||||
ref.read(exchangeFormStateProvider),
|
||||
ref.read(efCurrencyPairProvider),
|
||||
ref.read(efRateTypeProvider),
|
||||
);
|
||||
unawaited(ExchangeDataLoadingService.instance.loadAll());
|
||||
}
|
||||
|
|
35
lib/models/exchange/active_pair.dart
Normal file
35
lib/models/exchange/active_pair.dart
Normal file
|
@ -0,0 +1,35 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:stackwallet/models/exchange/aggregate_currency.dart';
|
||||
|
||||
class ActivePair extends ChangeNotifier {
|
||||
AggregateCurrency? _send;
|
||||
AggregateCurrency? _receive;
|
||||
|
||||
AggregateCurrency? get send => _send;
|
||||
AggregateCurrency? get receive => _receive;
|
||||
|
||||
void setSend(
|
||||
AggregateCurrency? newSend, {
|
||||
bool notifyListeners = false,
|
||||
}) {
|
||||
_send = newSend;
|
||||
if (notifyListeners) {
|
||||
this.notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
void setReceive(
|
||||
AggregateCurrency? newReceive, {
|
||||
bool notifyListeners = false,
|
||||
}) {
|
||||
_receive = newReceive;
|
||||
if (notifyListeners) {
|
||||
this.notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return "ActivePair{ send: $send, receive: $receive }";
|
||||
}
|
||||
}
|
|
@ -5,8 +5,9 @@ import 'package:tuple/tuple.dart';
|
|||
class AggregateCurrency {
|
||||
final Map<String, Currency?> _map = {};
|
||||
|
||||
AggregateCurrency(
|
||||
{required List<Tuple2<String, Currency>> exchangeCurrencyPairs}) {
|
||||
AggregateCurrency({
|
||||
required List<Tuple2<String, Currency>> exchangeCurrencyPairs,
|
||||
}) {
|
||||
assert(exchangeCurrencyPairs.isNotEmpty);
|
||||
|
||||
for (final item in exchangeCurrencyPairs) {
|
||||
|
|
|
@ -1,519 +0,0 @@
|
|||
import 'package:decimal/decimal.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:stackwallet/models/exchange/aggregate_currency.dart';
|
||||
import 'package:stackwallet/models/exchange/response_objects/estimate.dart';
|
||||
import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart';
|
||||
import 'package:stackwallet/services/exchange/exchange.dart';
|
||||
import 'package:stackwallet/services/exchange/majestic_bank/majestic_bank_exchange.dart';
|
||||
import 'package:stackwallet/utilities/enums/exchange_rate_type_enum.dart';
|
||||
import 'package:stackwallet/utilities/logger.dart';
|
||||
|
||||
class ExchangeFormState extends ChangeNotifier {
|
||||
Exchange? _exchange;
|
||||
Exchange get exchange => _exchange ??= Exchange.defaultExchange;
|
||||
|
||||
ExchangeRateType _exchangeRateType = ExchangeRateType.estimated;
|
||||
ExchangeRateType get exchangeRateType => _exchangeRateType;
|
||||
set exchangeRateType(ExchangeRateType exchangeRateType) {
|
||||
_exchangeRateType = exchangeRateType;
|
||||
//
|
||||
}
|
||||
|
||||
Estimate? _estimate;
|
||||
Estimate? get estimate => _estimate;
|
||||
|
||||
bool _reversed = false;
|
||||
bool get reversed => _reversed;
|
||||
set reversed(bool reversed) {
|
||||
_reversed = reversed;
|
||||
//
|
||||
}
|
||||
|
||||
Decimal? _rate;
|
||||
Decimal? get rate => _rate;
|
||||
// set rate(Decimal? rate) {
|
||||
// _rate = rate;
|
||||
// //
|
||||
// }
|
||||
|
||||
Decimal? _sendAmount;
|
||||
Decimal? get sendAmount => _sendAmount;
|
||||
// set sendAmount(Decimal? sendAmount) {
|
||||
// _sendAmount = sendAmount;
|
||||
// //
|
||||
// }
|
||||
|
||||
Decimal? _receiveAmount;
|
||||
Decimal? get receiveAmount => _receiveAmount;
|
||||
set receiveAmount(Decimal? receiveAmount) {
|
||||
_receiveAmount = receiveAmount;
|
||||
//
|
||||
}
|
||||
|
||||
AggregateCurrency? _sendCurrency;
|
||||
AggregateCurrency? get sendCurrency => _sendCurrency;
|
||||
// set sendCurrency(Currency? sendCurrency) {
|
||||
// _sendCurrency = sendCurrency;
|
||||
// //
|
||||
// }
|
||||
|
||||
AggregateCurrency? _receiveCurrency;
|
||||
AggregateCurrency? get receiveCurrency => _receiveCurrency;
|
||||
// set receiveCurrency(Currency? receiveCurrency) {
|
||||
// _receiveCurrency = receiveCurrency;
|
||||
// //
|
||||
// }
|
||||
|
||||
Decimal? _minSendAmount;
|
||||
Decimal? get minSendAmount => _minSendAmount;
|
||||
// set minSendAmount(Decimal? minSendAmount) {
|
||||
// _minSendAmount = minSendAmount;
|
||||
// //
|
||||
// }
|
||||
|
||||
Decimal? _minReceiveAmount;
|
||||
Decimal? get minReceiveAmount => _minReceiveAmount;
|
||||
// set minReceiveAmount(Decimal? minReceiveAmount) {
|
||||
// _minReceiveAmount = minReceiveAmount;
|
||||
// //
|
||||
// }
|
||||
|
||||
Decimal? _maxSendAmount;
|
||||
Decimal? get maxSendAmount => _maxSendAmount;
|
||||
// set maxSendAmount(Decimal? maxSendAmount) {
|
||||
// _maxSendAmount = maxSendAmount;
|
||||
// //
|
||||
// }
|
||||
|
||||
Decimal? _maxReceiveAmount;
|
||||
Decimal? get maxReceiveAmount => _maxReceiveAmount;
|
||||
// set maxReceiveAmount(Decimal? maxReceiveAmount) {
|
||||
// _maxReceiveAmount = maxReceiveAmount;
|
||||
// //
|
||||
// }
|
||||
|
||||
//============================================================================
|
||||
// computed properties
|
||||
//============================================================================
|
||||
|
||||
String? get fromTicker => _sendCurrency?.ticker;
|
||||
String? get toTicker => _receiveCurrency?.ticker;
|
||||
|
||||
String get fromAmountString => _sendAmount?.toStringAsFixed(8) ?? "";
|
||||
String get toAmountString => _receiveAmount?.toStringAsFixed(8) ?? "";
|
||||
|
||||
bool get canExchange {
|
||||
return sendCurrency != null &&
|
||||
receiveCurrency != null &&
|
||||
sendAmount != null &&
|
||||
sendAmount! >= Decimal.zero &&
|
||||
receiveAmount != null &&
|
||||
rate != null &&
|
||||
rate! >= Decimal.zero &&
|
||||
sendCurrency!.forExchange(exchange.name) != null &&
|
||||
receiveCurrency!.forExchange(exchange.name) != null &&
|
||||
warning.isEmpty;
|
||||
}
|
||||
|
||||
String get warning {
|
||||
if (reversed) {
|
||||
if (_receiveCurrency != null && _receiveAmount != null) {
|
||||
if (_minReceiveAmount != null &&
|
||||
_receiveAmount! < _minReceiveAmount! &&
|
||||
_receiveAmount! > Decimal.zero) {
|
||||
return "Min receive amount ${_minReceiveAmount!.toString()} ${_receiveCurrency!.ticker.toUpperCase()}";
|
||||
} else if (_maxReceiveAmount != null &&
|
||||
_receiveAmount! > _maxReceiveAmount!) {
|
||||
return "Max receive amount ${_maxReceiveAmount!.toString()} ${_receiveCurrency!.ticker.toUpperCase()}";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (_sendCurrency != null && _sendAmount != null) {
|
||||
if (_minSendAmount != null &&
|
||||
_sendAmount! < _minSendAmount! &&
|
||||
_sendAmount! > Decimal.zero) {
|
||||
return "Min send amount ${_minSendAmount!.toString()} ${_sendCurrency!.ticker.toUpperCase()}";
|
||||
} else if (_maxSendAmount != null && _sendAmount! > _maxSendAmount!) {
|
||||
return "Max send amount ${_maxSendAmount!.toString()} ${_sendCurrency!.ticker.toUpperCase()}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
//============================================================================
|
||||
// public state updaters
|
||||
//============================================================================
|
||||
|
||||
Future<void> updateExchange({
|
||||
required Exchange exchange,
|
||||
required bool shouldUpdateData,
|
||||
required bool shouldNotifyListeners,
|
||||
}) async {
|
||||
_exchange = exchange;
|
||||
if (shouldUpdateData) {
|
||||
await _updateRangesAndEstimate(
|
||||
shouldNotifyListeners: false,
|
||||
);
|
||||
}
|
||||
|
||||
if (shouldNotifyListeners) {
|
||||
_notify();
|
||||
}
|
||||
}
|
||||
|
||||
void setCurrencies(AggregateCurrency? from, AggregateCurrency? to) {
|
||||
_sendCurrency = from;
|
||||
_receiveCurrency = to;
|
||||
}
|
||||
|
||||
void reset({
|
||||
required bool shouldNotifyListeners,
|
||||
}) {
|
||||
_exchange = null;
|
||||
_reversed = false;
|
||||
_rate = null;
|
||||
_sendAmount = null;
|
||||
_receiveAmount = null;
|
||||
_sendCurrency = null;
|
||||
_receiveCurrency = null;
|
||||
_minSendAmount = null;
|
||||
_minReceiveAmount = null;
|
||||
_maxSendAmount = null;
|
||||
_maxReceiveAmount = null;
|
||||
|
||||
if (shouldNotifyListeners) {
|
||||
_notify();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> setSendAmountAndCalculateReceiveAmount(
|
||||
Decimal? newSendAmount,
|
||||
bool shouldNotifyListeners,
|
||||
) async {
|
||||
if (newSendAmount == null) {
|
||||
// todo: check if this breaks things and stuff
|
||||
_receiveAmount = null;
|
||||
_sendAmount = null;
|
||||
} else {
|
||||
if (newSendAmount <= Decimal.zero) {
|
||||
_receiveAmount = Decimal.zero;
|
||||
}
|
||||
|
||||
_sendAmount = newSendAmount;
|
||||
_reversed = false;
|
||||
|
||||
await _updateRangesAndEstimate(
|
||||
shouldNotifyListeners: false,
|
||||
);
|
||||
}
|
||||
|
||||
if (shouldNotifyListeners) {
|
||||
_notify();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> setReceivingAmountAndCalculateSendAmount(
|
||||
Decimal? newReceiveAmount,
|
||||
bool shouldNotifyListeners,
|
||||
) async {
|
||||
if (newReceiveAmount == null) {
|
||||
// todo: check if this breaks things and stuff
|
||||
_receiveAmount = null;
|
||||
_sendAmount = null;
|
||||
} else {
|
||||
if (newReceiveAmount <= Decimal.zero) {
|
||||
_sendAmount = Decimal.zero;
|
||||
}
|
||||
|
||||
_receiveAmount = newReceiveAmount;
|
||||
_reversed = true;
|
||||
|
||||
await _updateRangesAndEstimate(
|
||||
shouldNotifyListeners: false,
|
||||
);
|
||||
}
|
||||
|
||||
if (shouldNotifyListeners) {
|
||||
_notify();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> updateSendCurrency(
|
||||
AggregateCurrency sendCurrency,
|
||||
bool shouldNotifyListeners,
|
||||
) async {
|
||||
try {
|
||||
_sendCurrency = sendCurrency;
|
||||
_minSendAmount = null;
|
||||
_maxSendAmount = null;
|
||||
|
||||
if (_receiveCurrency == null) {
|
||||
_rate = null;
|
||||
} else {
|
||||
await _updateRangesAndEstimate(
|
||||
shouldNotifyListeners: false,
|
||||
);
|
||||
}
|
||||
if (shouldNotifyListeners) {
|
||||
_notify();
|
||||
}
|
||||
} catch (e, s) {
|
||||
Logging.instance.log("$e\n$s", level: LogLevel.Error);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> updateReceivingCurrency(
|
||||
AggregateCurrency receiveCurrency,
|
||||
bool shouldNotifyListeners,
|
||||
) async {
|
||||
try {
|
||||
_receiveCurrency = receiveCurrency;
|
||||
_minReceiveAmount = null;
|
||||
_maxReceiveAmount = null;
|
||||
|
||||
if (_sendCurrency == null) {
|
||||
_rate = null;
|
||||
} else {
|
||||
await _updateRangesAndEstimate(
|
||||
shouldNotifyListeners: false,
|
||||
);
|
||||
}
|
||||
if (shouldNotifyListeners) {
|
||||
_notify();
|
||||
}
|
||||
} catch (e, s) {
|
||||
Logging.instance.log("$e\n$s", level: LogLevel.Error);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> swap({
|
||||
required bool shouldNotifyListeners,
|
||||
}) async {
|
||||
final Decimal? temp = sendAmount;
|
||||
_sendAmount = receiveAmount;
|
||||
_receiveAmount = temp;
|
||||
|
||||
_minSendAmount = null;
|
||||
_maxSendAmount = null;
|
||||
_minReceiveAmount = null;
|
||||
_maxReceiveAmount = null;
|
||||
|
||||
final AggregateCurrency? tmp = sendCurrency;
|
||||
_sendCurrency = receiveCurrency;
|
||||
_receiveCurrency = tmp;
|
||||
|
||||
await _updateRangesAndEstimate(
|
||||
shouldNotifyListeners: false,
|
||||
);
|
||||
|
||||
if (shouldNotifyListeners) {
|
||||
_notify();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> refresh() => _updateRangesAndEstimate(
|
||||
shouldNotifyListeners: true,
|
||||
);
|
||||
|
||||
//============================================================================
|
||||
// private state updaters
|
||||
//============================================================================
|
||||
|
||||
Future<void> _updateRangesAndEstimate({
|
||||
required bool shouldNotifyListeners,
|
||||
}) async {
|
||||
try {
|
||||
switch (exchange.name) {
|
||||
case ChangeNowExchange.exchangeName:
|
||||
if (!_exchangeSupported(
|
||||
exchangeName: exchange.name,
|
||||
sendCurrency: sendCurrency,
|
||||
receiveCurrency: receiveCurrency,
|
||||
exchangeRateType: exchangeRateType,
|
||||
)) {
|
||||
_exchange = MajesticBankExchange.instance;
|
||||
}
|
||||
break;
|
||||
case MajesticBankExchange.exchangeName:
|
||||
if (!_exchangeSupported(
|
||||
exchangeName: exchange.name,
|
||||
sendCurrency: sendCurrency,
|
||||
receiveCurrency: receiveCurrency,
|
||||
exchangeRateType: exchangeRateType,
|
||||
)) {
|
||||
_exchange = ChangeNowExchange.instance;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
await _updateRanges(shouldNotifyListeners: false);
|
||||
await _updateEstimate(shouldNotifyListeners: false);
|
||||
if (shouldNotifyListeners) {
|
||||
_notify();
|
||||
}
|
||||
} catch (_) {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _updateRanges({
|
||||
required bool shouldNotifyListeners,
|
||||
}) async {
|
||||
// if (exchange?.name == SimpleSwapExchange.exchangeName) {
|
||||
// reversed = false;
|
||||
// }
|
||||
final _send = sendCurrency;
|
||||
final _receive = receiveCurrency;
|
||||
if (_send == null || _receive == null) {
|
||||
Logging.instance.log(
|
||||
"Tried to $runtimeType.updateRanges where ( $_send || $_receive) for: $exchange",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
return;
|
||||
}
|
||||
final response = await exchange.getRange(
|
||||
_send.ticker,
|
||||
_receive.ticker,
|
||||
exchangeRateType == ExchangeRateType.fixed,
|
||||
);
|
||||
|
||||
if (response.value == null) {
|
||||
Logging.instance.log(
|
||||
"Tried to $runtimeType.updateRanges for: $exchange where response: $response",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
return;
|
||||
}
|
||||
final responseReversed = await exchange.getRange(
|
||||
_receive.ticker,
|
||||
_send.ticker,
|
||||
exchangeRateType == ExchangeRateType.fixed,
|
||||
);
|
||||
|
||||
if (responseReversed.value == null) {
|
||||
Logging.instance.log(
|
||||
"Tried to $runtimeType.updateRanges for: $exchange where response: $responseReversed",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
final range = response.value!;
|
||||
final rangeReversed = responseReversed.value!;
|
||||
|
||||
_minSendAmount = range.min;
|
||||
_maxSendAmount = range.max;
|
||||
_minReceiveAmount = rangeReversed.min;
|
||||
_maxReceiveAmount = rangeReversed.max;
|
||||
|
||||
//todo: check if print needed
|
||||
// debugPrint(
|
||||
// "updated range for: $exchange for $_fromTicker-$_toTicker: $range");
|
||||
|
||||
if (shouldNotifyListeners) {
|
||||
_notify();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _updateEstimate({
|
||||
required bool shouldNotifyListeners,
|
||||
}) async {
|
||||
// if (exchange?.name == SimpleSwapExchange.exchangeName) {
|
||||
// reversed = false;
|
||||
// }
|
||||
final amount = reversed ? receiveAmount : sendAmount;
|
||||
if (sendCurrency == null ||
|
||||
receiveCurrency == null ||
|
||||
amount == null ||
|
||||
amount <= Decimal.zero) {
|
||||
Logging.instance.log(
|
||||
"Tried to $runtimeType.updateEstimate for: $exchange where (from: $sendCurrency || to: $receiveCurrency || amount: $amount)",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
return;
|
||||
}
|
||||
final response = await exchange.getEstimate(
|
||||
sendCurrency!.ticker,
|
||||
receiveCurrency!.ticker,
|
||||
amount,
|
||||
exchangeRateType == ExchangeRateType.fixed,
|
||||
reversed,
|
||||
);
|
||||
|
||||
if (response.value == null) {
|
||||
Logging.instance.log(
|
||||
"Tried to $runtimeType.updateEstimate for: $exchange where response: $response",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
_estimate = response.value!;
|
||||
|
||||
if (reversed) {
|
||||
_sendAmount = _estimate!.estimatedAmount;
|
||||
} else {
|
||||
_receiveAmount = _estimate!.estimatedAmount;
|
||||
}
|
||||
|
||||
_rate =
|
||||
(receiveAmount! / sendAmount!).toDecimal(scaleOnInfinitePrecision: 12);
|
||||
|
||||
//todo: check if print needed
|
||||
// debugPrint(
|
||||
// "updated estimate for: $exchange for $fromTicker-$toTicker: $estimate");
|
||||
|
||||
if (shouldNotifyListeners) {
|
||||
_notify();
|
||||
}
|
||||
}
|
||||
|
||||
//============================================================================
|
||||
|
||||
void _notify() {
|
||||
debugPrint("ExFState NOTIFY: ${toString()}");
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
bool _exchangeSupported({
|
||||
required String exchangeName,
|
||||
required AggregateCurrency? sendCurrency,
|
||||
required AggregateCurrency? receiveCurrency,
|
||||
required ExchangeRateType exchangeRateType,
|
||||
}) {
|
||||
final send = sendCurrency?.forExchange(exchangeName);
|
||||
if (send == null) return false;
|
||||
|
||||
final rcv = receiveCurrency?.forExchange(exchangeName);
|
||||
if (rcv == null) return false;
|
||||
|
||||
if (exchangeRateType == ExchangeRateType.fixed) {
|
||||
return send.supportsFixedRate && rcv.supportsFixedRate;
|
||||
} else {
|
||||
return send.supportsEstimatedRate && rcv.supportsEstimatedRate;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return "{"
|
||||
"\n\t exchange: $exchange,"
|
||||
"\n\t exchangeRateType: $exchangeRateType,"
|
||||
"\n\t sendCurrency: $sendCurrency,"
|
||||
"\n\t receiveCurrency: $receiveCurrency,"
|
||||
"\n\t rate: $rate,"
|
||||
"\n\t reversed: $reversed,"
|
||||
"\n\t sendAmount: $sendAmount,"
|
||||
"\n\t receiveAmount: $receiveAmount,"
|
||||
"\n\t estimate: $estimate,"
|
||||
"\n\t minSendAmount: $minSendAmount,"
|
||||
"\n\t maxSendAmount: $maxSendAmount,"
|
||||
"\n\t minReceiveAmount: $minReceiveAmount,"
|
||||
"\n\t maxReceiveAmount: $maxReceiveAmount,"
|
||||
"\n\t canExchange: $canExchange,"
|
||||
"\n\t warning: $warning,"
|
||||
"\n}";
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:decimal/decimal.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:stackwallet/models/exchange/response_objects/estimate.dart';
|
||||
import 'package:stackwallet/models/exchange/response_objects/trade.dart';
|
||||
import 'package:stackwallet/utilities/enums/exchange_rate_type_enum.dart';
|
||||
|
||||
|
@ -39,13 +40,13 @@ class IncompleteExchangeModel extends ChangeNotifier {
|
|||
}
|
||||
}
|
||||
|
||||
String? _rateId;
|
||||
Estimate? _estimate;
|
||||
|
||||
String? get rateId => _rateId;
|
||||
Estimate? get estimate => _estimate;
|
||||
|
||||
set rateId(String? rateId) {
|
||||
if (_rateId != rateId) {
|
||||
_rateId = rateId;
|
||||
set estimate(Estimate? estimate) {
|
||||
if (_estimate != estimate) {
|
||||
_estimate = estimate;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
@ -70,6 +71,6 @@ class IncompleteExchangeModel extends ChangeNotifier {
|
|||
required this.rateType,
|
||||
required this.reversed,
|
||||
required this.walletInitiated,
|
||||
String? rateId,
|
||||
}) : _rateId = rateId;
|
||||
Estimate? estimate,
|
||||
}) : _estimate = estimate;
|
||||
}
|
||||
|
|
|
@ -7,6 +7,8 @@ class Estimate {
|
|||
final bool reversed;
|
||||
final String? warningMessage;
|
||||
final String? rateId;
|
||||
final String exchangeProvider;
|
||||
final String? kycRating;
|
||||
|
||||
Estimate({
|
||||
required this.estimatedAmount,
|
||||
|
@ -14,9 +16,15 @@ class Estimate {
|
|||
required this.reversed,
|
||||
this.warningMessage,
|
||||
this.rateId,
|
||||
required this.exchangeProvider,
|
||||
this.kycRating,
|
||||
});
|
||||
|
||||
factory Estimate.fromMap(Map<String, dynamic> map) {
|
||||
factory Estimate.fromMap(
|
||||
Map<String, dynamic> map, {
|
||||
required String exchangeProvider,
|
||||
String? kycRating,
|
||||
}) {
|
||||
try {
|
||||
return Estimate(
|
||||
estimatedAmount: Decimal.parse(map["estimatedAmount"] as String),
|
||||
|
@ -24,6 +32,8 @@ class Estimate {
|
|||
reversed: map["reversed"] as bool,
|
||||
warningMessage: map["warningMessage"] as String?,
|
||||
rateId: map["rateId"] as String?,
|
||||
exchangeProvider: exchangeProvider,
|
||||
kycRating: kycRating,
|
||||
);
|
||||
} catch (e, s) {
|
||||
Logging.instance.log("Estimate.fromMap(): $e\n$s", level: LogLevel.Error);
|
||||
|
@ -38,6 +48,8 @@ class Estimate {
|
|||
"reversed": reversed,
|
||||
"warningMessage": warningMessage,
|
||||
"rateId": rateId,
|
||||
"exchangeProvider": exchangeProvider,
|
||||
"kycRating": kycRating,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
35
lib/models/isar/models/block_explorer.dart
Normal file
35
lib/models/isar/models/block_explorer.dart
Normal file
|
@ -0,0 +1,35 @@
|
|||
import 'package:isar/isar.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
|
||||
part 'block_explorer.g.dart';
|
||||
|
||||
@collection
|
||||
class TransactionBlockExplorer {
|
||||
TransactionBlockExplorer({
|
||||
required this.ticker,
|
||||
required this.url,
|
||||
});
|
||||
|
||||
Id id = Isar.autoIncrement;
|
||||
|
||||
@Index(unique: true, replace: true)
|
||||
late final String ticker;
|
||||
|
||||
late final String url;
|
||||
|
||||
@ignore
|
||||
Coin? get coin {
|
||||
try {
|
||||
return coinFromTickerCaseInsensitive(ticker);
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Uri? getUrlFor({required String txid}) => Uri.tryParse(
|
||||
url.replaceFirst(
|
||||
"%5BTXID%5D",
|
||||
txid,
|
||||
),
|
||||
);
|
||||
}
|
764
lib/models/isar/models/block_explorer.g.dart
Normal file
764
lib/models/isar/models/block_explorer.g.dart
Normal file
|
@ -0,0 +1,764 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'block_explorer.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// IsarCollectionGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters
|
||||
|
||||
extension GetTransactionBlockExplorerCollection on Isar {
|
||||
IsarCollection<TransactionBlockExplorer> get transactionBlockExplorers =>
|
||||
this.collection();
|
||||
}
|
||||
|
||||
const TransactionBlockExplorerSchema = CollectionSchema(
|
||||
name: r'TransactionBlockExplorer',
|
||||
id: 4209077296238413906,
|
||||
properties: {
|
||||
r'ticker': PropertySchema(
|
||||
id: 0,
|
||||
name: r'ticker',
|
||||
type: IsarType.string,
|
||||
),
|
||||
r'url': PropertySchema(
|
||||
id: 1,
|
||||
name: r'url',
|
||||
type: IsarType.string,
|
||||
)
|
||||
},
|
||||
estimateSize: _transactionBlockExplorerEstimateSize,
|
||||
serialize: _transactionBlockExplorerSerialize,
|
||||
deserialize: _transactionBlockExplorerDeserialize,
|
||||
deserializeProp: _transactionBlockExplorerDeserializeProp,
|
||||
idName: r'id',
|
||||
indexes: {
|
||||
r'ticker': IndexSchema(
|
||||
id: -8264639257510259247,
|
||||
name: r'ticker',
|
||||
unique: true,
|
||||
replace: true,
|
||||
properties: [
|
||||
IndexPropertySchema(
|
||||
name: r'ticker',
|
||||
type: IndexType.hash,
|
||||
caseSensitive: true,
|
||||
)
|
||||
],
|
||||
)
|
||||
},
|
||||
links: {},
|
||||
embeddedSchemas: {},
|
||||
getId: _transactionBlockExplorerGetId,
|
||||
getLinks: _transactionBlockExplorerGetLinks,
|
||||
attach: _transactionBlockExplorerAttach,
|
||||
version: '3.0.5',
|
||||
);
|
||||
|
||||
int _transactionBlockExplorerEstimateSize(
|
||||
TransactionBlockExplorer object,
|
||||
List<int> offsets,
|
||||
Map<Type, List<int>> allOffsets,
|
||||
) {
|
||||
var bytesCount = offsets.last;
|
||||
bytesCount += 3 + object.ticker.length * 3;
|
||||
bytesCount += 3 + object.url.length * 3;
|
||||
return bytesCount;
|
||||
}
|
||||
|
||||
void _transactionBlockExplorerSerialize(
|
||||
TransactionBlockExplorer object,
|
||||
IsarWriter writer,
|
||||
List<int> offsets,
|
||||
Map<Type, List<int>> allOffsets,
|
||||
) {
|
||||
writer.writeString(offsets[0], object.ticker);
|
||||
writer.writeString(offsets[1], object.url);
|
||||
}
|
||||
|
||||
TransactionBlockExplorer _transactionBlockExplorerDeserialize(
|
||||
Id id,
|
||||
IsarReader reader,
|
||||
List<int> offsets,
|
||||
Map<Type, List<int>> allOffsets,
|
||||
) {
|
||||
final object = TransactionBlockExplorer(
|
||||
ticker: reader.readString(offsets[0]),
|
||||
url: reader.readString(offsets[1]),
|
||||
);
|
||||
object.id = id;
|
||||
return object;
|
||||
}
|
||||
|
||||
P _transactionBlockExplorerDeserializeProp<P>(
|
||||
IsarReader reader,
|
||||
int propertyId,
|
||||
int offset,
|
||||
Map<Type, List<int>> allOffsets,
|
||||
) {
|
||||
switch (propertyId) {
|
||||
case 0:
|
||||
return (reader.readString(offset)) as P;
|
||||
case 1:
|
||||
return (reader.readString(offset)) as P;
|
||||
default:
|
||||
throw IsarError('Unknown property with id $propertyId');
|
||||
}
|
||||
}
|
||||
|
||||
Id _transactionBlockExplorerGetId(TransactionBlockExplorer object) {
|
||||
return object.id;
|
||||
}
|
||||
|
||||
List<IsarLinkBase<dynamic>> _transactionBlockExplorerGetLinks(
|
||||
TransactionBlockExplorer object) {
|
||||
return [];
|
||||
}
|
||||
|
||||
void _transactionBlockExplorerAttach(
|
||||
IsarCollection<dynamic> col, Id id, TransactionBlockExplorer object) {
|
||||
object.id = id;
|
||||
}
|
||||
|
||||
extension TransactionBlockExplorerByIndex
|
||||
on IsarCollection<TransactionBlockExplorer> {
|
||||
Future<TransactionBlockExplorer?> getByTicker(String ticker) {
|
||||
return getByIndex(r'ticker', [ticker]);
|
||||
}
|
||||
|
||||
TransactionBlockExplorer? getByTickerSync(String ticker) {
|
||||
return getByIndexSync(r'ticker', [ticker]);
|
||||
}
|
||||
|
||||
Future<bool> deleteByTicker(String ticker) {
|
||||
return deleteByIndex(r'ticker', [ticker]);
|
||||
}
|
||||
|
||||
bool deleteByTickerSync(String ticker) {
|
||||
return deleteByIndexSync(r'ticker', [ticker]);
|
||||
}
|
||||
|
||||
Future<List<TransactionBlockExplorer?>> getAllByTicker(
|
||||
List<String> tickerValues) {
|
||||
final values = tickerValues.map((e) => [e]).toList();
|
||||
return getAllByIndex(r'ticker', values);
|
||||
}
|
||||
|
||||
List<TransactionBlockExplorer?> getAllByTickerSync(
|
||||
List<String> tickerValues) {
|
||||
final values = tickerValues.map((e) => [e]).toList();
|
||||
return getAllByIndexSync(r'ticker', values);
|
||||
}
|
||||
|
||||
Future<int> deleteAllByTicker(List<String> tickerValues) {
|
||||
final values = tickerValues.map((e) => [e]).toList();
|
||||
return deleteAllByIndex(r'ticker', values);
|
||||
}
|
||||
|
||||
int deleteAllByTickerSync(List<String> tickerValues) {
|
||||
final values = tickerValues.map((e) => [e]).toList();
|
||||
return deleteAllByIndexSync(r'ticker', values);
|
||||
}
|
||||
|
||||
Future<Id> putByTicker(TransactionBlockExplorer object) {
|
||||
return putByIndex(r'ticker', object);
|
||||
}
|
||||
|
||||
Id putByTickerSync(TransactionBlockExplorer object, {bool saveLinks = true}) {
|
||||
return putByIndexSync(r'ticker', object, saveLinks: saveLinks);
|
||||
}
|
||||
|
||||
Future<List<Id>> putAllByTicker(List<TransactionBlockExplorer> objects) {
|
||||
return putAllByIndex(r'ticker', objects);
|
||||
}
|
||||
|
||||
List<Id> putAllByTickerSync(List<TransactionBlockExplorer> objects,
|
||||
{bool saveLinks = true}) {
|
||||
return putAllByIndexSync(r'ticker', objects, saveLinks: saveLinks);
|
||||
}
|
||||
}
|
||||
|
||||
extension TransactionBlockExplorerQueryWhereSort on QueryBuilder<
|
||||
TransactionBlockExplorer, TransactionBlockExplorer, QWhere> {
|
||||
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer, QAfterWhere>
|
||||
anyId() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addWhereClause(const IdWhereClause.any());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
extension TransactionBlockExplorerQueryWhere on QueryBuilder<
|
||||
TransactionBlockExplorer, TransactionBlockExplorer, QWhereClause> {
|
||||
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer,
|
||||
QAfterWhereClause> idEqualTo(Id id) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addWhereClause(IdWhereClause.between(
|
||||
lower: id,
|
||||
upper: id,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer,
|
||||
QAfterWhereClause> idNotEqualTo(Id id) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
if (query.whereSort == Sort.asc) {
|
||||
return query
|
||||
.addWhereClause(
|
||||
IdWhereClause.lessThan(upper: id, includeUpper: false),
|
||||
)
|
||||
.addWhereClause(
|
||||
IdWhereClause.greaterThan(lower: id, includeLower: false),
|
||||
);
|
||||
} else {
|
||||
return query
|
||||
.addWhereClause(
|
||||
IdWhereClause.greaterThan(lower: id, includeLower: false),
|
||||
)
|
||||
.addWhereClause(
|
||||
IdWhereClause.lessThan(upper: id, includeUpper: false),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer,
|
||||
QAfterWhereClause> idGreaterThan(Id id, {bool include = false}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addWhereClause(
|
||||
IdWhereClause.greaterThan(lower: id, includeLower: include),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer,
|
||||
QAfterWhereClause> idLessThan(Id id, {bool include = false}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addWhereClause(
|
||||
IdWhereClause.lessThan(upper: id, includeUpper: include),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer,
|
||||
QAfterWhereClause> idBetween(
|
||||
Id lowerId,
|
||||
Id upperId, {
|
||||
bool includeLower = true,
|
||||
bool includeUpper = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addWhereClause(IdWhereClause.between(
|
||||
lower: lowerId,
|
||||
includeLower: includeLower,
|
||||
upper: upperId,
|
||||
includeUpper: includeUpper,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer,
|
||||
QAfterWhereClause> tickerEqualTo(String ticker) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addWhereClause(IndexWhereClause.equalTo(
|
||||
indexName: r'ticker',
|
||||
value: [ticker],
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer,
|
||||
QAfterWhereClause> tickerNotEqualTo(String ticker) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
if (query.whereSort == Sort.asc) {
|
||||
return query
|
||||
.addWhereClause(IndexWhereClause.between(
|
||||
indexName: r'ticker',
|
||||
lower: [],
|
||||
upper: [ticker],
|
||||
includeUpper: false,
|
||||
))
|
||||
.addWhereClause(IndexWhereClause.between(
|
||||
indexName: r'ticker',
|
||||
lower: [ticker],
|
||||
includeLower: false,
|
||||
upper: [],
|
||||
));
|
||||
} else {
|
||||
return query
|
||||
.addWhereClause(IndexWhereClause.between(
|
||||
indexName: r'ticker',
|
||||
lower: [ticker],
|
||||
includeLower: false,
|
||||
upper: [],
|
||||
))
|
||||
.addWhereClause(IndexWhereClause.between(
|
||||
indexName: r'ticker',
|
||||
lower: [],
|
||||
upper: [ticker],
|
||||
includeUpper: false,
|
||||
));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
extension TransactionBlockExplorerQueryFilter on QueryBuilder<
|
||||
TransactionBlockExplorer, TransactionBlockExplorer, QFilterCondition> {
|
||||
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer,
|
||||
QAfterFilterCondition> idEqualTo(Id value) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.equalTo(
|
||||
property: r'id',
|
||||
value: value,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer,
|
||||
QAfterFilterCondition> idGreaterThan(
|
||||
Id value, {
|
||||
bool include = false,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.greaterThan(
|
||||
include: include,
|
||||
property: r'id',
|
||||
value: value,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer,
|
||||
QAfterFilterCondition> idLessThan(
|
||||
Id value, {
|
||||
bool include = false,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.lessThan(
|
||||
include: include,
|
||||
property: r'id',
|
||||
value: value,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer,
|
||||
QAfterFilterCondition> idBetween(
|
||||
Id lower,
|
||||
Id upper, {
|
||||
bool includeLower = true,
|
||||
bool includeUpper = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.between(
|
||||
property: r'id',
|
||||
lower: lower,
|
||||
includeLower: includeLower,
|
||||
upper: upper,
|
||||
includeUpper: includeUpper,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer,
|
||||
QAfterFilterCondition> tickerEqualTo(
|
||||
String value, {
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.equalTo(
|
||||
property: r'ticker',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer,
|
||||
QAfterFilterCondition> tickerGreaterThan(
|
||||
String value, {
|
||||
bool include = false,
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.greaterThan(
|
||||
include: include,
|
||||
property: r'ticker',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer,
|
||||
QAfterFilterCondition> tickerLessThan(
|
||||
String value, {
|
||||
bool include = false,
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.lessThan(
|
||||
include: include,
|
||||
property: r'ticker',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer,
|
||||
QAfterFilterCondition> tickerBetween(
|
||||
String lower,
|
||||
String upper, {
|
||||
bool includeLower = true,
|
||||
bool includeUpper = true,
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.between(
|
||||
property: r'ticker',
|
||||
lower: lower,
|
||||
includeLower: includeLower,
|
||||
upper: upper,
|
||||
includeUpper: includeUpper,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer,
|
||||
QAfterFilterCondition> tickerStartsWith(
|
||||
String value, {
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.startsWith(
|
||||
property: r'ticker',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer,
|
||||
QAfterFilterCondition> tickerEndsWith(
|
||||
String value, {
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.endsWith(
|
||||
property: r'ticker',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer,
|
||||
QAfterFilterCondition>
|
||||
tickerContains(String value, {bool caseSensitive = true}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.contains(
|
||||
property: r'ticker',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer,
|
||||
QAfterFilterCondition>
|
||||
tickerMatches(String pattern, {bool caseSensitive = true}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.matches(
|
||||
property: r'ticker',
|
||||
wildcard: pattern,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer,
|
||||
QAfterFilterCondition> tickerIsEmpty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.equalTo(
|
||||
property: r'ticker',
|
||||
value: '',
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer,
|
||||
QAfterFilterCondition> tickerIsNotEmpty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.greaterThan(
|
||||
property: r'ticker',
|
||||
value: '',
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer,
|
||||
QAfterFilterCondition> urlEqualTo(
|
||||
String value, {
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.equalTo(
|
||||
property: r'url',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer,
|
||||
QAfterFilterCondition> urlGreaterThan(
|
||||
String value, {
|
||||
bool include = false,
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.greaterThan(
|
||||
include: include,
|
||||
property: r'url',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer,
|
||||
QAfterFilterCondition> urlLessThan(
|
||||
String value, {
|
||||
bool include = false,
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.lessThan(
|
||||
include: include,
|
||||
property: r'url',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer,
|
||||
QAfterFilterCondition> urlBetween(
|
||||
String lower,
|
||||
String upper, {
|
||||
bool includeLower = true,
|
||||
bool includeUpper = true,
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.between(
|
||||
property: r'url',
|
||||
lower: lower,
|
||||
includeLower: includeLower,
|
||||
upper: upper,
|
||||
includeUpper: includeUpper,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer,
|
||||
QAfterFilterCondition> urlStartsWith(
|
||||
String value, {
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.startsWith(
|
||||
property: r'url',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer,
|
||||
QAfterFilterCondition> urlEndsWith(
|
||||
String value, {
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.endsWith(
|
||||
property: r'url',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer,
|
||||
QAfterFilterCondition>
|
||||
urlContains(String value, {bool caseSensitive = true}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.contains(
|
||||
property: r'url',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer,
|
||||
QAfterFilterCondition>
|
||||
urlMatches(String pattern, {bool caseSensitive = true}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.matches(
|
||||
property: r'url',
|
||||
wildcard: pattern,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer,
|
||||
QAfterFilterCondition> urlIsEmpty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.equalTo(
|
||||
property: r'url',
|
||||
value: '',
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer,
|
||||
QAfterFilterCondition> urlIsNotEmpty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.greaterThan(
|
||||
property: r'url',
|
||||
value: '',
|
||||
));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
extension TransactionBlockExplorerQueryObject on QueryBuilder<
|
||||
TransactionBlockExplorer, TransactionBlockExplorer, QFilterCondition> {}
|
||||
|
||||
extension TransactionBlockExplorerQueryLinks on QueryBuilder<
|
||||
TransactionBlockExplorer, TransactionBlockExplorer, QFilterCondition> {}
|
||||
|
||||
extension TransactionBlockExplorerQuerySortBy on QueryBuilder<
|
||||
TransactionBlockExplorer, TransactionBlockExplorer, QSortBy> {
|
||||
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer, QAfterSortBy>
|
||||
sortByTicker() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'ticker', Sort.asc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer, QAfterSortBy>
|
||||
sortByTickerDesc() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'ticker', Sort.desc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer, QAfterSortBy>
|
||||
sortByUrl() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'url', Sort.asc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer, QAfterSortBy>
|
||||
sortByUrlDesc() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'url', Sort.desc);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
extension TransactionBlockExplorerQuerySortThenBy on QueryBuilder<
|
||||
TransactionBlockExplorer, TransactionBlockExplorer, QSortThenBy> {
|
||||
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer, QAfterSortBy>
|
||||
thenById() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'id', Sort.asc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer, QAfterSortBy>
|
||||
thenByIdDesc() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'id', Sort.desc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer, QAfterSortBy>
|
||||
thenByTicker() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'ticker', Sort.asc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer, QAfterSortBy>
|
||||
thenByTickerDesc() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'ticker', Sort.desc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer, QAfterSortBy>
|
||||
thenByUrl() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'url', Sort.asc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer, QAfterSortBy>
|
||||
thenByUrlDesc() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'url', Sort.desc);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
extension TransactionBlockExplorerQueryWhereDistinct on QueryBuilder<
|
||||
TransactionBlockExplorer, TransactionBlockExplorer, QDistinct> {
|
||||
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer, QDistinct>
|
||||
distinctByTicker({bool caseSensitive = true}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addDistinctBy(r'ticker', caseSensitive: caseSensitive);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer, QDistinct>
|
||||
distinctByUrl({bool caseSensitive = true}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addDistinctBy(r'url', caseSensitive: caseSensitive);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
extension TransactionBlockExplorerQueryProperty on QueryBuilder<
|
||||
TransactionBlockExplorer, TransactionBlockExplorer, QQueryProperty> {
|
||||
QueryBuilder<TransactionBlockExplorer, int, QQueryOperations> idProperty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addPropertyName(r'id');
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TransactionBlockExplorer, String, QQueryOperations>
|
||||
tickerProperty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addPropertyName(r'ticker');
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TransactionBlockExplorer, String, QQueryOperations>
|
||||
urlProperty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addPropertyName(r'url');
|
||||
});
|
||||
}
|
||||
}
|
|
@ -5,10 +5,11 @@ import 'package:flutter/material.dart';
|
|||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:stackwallet/models/exchange/aggregate_currency.dart';
|
||||
import 'package:stackwallet/models/exchange/incomplete_exchange.dart';
|
||||
import 'package:stackwallet/models/exchange/response_objects/estimate.dart';
|
||||
import 'package:stackwallet/models/exchange/response_objects/range.dart';
|
||||
import 'package:stackwallet/models/isar/exchange_cache/currency.dart';
|
||||
import 'package:stackwallet/models/isar/exchange_cache/pair.dart';
|
||||
import 'package:stackwallet/models/isar/models/ethereum/eth_contract.dart';
|
||||
|
@ -21,6 +22,8 @@ import 'package:stackwallet/pages_desktop_specific/desktop_exchange/exchange_ste
|
|||
import 'package:stackwallet/providers/providers.dart';
|
||||
import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart';
|
||||
import 'package:stackwallet/services/exchange/exchange_data_loading_service.dart';
|
||||
import 'package:stackwallet/services/exchange/majestic_bank/majestic_bank_exchange.dart';
|
||||
import 'package:stackwallet/services/exchange/trocador/trocador_exchange.dart';
|
||||
import 'package:stackwallet/utilities/assets.dart';
|
||||
import 'package:stackwallet/utilities/constants.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
|
@ -39,6 +42,9 @@ import 'package:stackwallet/widgets/rounded_white_container.dart';
|
|||
import 'package:stackwallet/widgets/stack_dialog.dart';
|
||||
import 'package:stackwallet/widgets/textfields/exchange_textfield.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
import '../../services/exchange/exchange_response.dart';
|
||||
|
||||
class ExchangeForm extends ConsumerStatefulWidget {
|
||||
const ExchangeForm({
|
||||
|
@ -61,6 +67,12 @@ class _ExchangeFormState extends ConsumerState<ExchangeForm> {
|
|||
late final Coin? coin;
|
||||
late final bool walletInitiated;
|
||||
|
||||
final exchanges = [
|
||||
MajesticBankExchange.instance,
|
||||
ChangeNowExchange.instance,
|
||||
TrocadorExchange.instance,
|
||||
];
|
||||
|
||||
late final TextEditingController _sendController;
|
||||
late final TextEditingController _receiveController;
|
||||
final isDesktop = Util.isDesktop;
|
||||
|
@ -70,7 +82,7 @@ class _ExchangeFormState extends ConsumerState<ExchangeForm> {
|
|||
bool _swapLock = false;
|
||||
|
||||
// todo: check and adjust this value?
|
||||
static const _valueCheckInterval = Duration(milliseconds: 300);
|
||||
static const _valueCheckInterval = Duration(milliseconds: 1500);
|
||||
|
||||
Future<T> showUpdatingExchangeRate<T>({
|
||||
required Future<T> whileFuture,
|
||||
|
@ -105,16 +117,17 @@ class _ExchangeFormState extends ConsumerState<ExchangeForm> {
|
|||
}
|
||||
|
||||
Timer? _sendFieldOnChangedTimer;
|
||||
void sendFieldOnChanged(String value) async {
|
||||
void sendFieldOnChanged(String value) {
|
||||
if (_sendFocusNode.hasFocus) {
|
||||
_sendFieldOnChangedTimer?.cancel();
|
||||
|
||||
_sendFieldOnChangedTimer = Timer(_valueCheckInterval, () async {
|
||||
final newFromAmount = _localizedStringToNum(value);
|
||||
|
||||
await ref
|
||||
.read(exchangeFormStateProvider)
|
||||
.setSendAmountAndCalculateReceiveAmount(newFromAmount, true);
|
||||
ref.read(efSendAmountProvider.notifier).state = newFromAmount;
|
||||
if (!_swapLock && !ref.read(efReversedProvider)) {
|
||||
unawaited(update());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -126,9 +139,10 @@ class _ExchangeFormState extends ConsumerState<ExchangeForm> {
|
|||
_receiveFieldOnChangedTimer = Timer(_valueCheckInterval, () async {
|
||||
final newToAmount = _localizedStringToNum(value);
|
||||
|
||||
await ref
|
||||
.read(exchangeFormStateProvider)
|
||||
.setReceivingAmountAndCalculateSendAmount(newToAmount, true);
|
||||
ref.read(efReceiveAmountProvider.notifier).state = newToAmount;
|
||||
if (!_swapLock && ref.read(efReversedProvider)) {
|
||||
unawaited(update());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -137,17 +151,29 @@ class _ExchangeFormState extends ConsumerState<ExchangeForm> {
|
|||
return null;
|
||||
}
|
||||
try {
|
||||
final numFromLocalised = NumberFormat.decimalPattern(
|
||||
ref.read(localeServiceChangeNotifierProvider).locale)
|
||||
.parse(value);
|
||||
return Decimal.tryParse(numFromLocalised.toString());
|
||||
// wtf Dart?????
|
||||
// This turns "99999999999999999999" into 100000000000000000000.0
|
||||
// final numFromLocalised = NumberFormat.decimalPattern(
|
||||
// ref.read(localeServiceChangeNotifierProvider).locale)
|
||||
// .parse(value);
|
||||
// return Decimal.tryParse(numFromLocalised.toString());
|
||||
|
||||
try {
|
||||
return Decimal.parse(value);
|
||||
} catch (_) {
|
||||
try {
|
||||
return Decimal.parse(value.replaceAll(",", "."));
|
||||
} catch (_) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<AggregateCurrency> _getAggregateCurrency(Currency currency) async {
|
||||
final rateType = ref.read(exchangeFormStateProvider).exchangeRateType;
|
||||
final rateType = ref.read(efRateTypeProvider);
|
||||
final currencies = await ExchangeDataLoadingService.instance.isar.currencies
|
||||
.filter()
|
||||
.group((q) => rateType == ExchangeRateType.fixed
|
||||
|
@ -178,8 +204,8 @@ class _ExchangeFormState extends ConsumerState<ExchangeForm> {
|
|||
}
|
||||
|
||||
void selectSendCurrency() async {
|
||||
final type = (ref.read(exchangeFormStateProvider).exchangeRateType);
|
||||
final fromTicker = ref.read(exchangeFormStateProvider).fromTicker ?? "";
|
||||
final type = ref.read(efRateTypeProvider);
|
||||
final fromTicker = ref.read(efCurrencyPairProvider).send?.ticker ?? "";
|
||||
|
||||
if (walletInitiated) {
|
||||
if (widget.contract != null &&
|
||||
|
@ -194,24 +220,26 @@ class _ExchangeFormState extends ConsumerState<ExchangeForm> {
|
|||
}
|
||||
|
||||
final selectedCurrency = await _showCurrencySelectionSheet(
|
||||
willChange: ref.read(exchangeFormStateProvider).sendCurrency?.ticker,
|
||||
willChange: ref.read(efCurrencyPairProvider).send?.ticker,
|
||||
willChangeIsSend: true,
|
||||
paired: ref.read(exchangeFormStateProvider).receiveCurrency?.ticker,
|
||||
paired: ref.read(efCurrencyPairProvider).receive?.ticker,
|
||||
isFixedRate: type == ExchangeRateType.fixed,
|
||||
);
|
||||
|
||||
if (selectedCurrency != null) {
|
||||
await showUpdatingExchangeRate(
|
||||
whileFuture: _getAggregateCurrency(selectedCurrency).then(
|
||||
(aggregateSelected) => ref
|
||||
.read(exchangeFormStateProvider)
|
||||
.updateSendCurrency(aggregateSelected, true)),
|
||||
(aggregateSelected) => ref.read(efCurrencyPairProvider).setSend(
|
||||
aggregateSelected,
|
||||
notifyListeners: true,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void selectReceiveCurrency() async {
|
||||
final toTicker = ref.read(exchangeFormStateProvider).toTicker ?? "";
|
||||
final toTicker = ref.read(efCurrencyPairProvider).receive?.ticker ?? "";
|
||||
if (walletInitiated &&
|
||||
toTicker.toLowerCase() == coin!.ticker.toLowerCase()) {
|
||||
// do not allow changing away from wallet coin
|
||||
|
@ -219,19 +247,20 @@ class _ExchangeFormState extends ConsumerState<ExchangeForm> {
|
|||
}
|
||||
|
||||
final selectedCurrency = await _showCurrencySelectionSheet(
|
||||
willChange: ref.read(exchangeFormStateProvider).receiveCurrency?.ticker,
|
||||
willChange: ref.read(efCurrencyPairProvider).receive?.ticker,
|
||||
willChangeIsSend: false,
|
||||
paired: ref.read(exchangeFormStateProvider).sendCurrency?.ticker,
|
||||
isFixedRate: ref.read(exchangeFormStateProvider).exchangeRateType ==
|
||||
ExchangeRateType.fixed,
|
||||
paired: ref.read(efCurrencyPairProvider).send?.ticker,
|
||||
isFixedRate: ref.read(efRateTypeProvider) == ExchangeRateType.fixed,
|
||||
);
|
||||
|
||||
if (selectedCurrency != null) {
|
||||
await showUpdatingExchangeRate(
|
||||
whileFuture: _getAggregateCurrency(selectedCurrency).then(
|
||||
(aggregateSelected) => ref
|
||||
.read(exchangeFormStateProvider)
|
||||
.updateReceivingCurrency(aggregateSelected, true)),
|
||||
(aggregateSelected) => ref.read(efCurrencyPairProvider).setReceive(
|
||||
aggregateSelected,
|
||||
notifyListeners: true,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -241,10 +270,25 @@ class _ExchangeFormState extends ConsumerState<ExchangeForm> {
|
|||
_sendFocusNode.unfocus();
|
||||
_receiveFocusNode.unfocus();
|
||||
|
||||
await showUpdatingExchangeRate(
|
||||
whileFuture:
|
||||
ref.read(exchangeFormStateProvider).swap(shouldNotifyListeners: true),
|
||||
);
|
||||
final temp = ref.read(efCurrencyPairProvider).send;
|
||||
ref.read(efCurrencyPairProvider).setSend(
|
||||
ref.read(efCurrencyPairProvider).receive,
|
||||
notifyListeners: true,
|
||||
);
|
||||
ref.read(efCurrencyPairProvider).setReceive(
|
||||
temp,
|
||||
notifyListeners: true,
|
||||
);
|
||||
|
||||
// final reversed = ref.read(efReversedProvider);
|
||||
|
||||
final amount = ref.read(efSendAmountProvider);
|
||||
ref.read(efSendAmountProvider.notifier).state =
|
||||
ref.read(efReceiveAmountProvider);
|
||||
|
||||
ref.read(efReceiveAmountProvider.notifier).state = amount;
|
||||
|
||||
unawaited(update());
|
||||
|
||||
_swapLock = false;
|
||||
}
|
||||
|
@ -331,85 +375,20 @@ class _ExchangeFormState extends ConsumerState<ExchangeForm> {
|
|||
}
|
||||
}
|
||||
|
||||
void onRateTypeChanged(ExchangeRateType newType) async {
|
||||
void onRateTypeChanged(ExchangeRateType newType) {
|
||||
_receiveFocusNode.unfocus();
|
||||
_sendFocusNode.unfocus();
|
||||
|
||||
await showUpdatingExchangeRate(
|
||||
whileFuture: _onRateTypeChangedFuture(newType),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onRateTypeChangedFuture(ExchangeRateType newType) async {
|
||||
ref.read(exchangeFormStateProvider).exchangeRateType = newType;
|
||||
|
||||
final fromTicker = ref.read(exchangeFormStateProvider).fromTicker ?? "-";
|
||||
final toTicker = ref.read(exchangeFormStateProvider).toTicker ?? "-";
|
||||
|
||||
ref.read(exchangeFormStateProvider).reversed = false;
|
||||
|
||||
if (!(toTicker == "-" || fromTicker == "-")) {
|
||||
// final available = await ExchangeDataLoadingService.instance.isar.pairs
|
||||
// .where()
|
||||
// .exchangeNameEqualTo(
|
||||
// ref.read(currentExchangeNameStateProvider.state).state)
|
||||
// .filter()
|
||||
// .fromEqualTo(fromTicker)
|
||||
// .and()
|
||||
// .toEqualTo(toTicker)
|
||||
// .findAll();
|
||||
await ref.read(exchangeFormStateProvider).refresh();
|
||||
|
||||
// if (available.isNotEmpty) {
|
||||
// final availableCurrencies = await ExchangeDataLoadingService
|
||||
// .instance.isar.currencies
|
||||
// .where()
|
||||
// .exchangeNameEqualTo(
|
||||
// ref.read(currentExchangeNameStateProvider.state).state)
|
||||
// .filter()
|
||||
// .tickerEqualTo(fromTicker)
|
||||
// .or()
|
||||
// .tickerEqualTo(toTicker)
|
||||
// .findAll();
|
||||
//
|
||||
// if (availableCurrencies.length > 1) {
|
||||
// final from =
|
||||
// availableCurrencies.firstWhere((e) => e.ticker == fromTicker);
|
||||
// final to =
|
||||
// availableCurrencies.firstWhere((e) => e.ticker == toTicker);
|
||||
//
|
||||
// final newFromAmount = Decimal.tryParse(_sendController.text);
|
||||
// ref.read(exchangeFormStateProvider).receiveAmount = newFromAmount;
|
||||
// if (newFromAmount == null) {
|
||||
// _receiveController.text = "";
|
||||
// }
|
||||
//
|
||||
// await ref
|
||||
// .read(exchangeFormStateProvider)
|
||||
// .updateReceivingCurrency(to, false);
|
||||
// await ref
|
||||
// .read(exchangeFormStateProvider)
|
||||
// .updateSendCurrency(from, true);
|
||||
//
|
||||
// _receiveController.text =
|
||||
// ref.read(exchangeFormStateProvider).toAmountString.isEmpty
|
||||
// ? "-"
|
||||
// : ref.read(exchangeFormStateProvider).toAmountString;
|
||||
// if (mounted) {
|
||||
// Navigator.of(context, rootNavigator: isDesktop).pop();
|
||||
// }
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
ref.read(efRateTypeProvider.notifier).state = newType;
|
||||
update();
|
||||
}
|
||||
|
||||
void onExchangePressed() async {
|
||||
final rateType = ref.read(exchangeFormStateProvider).exchangeRateType;
|
||||
final fromTicker = ref.read(exchangeFormStateProvider).fromTicker ?? "";
|
||||
final toTicker = ref.read(exchangeFormStateProvider).toTicker ?? "";
|
||||
final sendAmount = ref.read(exchangeFormStateProvider).sendAmount!;
|
||||
final estimate = ref.read(exchangeFormStateProvider).estimate!;
|
||||
final rateType = ref.read(efRateTypeProvider);
|
||||
final fromTicker = ref.read(efCurrencyPairProvider).send?.ticker ?? "";
|
||||
final toTicker = ref.read(efCurrencyPairProvider).receive?.ticker ?? "";
|
||||
final estimate = ref.read(efEstimateProvider)!;
|
||||
final sendAmount = ref.read(efSendAmountProvider)!;
|
||||
|
||||
if (rateType == ExchangeRateType.fixed && toTicker.toUpperCase() == "WOW") {
|
||||
await showDialog<void>(
|
||||
|
@ -426,10 +405,16 @@ class _ExchangeFormState extends ConsumerState<ExchangeForm> {
|
|||
|
||||
String rate;
|
||||
|
||||
final amountToSend =
|
||||
estimate.reversed ? estimate.estimatedAmount : sendAmount;
|
||||
final amountToReceive = estimate.reversed
|
||||
? ref.read(efReceiveAmountProvider)!
|
||||
: estimate.estimatedAmount;
|
||||
|
||||
switch (rateType) {
|
||||
case ExchangeRateType.estimated:
|
||||
rate =
|
||||
"1 ${fromTicker.toUpperCase()} ~${(estimate.estimatedAmount / sendAmount).toDecimal(scaleOnInfinitePrecision: 8).toStringAsFixed(8)} ${toTicker.toUpperCase()}";
|
||||
"1 ${fromTicker.toUpperCase()} ~${(amountToReceive / sendAmount).toDecimal(scaleOnInfinitePrecision: 8).toStringAsFixed(8)} ${toTicker.toUpperCase()}";
|
||||
break;
|
||||
case ExchangeRateType.fixed:
|
||||
bool? shouldCancel;
|
||||
|
@ -541,7 +526,9 @@ class _ExchangeFormState extends ConsumerState<ExchangeForm> {
|
|||
return;
|
||||
}
|
||||
rate =
|
||||
"1 ${fromTicker.toUpperCase()} ~${ref.read(exchangeFormStateProvider).rate!.toStringAsFixed(8)} ${toTicker.toUpperCase()}";
|
||||
"1 ${fromTicker.toUpperCase()} ~${(amountToReceive / amountToSend).toDecimal(
|
||||
scaleOnInfinitePrecision: 12,
|
||||
).toStringAsFixed(8)} ${toTicker.toUpperCase()}";
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -549,12 +536,10 @@ class _ExchangeFormState extends ConsumerState<ExchangeForm> {
|
|||
sendTicker: fromTicker.toUpperCase(),
|
||||
receiveTicker: toTicker.toUpperCase(),
|
||||
rateInfo: rate,
|
||||
sendAmount: estimate.reversed ? estimate.estimatedAmount : sendAmount,
|
||||
receiveAmount: estimate.reversed
|
||||
? ref.read(exchangeFormStateProvider).receiveAmount!
|
||||
: estimate.estimatedAmount,
|
||||
sendAmount: amountToSend,
|
||||
receiveAmount: amountToReceive,
|
||||
rateType: rateType,
|
||||
rateId: estimate.rateId,
|
||||
estimate: estimate,
|
||||
reversed: estimate.reversed,
|
||||
walletInitiated: walletInitiated,
|
||||
);
|
||||
|
@ -622,8 +607,8 @@ class _ExchangeFormState extends ConsumerState<ExchangeForm> {
|
|||
}
|
||||
|
||||
String? ticker = isSend
|
||||
? ref.read(exchangeFormStateProvider).fromTicker
|
||||
: ref.read(exchangeFormStateProvider).toTicker;
|
||||
? ref.read(efCurrencyPairProvider).send?.ticker
|
||||
: ref.read(efCurrencyPairProvider).receive?.ticker;
|
||||
|
||||
if (ticker == null) {
|
||||
return false;
|
||||
|
@ -632,6 +617,97 @@ class _ExchangeFormState extends ConsumerState<ExchangeForm> {
|
|||
return coin.ticker.toUpperCase() == ticker.toUpperCase();
|
||||
}
|
||||
|
||||
Future<void> update() async {
|
||||
final uuid = const Uuid().v1();
|
||||
_latestUuid = uuid;
|
||||
_addUpdate(uuid);
|
||||
for (final exchange in exchanges) {
|
||||
ref.read(efEstimatesListProvider(exchange.name).notifier).state = null;
|
||||
}
|
||||
|
||||
final reversed = ref.read(efReversedProvider);
|
||||
final amount = reversed
|
||||
? ref.read(efReceiveAmountProvider)
|
||||
: ref.read(efSendAmountProvider);
|
||||
|
||||
final pair = ref.read(efCurrencyPairProvider);
|
||||
if (amount == null ||
|
||||
amount <= Decimal.zero ||
|
||||
pair.send == null ||
|
||||
pair.receive == null) {
|
||||
_removeUpdate(uuid);
|
||||
return;
|
||||
}
|
||||
final rateType = ref.read(efRateTypeProvider);
|
||||
final Map<String, Tuple2<ExchangeResponse<List<Estimate>>, Range?>>
|
||||
results = {};
|
||||
|
||||
for (final exchange in exchanges) {
|
||||
final sendCurrency = pair.send?.forExchange(exchange.name);
|
||||
final receiveCurrency = pair.receive?.forExchange(exchange.name);
|
||||
|
||||
if (sendCurrency != null && receiveCurrency != null) {
|
||||
final rangeResponse = await exchange.getRange(
|
||||
reversed ? receiveCurrency.ticker : sendCurrency.ticker,
|
||||
reversed ? sendCurrency.ticker : receiveCurrency.ticker,
|
||||
rateType == ExchangeRateType.fixed,
|
||||
);
|
||||
|
||||
final estimateResponse = await exchange.getEstimates(
|
||||
sendCurrency.ticker,
|
||||
receiveCurrency.ticker,
|
||||
amount,
|
||||
rateType == ExchangeRateType.fixed,
|
||||
reversed,
|
||||
);
|
||||
|
||||
results.addAll(
|
||||
{
|
||||
exchange.name: Tuple2(
|
||||
estimateResponse,
|
||||
rangeResponse.value,
|
||||
),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for (final exchange in exchanges) {
|
||||
if (uuid == _latestUuid) {
|
||||
ref.read(efEstimatesListProvider(exchange.name).notifier).state =
|
||||
results[exchange.name];
|
||||
}
|
||||
}
|
||||
|
||||
_removeUpdate(uuid);
|
||||
}
|
||||
|
||||
String? _latestUuid;
|
||||
final Set<String> _uuids = {};
|
||||
|
||||
void _addUpdate(String uuid) {
|
||||
_uuids.add(uuid);
|
||||
ref.read(efRefreshingProvider.notifier).state = true;
|
||||
}
|
||||
|
||||
void _removeUpdate(String uuid) {
|
||||
_uuids.remove(uuid);
|
||||
if (_uuids.isEmpty) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
ref.read(efRefreshingProvider.notifier).state = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void updateSend(Estimate? estimate) {
|
||||
ref.read(efSendAmountProvider.notifier).state = estimate?.estimatedAmount;
|
||||
}
|
||||
|
||||
void updateReceive(Estimate? estimate) {
|
||||
ref.read(efReceiveAmountProvider.notifier).state =
|
||||
estimate?.estimatedAmount;
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_sendController = TextEditingController();
|
||||
|
@ -641,9 +717,40 @@ class _ExchangeFormState extends ConsumerState<ExchangeForm> {
|
|||
coin = widget.coin;
|
||||
walletInitiated = walletId != null && coin != null;
|
||||
|
||||
_sendFocusNode.addListener(() {
|
||||
if (_sendFocusNode.hasFocus) {
|
||||
final reversed = ref.read(efReversedProvider);
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
ref.read(efReversedProvider.notifier).state = false;
|
||||
if (reversed == true) {
|
||||
update();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
_receiveFocusNode.addListener(() {
|
||||
if (_receiveFocusNode.hasFocus &&
|
||||
ref.read(efExchangeProvider).name != ChangeNowExchange.exchangeName) {
|
||||
final reversed = ref.read(efReversedProvider);
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
ref.read(efReversedProvider.notifier).state = true;
|
||||
if (reversed != true) {
|
||||
update();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (walletInitiated) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||
ref.read(exchangeFormStateProvider).reset(shouldNotifyListeners: true);
|
||||
ref.read(efSendAmountProvider.notifier).state = null;
|
||||
ref.read(efReceiveAmountProvider.notifier).state = null;
|
||||
ref.read(efReversedProvider.notifier).state = false;
|
||||
ref.read(efRefreshingProvider.notifier).state = false;
|
||||
ref.read(efCurrencyPairProvider).setSend(null, notifyListeners: true);
|
||||
ref
|
||||
.read(efCurrencyPairProvider)
|
||||
.setReceive(null, notifyListeners: true);
|
||||
ExchangeDataLoadingService.instance
|
||||
.getAggregateCurrency(
|
||||
widget.contract == null ? coin!.ticker : widget.contract!.symbol,
|
||||
|
@ -652,17 +759,17 @@ class _ExchangeFormState extends ConsumerState<ExchangeForm> {
|
|||
)
|
||||
.then((value) {
|
||||
if (value != null) {
|
||||
ref.read(exchangeFormStateProvider).updateSendCurrency(value, true);
|
||||
ref.read(efCurrencyPairProvider).setSend(
|
||||
value,
|
||||
notifyListeners: true,
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
_sendController.text =
|
||||
ref.read(exchangeFormStateProvider).fromAmountString;
|
||||
_receiveController.text =
|
||||
ref.read(exchangeFormStateProvider).toAmountString;
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||
ref.read(exchangeFormStateProvider).refresh();
|
||||
_sendController.text = ref.read(efSendAmountStringProvider);
|
||||
_receiveController.text = ref.read(efReceiveAmountStringProvider);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -673,6 +780,8 @@ class _ExchangeFormState extends ConsumerState<ExchangeForm> {
|
|||
void dispose() {
|
||||
_receiveController.dispose();
|
||||
_sendController.dispose();
|
||||
_receiveFocusNode.dispose();
|
||||
_sendFocusNode.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
@ -680,34 +789,42 @@ class _ExchangeFormState extends ConsumerState<ExchangeForm> {
|
|||
Widget build(BuildContext context) {
|
||||
debugPrint("BUILD: $runtimeType");
|
||||
|
||||
final rateType = ref.watch(
|
||||
exchangeFormStateProvider.select((value) => value.exchangeRateType));
|
||||
final rateType = ref.watch(efRateTypeProvider);
|
||||
|
||||
final isEstimated = rateType == ExchangeRateType.estimated;
|
||||
|
||||
ref.listen(
|
||||
exchangeFormStateProvider.select((value) => value.toAmountString),
|
||||
(previous, String next) {
|
||||
ref.listen(efReceiveAmountStringProvider, (previous, String next) {
|
||||
if (!_receiveFocusNode.hasFocus) {
|
||||
_receiveController.text = isEstimated && next.isEmpty ? "-" : next;
|
||||
if (_swapLock) {
|
||||
_sendController.text =
|
||||
ref.read(exchangeFormStateProvider).fromAmountString;
|
||||
}
|
||||
// if (_swapLock) {
|
||||
_sendController.text = ref.read(efSendAmountStringProvider);
|
||||
// }
|
||||
}
|
||||
});
|
||||
ref.listen(
|
||||
exchangeFormStateProvider.select((value) => value.fromAmountString),
|
||||
(previous, String next) {
|
||||
ref.listen(efSendAmountStringProvider, (previous, String next) {
|
||||
if (!_sendFocusNode.hasFocus) {
|
||||
_sendController.text = next;
|
||||
if (_swapLock) {
|
||||
_receiveController.text = isEstimated
|
||||
? ref.read(exchangeFormStateProvider).toAmountString.isEmpty
|
||||
? "-"
|
||||
: ref.read(exchangeFormStateProvider).toAmountString
|
||||
: ref.read(exchangeFormStateProvider).toAmountString;
|
||||
}
|
||||
// if (_swapLock) {
|
||||
_receiveController.text =
|
||||
isEstimated && ref.read(efReceiveAmountStringProvider).isEmpty
|
||||
? "-"
|
||||
: ref.read(efReceiveAmountStringProvider);
|
||||
// }
|
||||
}
|
||||
});
|
||||
|
||||
ref.listen(efEstimateProvider.notifier, (previous, next) {
|
||||
final estimate = (next as StateController<Estimate?>).state;
|
||||
if (ref.read(efReversedProvider)) {
|
||||
updateSend(estimate);
|
||||
} else {
|
||||
updateReceive(estimate);
|
||||
}
|
||||
});
|
||||
|
||||
ref.listen(efCurrencyPairProvider, (previous, next) {
|
||||
if (!_swapLock) {
|
||||
update();
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -725,8 +842,9 @@ class _ExchangeFormState extends ConsumerState<ExchangeForm> {
|
|||
height: isDesktop ? 10 : 4,
|
||||
),
|
||||
ExchangeTextField(
|
||||
key: Key(
|
||||
"exchangeTextFieldKeyFor_${Theme.of(context).extension<StackColors>()!.themeType.name}"),
|
||||
key: Key("exchangeTextFieldKeyFor_"
|
||||
"${Theme.of(context).extension<StackColors>()!.themeType.name}"
|
||||
"${ref.watch(efCurrencyPairProvider.select((value) => value.send?.ticker))}"),
|
||||
controller: _sendController,
|
||||
focusNode: _sendFocusNode,
|
||||
textStyle: STextStyles.smallMed14(context).copyWith(
|
||||
|
@ -745,8 +863,8 @@ class _ExchangeFormState extends ConsumerState<ExchangeForm> {
|
|||
onChanged: sendFieldOnChanged,
|
||||
onButtonTap: selectSendCurrency,
|
||||
isWalletCoin: isWalletCoin(coin, true),
|
||||
currency: ref.watch(
|
||||
exchangeFormStateProvider.select((value) => value.sendCurrency)),
|
||||
currency:
|
||||
ref.watch(efCurrencyPairProvider.select((value) => value.send)),
|
||||
),
|
||||
SizedBox(
|
||||
height: isDesktop ? 10 : 4,
|
||||
|
@ -754,17 +872,6 @@ class _ExchangeFormState extends ConsumerState<ExchangeForm> {
|
|||
SizedBox(
|
||||
height: isDesktop ? 10 : 4,
|
||||
),
|
||||
if (ref
|
||||
.watch(
|
||||
exchangeFormStateProvider.select((value) => value.warning))
|
||||
.isNotEmpty &&
|
||||
!ref.watch(
|
||||
exchangeFormStateProvider.select((value) => value.reversed)))
|
||||
Text(
|
||||
ref.watch(
|
||||
exchangeFormStateProvider.select((value) => value.warning)),
|
||||
style: STextStyles.errorSmall(context),
|
||||
),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
|
@ -809,7 +916,7 @@ class _ExchangeFormState extends ConsumerState<ExchangeForm> {
|
|||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -829,34 +936,24 @@ class _ExchangeFormState extends ConsumerState<ExchangeForm> {
|
|||
borderRadius: Constants.size.circularBorderRadius,
|
||||
background:
|
||||
Theme.of(context).extension<StackColors>()!.textFieldDefaultBG,
|
||||
onTap: () {
|
||||
if (!(ref.read(exchangeFormStateProvider).exchangeRateType ==
|
||||
ExchangeRateType.estimated) &&
|
||||
_receiveController.text == "-") {
|
||||
_receiveController.text = "";
|
||||
}
|
||||
},
|
||||
onTap: rateType == ExchangeRateType.estimated &&
|
||||
ref.watch(efExchangeProvider).name ==
|
||||
ChangeNowExchange.exchangeName
|
||||
? null
|
||||
: () {
|
||||
if (_sendController.text == "-") {
|
||||
_sendController.text = "";
|
||||
}
|
||||
},
|
||||
onChanged: receiveFieldOnChanged,
|
||||
onButtonTap: selectReceiveCurrency,
|
||||
isWalletCoin: isWalletCoin(coin, true),
|
||||
currency: ref.watch(exchangeFormStateProvider
|
||||
.select((value) => value.receiveCurrency)),
|
||||
readOnly: (rateType) == ExchangeRateType.estimated &&
|
||||
ref.watch(exchangeFormStateProvider
|
||||
.select((value) => value.exchange.name)) ==
|
||||
currency: ref
|
||||
.watch(efCurrencyPairProvider.select((value) => value.receive)),
|
||||
readOnly: rateType == ExchangeRateType.estimated &&
|
||||
ref.watch(efExchangeProvider).name ==
|
||||
ChangeNowExchange.exchangeName,
|
||||
),
|
||||
if (ref
|
||||
.watch(
|
||||
exchangeFormStateProvider.select((value) => value.warning))
|
||||
.isNotEmpty &&
|
||||
ref.watch(
|
||||
exchangeFormStateProvider.select((value) => value.reversed)))
|
||||
Text(
|
||||
ref.watch(
|
||||
exchangeFormStateProvider.select((value) => value.warning)),
|
||||
style: STextStyles.errorSmall(context),
|
||||
),
|
||||
SizedBox(
|
||||
height: isDesktop ? 20 : 12,
|
||||
),
|
||||
|
@ -867,27 +964,27 @@ class _ExchangeFormState extends ConsumerState<ExchangeForm> {
|
|||
onChanged: onRateTypeChanged,
|
||||
),
|
||||
),
|
||||
// these reads should be watch
|
||||
if (ref.watch(exchangeFormStateProvider).sendAmount != null &&
|
||||
ref.watch(exchangeFormStateProvider).sendAmount != Decimal.zero)
|
||||
SizedBox(
|
||||
height: isDesktop ? 20 : 12,
|
||||
),
|
||||
// these reads should be watch
|
||||
if (ref.watch(exchangeFormStateProvider).sendAmount != null &&
|
||||
ref.watch(exchangeFormStateProvider).sendAmount != Decimal.zero)
|
||||
ExchangeProviderOptions(
|
||||
fixedRate: rateType == ExchangeRateType.fixed,
|
||||
reversed: ref.watch(
|
||||
exchangeFormStateProvider.select((value) => value.reversed)),
|
||||
),
|
||||
AnimatedSize(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
child: ref.watch(efSendAmountProvider) == null &&
|
||||
ref.watch(efReceiveAmountProvider) == null
|
||||
? const SizedBox(
|
||||
height: 0,
|
||||
)
|
||||
: Padding(
|
||||
padding: EdgeInsets.only(top: isDesktop ? 20 : 12),
|
||||
child: ExchangeProviderOptions(
|
||||
fixedRate: rateType == ExchangeRateType.fixed,
|
||||
reversed: ref.watch(efReversedProvider),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: isDesktop ? 20 : 12,
|
||||
),
|
||||
PrimaryButton(
|
||||
buttonHeight: isDesktop ? ButtonHeight.l : null,
|
||||
enabled: ref.watch(
|
||||
exchangeFormStateProvider.select((value) => value.canExchange)),
|
||||
enabled: ref.watch(efCanExchangeProvider),
|
||||
onPressed: onExchangePressed,
|
||||
label: "Swap",
|
||||
)
|
||||
|
|
|
@ -124,9 +124,8 @@ class _Step2ViewState extends ConsumerState<Step2View> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final supportsRefund = ref.watch(
|
||||
exchangeFormStateProvider.select((value) => value.exchange.name)) !=
|
||||
MajesticBankExchange.exchangeName;
|
||||
final supportsRefund =
|
||||
ref.watch(efExchangeProvider).name != MajesticBankExchange.exchangeName;
|
||||
|
||||
return Background(
|
||||
child: Scaffold(
|
||||
|
|
|
@ -52,9 +52,8 @@ class _Step3ViewState extends ConsumerState<Step3View> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final supportsRefund = ref.watch(
|
||||
exchangeFormStateProvider.select((value) => value.exchange.name)) !=
|
||||
MajesticBankExchange.exchangeName;
|
||||
final supportsRefund =
|
||||
ref.watch(efExchangeProvider).name != MajesticBankExchange.exchangeName;
|
||||
|
||||
return Background(
|
||||
child: Scaffold(
|
||||
|
@ -254,8 +253,7 @@ class _Step3ViewState extends ConsumerState<Step3View> {
|
|||
|
||||
final ExchangeResponse<Trade> response =
|
||||
await ref
|
||||
.read(exchangeFormStateProvider)
|
||||
.exchange
|
||||
.read(efExchangeProvider)
|
||||
.createTrade(
|
||||
from: model.sendTicker,
|
||||
to: model.receiveTicker,
|
||||
|
@ -271,24 +269,26 @@ class _Step3ViewState extends ConsumerState<Step3View> {
|
|||
? model.refundAddress!
|
||||
: "",
|
||||
refundExtraId: "",
|
||||
rateId: model.rateId,
|
||||
estimate: model.estimate,
|
||||
reversed: model.reversed,
|
||||
);
|
||||
|
||||
if (response.value == null) {
|
||||
if (mounted) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
unawaited(showDialog<void>(
|
||||
context: context,
|
||||
barrierDismissible: true,
|
||||
builder: (_) => StackDialog(
|
||||
title: "Failed to create trade",
|
||||
message:
|
||||
response.exception?.toString(),
|
||||
),
|
||||
));
|
||||
unawaited(
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
barrierDismissible: true,
|
||||
builder: (_) => StackDialog(
|
||||
title: "Failed to create trade",
|
||||
message: response.exception
|
||||
?.toString(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -70,10 +70,8 @@ class _Step4ViewState extends ConsumerState<Step4View> {
|
|||
}
|
||||
|
||||
Future<void> _updateStatus() async {
|
||||
final statusResponse = await ref
|
||||
.read(exchangeFormStateProvider)
|
||||
.exchange
|
||||
.updateTrade(model.trade!);
|
||||
final statusResponse =
|
||||
await ref.read(efExchangeProvider).updateTrade(model.trade!);
|
||||
String status = "Waiting";
|
||||
if (statusResponse.value != null) {
|
||||
status = statusResponse.value!.status;
|
||||
|
|
|
@ -37,7 +37,8 @@ class _ExchangeViewState extends ConsumerState<ExchangeView> {
|
|||
ExchangeDataLoadingService.instance.onLoadingComplete = () {
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
|
||||
await ExchangeDataLoadingService.instance.setCurrenciesIfEmpty(
|
||||
ref.read(exchangeFormStateProvider),
|
||||
ref.read(efCurrencyPairProvider),
|
||||
ref.read(efRateTypeProvider),
|
||||
);
|
||||
setState(() {
|
||||
_initialCachePopulationUnderway = false;
|
||||
|
@ -53,7 +54,8 @@ class _ExchangeViewState extends ConsumerState<ExchangeView> {
|
|||
ExchangeDataLoadingService.instance.onLoadingComplete = () {
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
|
||||
await ExchangeDataLoadingService.instance.setCurrenciesIfEmpty(
|
||||
ref.read(exchangeFormStateProvider),
|
||||
ref.read(efCurrencyPairProvider),
|
||||
ref.read(efRateTypeProvider),
|
||||
);
|
||||
setState(() {
|
||||
_initialCachePopulationUnderway = false;
|
||||
|
|
|
@ -0,0 +1,349 @@
|
|||
import 'package:decimal/decimal.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:stackwallet/models/exchange/response_objects/estimate.dart';
|
||||
import 'package:stackwallet/providers/exchange/exchange_form_state_provider.dart';
|
||||
import 'package:stackwallet/providers/global/locale_provider.dart';
|
||||
import 'package:stackwallet/services/exchange/exchange.dart';
|
||||
import 'package:stackwallet/utilities/amount/amount.dart';
|
||||
import 'package:stackwallet/utilities/assets.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
import 'package:stackwallet/utilities/enums/exchange_rate_type_enum.dart';
|
||||
import 'package:stackwallet/utilities/logger.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/utilities/theme/stack_colors.dart';
|
||||
import 'package:stackwallet/utilities/util.dart';
|
||||
import 'package:stackwallet/widgets/animated_text.dart';
|
||||
import 'package:stackwallet/widgets/conditional_parent.dart';
|
||||
import 'package:stackwallet/widgets/exchange/trocador/trocador_kyc_info_button.dart';
|
||||
import 'package:stackwallet/widgets/exchange/trocador/trocador_rating_type_enum.dart';
|
||||
|
||||
class ExchangeOption extends ConsumerStatefulWidget {
|
||||
const ExchangeOption({
|
||||
Key? key,
|
||||
required this.exchange,
|
||||
required this.fixedRate,
|
||||
required this.reversed,
|
||||
}) : super(key: key);
|
||||
|
||||
final Exchange exchange;
|
||||
final bool fixedRate;
|
||||
final bool reversed;
|
||||
|
||||
@override
|
||||
ConsumerState<ExchangeOption> createState() => _ExchangeOptionState();
|
||||
}
|
||||
|
||||
class _ExchangeOptionState extends ConsumerState<ExchangeOption> {
|
||||
final isDesktop = Util.isDesktop;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final sendCurrency =
|
||||
ref.watch(efCurrencyPairProvider.select((value) => value.send));
|
||||
final receivingCurrency =
|
||||
ref.watch(efCurrencyPairProvider.select((value) => value.receive));
|
||||
final reversed = ref.watch(efReversedProvider);
|
||||
final amount = reversed
|
||||
? ref.watch(efReceiveAmountProvider)
|
||||
: ref.watch(efSendAmountProvider);
|
||||
|
||||
final data = ref.watch(efEstimatesListProvider(widget.exchange.name));
|
||||
final estimates = data?.item1.value;
|
||||
|
||||
return AnimatedSize(
|
||||
duration: const Duration(milliseconds: 500),
|
||||
curve: Curves.easeInOutCubicEmphasized,
|
||||
child: Builder(
|
||||
builder: (_) {
|
||||
if (ref.watch(efRefreshingProvider)) {
|
||||
// show loading
|
||||
return _ProviderOption(
|
||||
exchange: widget.exchange,
|
||||
estimate: null,
|
||||
rateString: "",
|
||||
loadingString: true,
|
||||
);
|
||||
} else if (sendCurrency != null &&
|
||||
receivingCurrency != null &&
|
||||
amount != null &&
|
||||
amount > Decimal.zero) {
|
||||
if (estimates != null && estimates.isNotEmpty) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
for (int i = 0; i < estimates.length; i++)
|
||||
Builder(
|
||||
builder: (context) {
|
||||
final e = estimates[i];
|
||||
|
||||
int decimals;
|
||||
try {
|
||||
decimals = coinFromTickerCaseInsensitive(
|
||||
receivingCurrency.ticker)
|
||||
.decimals;
|
||||
} catch (_) {
|
||||
decimals = 8; // some reasonable alternative
|
||||
}
|
||||
Amount rate;
|
||||
if (e.reversed) {
|
||||
rate = (amount / e.estimatedAmount)
|
||||
.toDecimal(scaleOnInfinitePrecision: 18)
|
||||
.toAmount(fractionDigits: decimals);
|
||||
} else {
|
||||
rate = (e.estimatedAmount / amount)
|
||||
.toDecimal(scaleOnInfinitePrecision: 18)
|
||||
.toAmount(fractionDigits: decimals);
|
||||
}
|
||||
|
||||
final rateString =
|
||||
"1 ${sendCurrency.ticker.toUpperCase()} ~ ${rate.localizedStringAsFixed(
|
||||
locale: ref.watch(
|
||||
localeServiceChangeNotifierProvider
|
||||
.select((value) => value.locale),
|
||||
),
|
||||
)} ${receivingCurrency.ticker.toUpperCase()}";
|
||||
|
||||
return ConditionalParent(
|
||||
condition: i > 0,
|
||||
builder: (child) => Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
isDesktop
|
||||
? Container(
|
||||
height: 1,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.background,
|
||||
)
|
||||
: const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
child,
|
||||
],
|
||||
),
|
||||
child: _ProviderOption(
|
||||
key: Key(widget.exchange.name + e.exchangeProvider),
|
||||
exchange: widget.exchange,
|
||||
estimate: e,
|
||||
rateString: rateString,
|
||||
kycRating: e.kycRating,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
} else {
|
||||
Logging.instance.log(
|
||||
"$runtimeType rate unavailable for ${widget.exchange.name}: $data",
|
||||
level: LogLevel.Warning,
|
||||
);
|
||||
|
||||
return Consumer(
|
||||
builder: (_, ref, __) {
|
||||
String? message;
|
||||
|
||||
final range = data?.item2;
|
||||
if (range != null) {
|
||||
if (range.min != null && amount < range.min!) {
|
||||
message ??= "Amount too small";
|
||||
} else if (range.max != null && amount > range.max!) {
|
||||
message ??= "Amount too large";
|
||||
}
|
||||
} else if (data?.item1.value == null) {
|
||||
final rateType = ref.watch(efRateTypeProvider) ==
|
||||
ExchangeRateType.estimated
|
||||
? "estimated"
|
||||
: "fixed";
|
||||
message ??= "Pair unavailable on $rateType rate flow";
|
||||
}
|
||||
|
||||
return _ProviderOption(
|
||||
exchange: widget.exchange,
|
||||
estimate: null,
|
||||
rateString: message ?? "Failed to fetch rate",
|
||||
rateColor:
|
||||
Theme.of(context).extension<StackColors>()!.textError,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// show n/a
|
||||
return _ProviderOption(
|
||||
exchange: widget.exchange,
|
||||
estimate: null,
|
||||
rateString: "n/a",
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ProviderOption extends ConsumerStatefulWidget {
|
||||
const _ProviderOption({
|
||||
Key? key,
|
||||
required this.exchange,
|
||||
required this.estimate,
|
||||
required this.rateString,
|
||||
this.kycRating,
|
||||
this.loadingString = false,
|
||||
this.rateColor,
|
||||
}) : super(key: key);
|
||||
|
||||
final Exchange exchange;
|
||||
final Estimate? estimate;
|
||||
final String rateString;
|
||||
final String? kycRating;
|
||||
final bool loadingString;
|
||||
final Color? rateColor;
|
||||
|
||||
@override
|
||||
ConsumerState<_ProviderOption> createState() => _ProviderOptionState();
|
||||
}
|
||||
|
||||
class _ProviderOptionState extends ConsumerState<_ProviderOption> {
|
||||
final isDesktop = Util.isDesktop;
|
||||
|
||||
late final String _id;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_id =
|
||||
"${widget.exchange.name} (${widget.estimate?.exchangeProvider ?? widget.exchange.name})";
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
String groupValue = ref.watch(currentCombinedExchangeIdProvider);
|
||||
|
||||
if (ref.watch(efExchangeProvider).name ==
|
||||
(widget.estimate?.exchangeProvider ?? widget.exchange.name)) {
|
||||
groupValue = _id;
|
||||
}
|
||||
|
||||
return ConditionalParent(
|
||||
condition: isDesktop,
|
||||
builder: (child) => MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: child,
|
||||
),
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
ref.read(efExchangeProvider.notifier).state = widget.exchange;
|
||||
ref.read(efExchangeProviderNameProvider.notifier).state =
|
||||
widget.estimate?.exchangeProvider ?? widget.exchange.name;
|
||||
},
|
||||
child: Container(
|
||||
color: Colors.transparent,
|
||||
child: Padding(
|
||||
padding:
|
||||
isDesktop ? const EdgeInsets.all(16) : const EdgeInsets.all(0),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(top: isDesktop ? 20.0 : 15.0),
|
||||
child: Radio(
|
||||
activeColor: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.radioButtonIconEnabled,
|
||||
value: _id,
|
||||
groupValue: groupValue,
|
||||
onChanged: (_) {
|
||||
ref.read(efExchangeProvider.notifier).state =
|
||||
widget.exchange;
|
||||
ref
|
||||
.read(efExchangeProviderNameProvider.notifier)
|
||||
.state =
|
||||
widget.estimate?.exchangeProvider ??
|
||||
widget.exchange.name;
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 14,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 5.0),
|
||||
child: SizedBox(
|
||||
width: isDesktop ? 32 : 24,
|
||||
height: isDesktop ? 32 : 24,
|
||||
child: SvgPicture.asset(
|
||||
Assets.exchange.getIconFor(
|
||||
exchangeName: widget.exchange.name,
|
||||
),
|
||||
width: isDesktop ? 32 : 24,
|
||||
height: isDesktop ? 32 : 24,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
widget.estimate?.exchangeProvider ??
|
||||
widget.exchange.name,
|
||||
style: STextStyles.titleBold12(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textDark2,
|
||||
),
|
||||
),
|
||||
widget.loadingString
|
||||
? AnimatedText(
|
||||
stringsToLoopThrough: const [
|
||||
"Loading",
|
||||
"Loading.",
|
||||
"Loading..",
|
||||
"Loading...",
|
||||
],
|
||||
style:
|
||||
STextStyles.itemSubtitle12(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textSubtitle1,
|
||||
),
|
||||
)
|
||||
: Text(
|
||||
widget.rateString,
|
||||
style:
|
||||
STextStyles.itemSubtitle12(context).copyWith(
|
||||
color: widget.rateColor ??
|
||||
Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textSubtitle1,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (widget.kycRating != null)
|
||||
TrocadorKYCInfoButton(
|
||||
kycType: TrocadorKYCType.fromString(
|
||||
widget.kycRating!,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,24 +1,13 @@
|
|||
import 'package:decimal/decimal.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:stackwallet/exceptions/exchange/pair_unavailable_exception.dart';
|
||||
import 'package:stackwallet/models/exchange/aggregate_currency.dart';
|
||||
import 'package:stackwallet/models/exchange/response_objects/estimate.dart';
|
||||
import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_provider_option.dart';
|
||||
import 'package:stackwallet/providers/providers.dart';
|
||||
import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart';
|
||||
import 'package:stackwallet/services/exchange/exchange_response.dart';
|
||||
import 'package:stackwallet/services/exchange/majestic_bank/majestic_bank_exchange.dart';
|
||||
import 'package:stackwallet/utilities/amount/amount.dart';
|
||||
import 'package:stackwallet/utilities/assets.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
import 'package:stackwallet/utilities/logger.dart';
|
||||
import 'package:stackwallet/utilities/show_loading.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/services/exchange/trocador/trocador_exchange.dart';
|
||||
import 'package:stackwallet/utilities/theme/stack_colors.dart';
|
||||
import 'package:stackwallet/utilities/util.dart';
|
||||
import 'package:stackwallet/widgets/animated_text.dart';
|
||||
import 'package:stackwallet/widgets/conditional_parent.dart';
|
||||
import 'package:stackwallet/widgets/rounded_white_container.dart';
|
||||
|
||||
class ExchangeProviderOptions extends ConsumerStatefulWidget {
|
||||
|
@ -60,11 +49,10 @@ class _ExchangeProviderOptionsState
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final sendCurrency = ref.watch(exchangeFormStateProvider).sendCurrency;
|
||||
final sendCurrency =
|
||||
ref.watch(efCurrencyPairProvider.select((value) => value.send));
|
||||
final receivingCurrency =
|
||||
ref.watch(exchangeFormStateProvider).receiveCurrency;
|
||||
final fromAmount = ref.watch(exchangeFormStateProvider).sendAmount;
|
||||
final toAmount = ref.watch(exchangeFormStateProvider).receiveAmount;
|
||||
ref.watch(efCurrencyPairProvider.select((value) => value.receive));
|
||||
|
||||
final showChangeNow = exchangeSupported(
|
||||
exchangeName: ChangeNowExchange.exchangeName,
|
||||
|
@ -76,6 +64,11 @@ class _ExchangeProviderOptionsState
|
|||
sendCurrency: sendCurrency,
|
||||
receiveCurrency: receivingCurrency,
|
||||
);
|
||||
final showTrocador = exchangeSupported(
|
||||
exchangeName: TrocadorExchange.exchangeName,
|
||||
sendCurrency: sendCurrency,
|
||||
receiveCurrency: receivingCurrency,
|
||||
);
|
||||
|
||||
return RoundedWhiteContainer(
|
||||
padding: isDesktop ? const EdgeInsets.all(0) : const EdgeInsets.all(12),
|
||||
|
@ -85,239 +78,11 @@ class _ExchangeProviderOptionsState
|
|||
child: Column(
|
||||
children: [
|
||||
if (showChangeNow)
|
||||
ConditionalParent(
|
||||
condition: isDesktop,
|
||||
builder: (child) => MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: child,
|
||||
),
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
if (ref.read(exchangeFormStateProvider).exchange.name !=
|
||||
ChangeNowExchange.exchangeName) {
|
||||
showLoading(
|
||||
whileFuture:
|
||||
ref.read(exchangeFormStateProvider).updateExchange(
|
||||
exchange: ChangeNowExchange.instance,
|
||||
shouldUpdateData: true,
|
||||
shouldNotifyListeners: true,
|
||||
),
|
||||
context: context,
|
||||
message: "Updating rates",
|
||||
isDesktop: isDesktop,
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
color: Colors.transparent,
|
||||
child: Padding(
|
||||
padding: isDesktop
|
||||
? const EdgeInsets.all(16)
|
||||
: const EdgeInsets.all(0),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: Padding(
|
||||
padding:
|
||||
EdgeInsets.only(top: isDesktop ? 20.0 : 15.0),
|
||||
child: Radio(
|
||||
activeColor: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.radioButtonIconEnabled,
|
||||
value: ChangeNowExchange.exchangeName,
|
||||
groupValue: ref.watch(exchangeFormStateProvider
|
||||
.select((value) => value.exchange.name)),
|
||||
onChanged: (_) {
|
||||
if (ref
|
||||
.read(exchangeFormStateProvider)
|
||||
.exchange
|
||||
.name !=
|
||||
ChangeNowExchange.exchangeName) {
|
||||
ref
|
||||
.read(exchangeFormStateProvider)
|
||||
.updateExchange(
|
||||
exchange: ChangeNowExchange.instance,
|
||||
shouldUpdateData: true,
|
||||
shouldNotifyListeners: true,
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 14,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 5.0),
|
||||
child: SizedBox(
|
||||
width: isDesktop ? 32 : 24,
|
||||
height: isDesktop ? 32 : 24,
|
||||
child: SvgPicture.asset(
|
||||
Assets.exchange.changeNow,
|
||||
width: isDesktop ? 32 : 24,
|
||||
height: isDesktop ? 32 : 24,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
ChangeNowExchange.exchangeName,
|
||||
style:
|
||||
STextStyles.titleBold12(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textDark2,
|
||||
),
|
||||
),
|
||||
if (sendCurrency != null &&
|
||||
receivingCurrency != null &&
|
||||
toAmount != null &&
|
||||
toAmount > Decimal.zero &&
|
||||
fromAmount != null &&
|
||||
fromAmount > Decimal.zero)
|
||||
FutureBuilder(
|
||||
future:
|
||||
ChangeNowExchange.instance.getEstimate(
|
||||
sendCurrency.ticker,
|
||||
receivingCurrency.ticker,
|
||||
widget.reversed ? toAmount : fromAmount,
|
||||
widget.fixedRate,
|
||||
widget.reversed,
|
||||
),
|
||||
builder: (context,
|
||||
AsyncSnapshot<ExchangeResponse<Estimate>>
|
||||
snapshot) {
|
||||
if (snapshot.connectionState ==
|
||||
ConnectionState.done &&
|
||||
snapshot.hasData) {
|
||||
final estimate = snapshot.data?.value;
|
||||
if (estimate != null) {
|
||||
Coin coin;
|
||||
try {
|
||||
coin = coinFromTickerCaseInsensitive(
|
||||
receivingCurrency.ticker);
|
||||
} catch (_) {
|
||||
coin = Coin.bitcoin;
|
||||
}
|
||||
Amount rate;
|
||||
if (estimate.reversed) {
|
||||
rate = (toAmount /
|
||||
estimate.estimatedAmount)
|
||||
.toDecimal(
|
||||
scaleOnInfinitePrecision: 18)
|
||||
.toAmount(
|
||||
fractionDigits:
|
||||
coin.decimals);
|
||||
} else {
|
||||
rate = (estimate.estimatedAmount /
|
||||
fromAmount)
|
||||
.toDecimal(
|
||||
scaleOnInfinitePrecision: 18)
|
||||
.toAmount(
|
||||
fractionDigits:
|
||||
coin.decimals);
|
||||
}
|
||||
|
||||
return Text(
|
||||
"1 ${sendCurrency.ticker.toUpperCase()} ~ ${rate.localizedStringAsFixed(
|
||||
locale: ref.watch(
|
||||
localeServiceChangeNotifierProvider
|
||||
.select(
|
||||
(value) => value.locale),
|
||||
),
|
||||
)} ${receivingCurrency.ticker.toUpperCase()}",
|
||||
style: STextStyles.itemSubtitle12(
|
||||
context)
|
||||
.copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textSubtitle1,
|
||||
),
|
||||
);
|
||||
} else if (snapshot.data?.exception
|
||||
is PairUnavailableException) {
|
||||
return Text(
|
||||
"Unsupported pair",
|
||||
style: STextStyles.itemSubtitle12(
|
||||
context)
|
||||
.copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textSubtitle1,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
Logging.instance.log(
|
||||
"$runtimeType failed to fetch rate for ChangeNOW: ${snapshot.data}",
|
||||
level: LogLevel.Warning,
|
||||
);
|
||||
return Text(
|
||||
"Failed to fetch rate",
|
||||
style: STextStyles.itemSubtitle12(
|
||||
context)
|
||||
.copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textSubtitle1,
|
||||
),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return AnimatedText(
|
||||
stringsToLoopThrough: const [
|
||||
"Loading",
|
||||
"Loading.",
|
||||
"Loading..",
|
||||
"Loading...",
|
||||
],
|
||||
style:
|
||||
STextStyles.itemSubtitle12(context)
|
||||
.copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textSubtitle1,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
if (!(sendCurrency != null &&
|
||||
receivingCurrency != null &&
|
||||
toAmount != null &&
|
||||
toAmount > Decimal.zero &&
|
||||
fromAmount != null &&
|
||||
fromAmount > Decimal.zero))
|
||||
Text(
|
||||
"n/a",
|
||||
style: STextStyles.itemSubtitle12(context)
|
||||
.copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textSubtitle1,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
ExchangeOption(
|
||||
exchange: ChangeNowExchange.instance,
|
||||
fixedRate: widget.fixedRate,
|
||||
reversed: widget.reversed,
|
||||
),
|
||||
|
||||
if (showChangeNow && showMajesticBank)
|
||||
isDesktop
|
||||
? Container(
|
||||
|
@ -328,446 +93,28 @@ class _ExchangeProviderOptionsState
|
|||
: const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
|
||||
if (showMajesticBank)
|
||||
ConditionalParent(
|
||||
condition: isDesktop,
|
||||
builder: (child) => MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: child,
|
||||
),
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
if (ref.read(exchangeFormStateProvider).exchange.name !=
|
||||
MajesticBankExchange.exchangeName) {
|
||||
showLoading(
|
||||
whileFuture:
|
||||
ref.read(exchangeFormStateProvider).updateExchange(
|
||||
exchange: MajesticBankExchange.instance,
|
||||
shouldUpdateData: true,
|
||||
shouldNotifyListeners: true,
|
||||
),
|
||||
context: context,
|
||||
isDesktop: isDesktop,
|
||||
message: "Updating rates",
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
color: Colors.transparent,
|
||||
child: Padding(
|
||||
padding: isDesktop
|
||||
? const EdgeInsets.all(16)
|
||||
: const EdgeInsets.all(0),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: Padding(
|
||||
padding:
|
||||
EdgeInsets.only(top: isDesktop ? 20.0 : 15.0),
|
||||
child: Radio(
|
||||
activeColor: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.radioButtonIconEnabled,
|
||||
value: MajesticBankExchange.exchangeName,
|
||||
groupValue: ref.watch(exchangeFormStateProvider
|
||||
.select((value) => value.exchange.name)),
|
||||
onChanged: (_) {
|
||||
if (ref
|
||||
.read(exchangeFormStateProvider)
|
||||
.exchange
|
||||
.name !=
|
||||
MajesticBankExchange.exchangeName) {
|
||||
ref
|
||||
.read(exchangeFormStateProvider)
|
||||
.updateExchange(
|
||||
exchange: MajesticBankExchange.instance,
|
||||
shouldUpdateData: true,
|
||||
shouldNotifyListeners: true,
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 14,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 5.0),
|
||||
child: SizedBox(
|
||||
width: isDesktop ? 32 : 24,
|
||||
height: isDesktop ? 32 : 24,
|
||||
child: SvgPicture.asset(
|
||||
Assets.exchange.majesticBankBlue,
|
||||
width: isDesktop ? 32 : 24,
|
||||
height: isDesktop ? 32 : 24,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
MajesticBankExchange.exchangeName,
|
||||
style:
|
||||
STextStyles.titleBold12(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textDark2,
|
||||
),
|
||||
),
|
||||
if (sendCurrency != null &&
|
||||
receivingCurrency != null &&
|
||||
toAmount != null &&
|
||||
toAmount > Decimal.zero &&
|
||||
fromAmount != null &&
|
||||
fromAmount > Decimal.zero)
|
||||
FutureBuilder(
|
||||
future:
|
||||
MajesticBankExchange.instance.getEstimate(
|
||||
sendCurrency.ticker,
|
||||
receivingCurrency.ticker,
|
||||
widget.reversed ? toAmount : fromAmount,
|
||||
widget.fixedRate,
|
||||
widget.reversed,
|
||||
),
|
||||
builder: (context,
|
||||
AsyncSnapshot<ExchangeResponse<Estimate>>
|
||||
snapshot) {
|
||||
if (snapshot.connectionState ==
|
||||
ConnectionState.done &&
|
||||
snapshot.hasData) {
|
||||
final estimate = snapshot.data?.value;
|
||||
if (estimate != null) {
|
||||
Coin coin;
|
||||
try {
|
||||
coin = coinFromTickerCaseInsensitive(
|
||||
receivingCurrency.ticker);
|
||||
} catch (_) {
|
||||
coin = Coin.bitcoin;
|
||||
}
|
||||
Amount rate;
|
||||
if (estimate.reversed) {
|
||||
rate = (toAmount /
|
||||
estimate.estimatedAmount)
|
||||
.toDecimal(
|
||||
scaleOnInfinitePrecision: 18)
|
||||
.toAmount(
|
||||
fractionDigits: coin.decimals,
|
||||
);
|
||||
} else {
|
||||
rate = (estimate.estimatedAmount /
|
||||
fromAmount)
|
||||
.toDecimal(
|
||||
scaleOnInfinitePrecision: 18)
|
||||
.toAmount(
|
||||
fractionDigits: coin.decimals,
|
||||
);
|
||||
}
|
||||
|
||||
return Text(
|
||||
"1 ${sendCurrency.ticker.toUpperCase()} ~ ${rate.localizedStringAsFixed(
|
||||
locale: ref.watch(
|
||||
localeServiceChangeNotifierProvider
|
||||
.select(
|
||||
(value) => value.locale),
|
||||
),
|
||||
)} ${receivingCurrency.ticker.toUpperCase()}",
|
||||
style: STextStyles.itemSubtitle12(
|
||||
context)
|
||||
.copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textSubtitle1,
|
||||
),
|
||||
);
|
||||
} else if (snapshot.data?.exception
|
||||
is PairUnavailableException) {
|
||||
return Text(
|
||||
"Unsupported pair",
|
||||
style: STextStyles.itemSubtitle12(
|
||||
context)
|
||||
.copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textSubtitle1,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
Logging.instance.log(
|
||||
"$runtimeType failed to fetch rate for ChangeNOW: ${snapshot.data}",
|
||||
level: LogLevel.Warning,
|
||||
);
|
||||
return Text(
|
||||
"Failed to fetch rate",
|
||||
style: STextStyles.itemSubtitle12(
|
||||
context)
|
||||
.copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textSubtitle1,
|
||||
),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return AnimatedText(
|
||||
stringsToLoopThrough: const [
|
||||
"Loading",
|
||||
"Loading.",
|
||||
"Loading..",
|
||||
"Loading...",
|
||||
],
|
||||
style:
|
||||
STextStyles.itemSubtitle12(context)
|
||||
.copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textSubtitle1,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
if (!(sendCurrency != null &&
|
||||
receivingCurrency != null &&
|
||||
toAmount != null &&
|
||||
toAmount > Decimal.zero &&
|
||||
fromAmount != null &&
|
||||
fromAmount > Decimal.zero))
|
||||
Text(
|
||||
"n/a",
|
||||
style: STextStyles.itemSubtitle12(context)
|
||||
.copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textSubtitle1,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
ExchangeOption(
|
||||
exchange: MajesticBankExchange.instance,
|
||||
fixedRate: widget.fixedRate,
|
||||
reversed: widget.reversed,
|
||||
),
|
||||
if ((showChangeNow || showMajesticBank) && showTrocador)
|
||||
isDesktop
|
||||
? Container(
|
||||
height: 1,
|
||||
color:
|
||||
Theme.of(context).extension<StackColors>()!.background,
|
||||
)
|
||||
: const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
if (showTrocador)
|
||||
ExchangeOption(
|
||||
fixedRate: widget.fixedRate,
|
||||
reversed: widget.reversed,
|
||||
exchange: TrocadorExchange.instance,
|
||||
),
|
||||
// if (isDesktop)
|
||||
// Container(
|
||||
// height: 1,
|
||||
// color: Theme.of(context).extension<StackColors>()!.background,
|
||||
// ),
|
||||
// if (!isDesktop)
|
||||
// const SizedBox(
|
||||
// height: 16,
|
||||
// ),
|
||||
// ConditionalParent(
|
||||
// condition: isDesktop,
|
||||
// builder: (child) => MouseRegion(
|
||||
// cursor: SystemMouseCursors.click,
|
||||
// child: child,
|
||||
// ),
|
||||
// child: GestureDetector(
|
||||
// onTap: () {
|
||||
// if (ref.read(currentExchangeNameStateProvider.state).state !=
|
||||
// SimpleSwapExchange.exchangeName) {
|
||||
// // ref.read(currentExchangeNameStateProvider.state).state =
|
||||
// // SimpleSwapExchange.exchangeName;
|
||||
// ref.read(exchangeFormStateProvider).exchange =
|
||||
// Exchange.fromName(ref
|
||||
// .read(currentExchangeNameStateProvider.state)
|
||||
// .state);
|
||||
// }
|
||||
// },
|
||||
// child: Container(
|
||||
// color: Colors.transparent,
|
||||
// child: Padding(
|
||||
// padding: isDesktop
|
||||
// ? const EdgeInsets.all(16)
|
||||
// : const EdgeInsets.all(0),
|
||||
// child: Row(
|
||||
// crossAxisAlignment: CrossAxisAlignment.start,
|
||||
// children: [
|
||||
// SizedBox(
|
||||
// width: 20,
|
||||
// height: 20,
|
||||
// child: Radio(
|
||||
// activeColor: Theme.of(context)
|
||||
// .extension<StackColors>()!
|
||||
// .radioButtonIconEnabled,
|
||||
// value: SimpleSwapExchange.exchangeName,
|
||||
// groupValue: ref
|
||||
// .watch(currentExchangeNameStateProvider.state)
|
||||
// .state,
|
||||
// onChanged: (value) {
|
||||
// if (value is String) {
|
||||
// ref
|
||||
// .read(currentExchangeNameStateProvider.state)
|
||||
// .state = value;
|
||||
// ref.read(exchangeFormStateProvider).exchange =
|
||||
// Exchange.fromName(ref
|
||||
// .read(currentExchangeNameStateProvider
|
||||
// .state)
|
||||
// .state);
|
||||
// }
|
||||
// },
|
||||
// ),
|
||||
// ),
|
||||
// const SizedBox(
|
||||
// width: 14,
|
||||
// ),
|
||||
// // SvgPicture.asset(
|
||||
// // Assets.exchange.simpleSwap,
|
||||
// // width: isDesktop ? 32 : 24,
|
||||
// // height: isDesktop ? 32 : 24,
|
||||
// // ),
|
||||
// // const SizedBox(
|
||||
// // width: 10,
|
||||
// // ),
|
||||
// // Expanded(
|
||||
// // child: Column(
|
||||
// // mainAxisAlignment: MainAxisAlignment.start,
|
||||
// // mainAxisSize: MainAxisSize.min,
|
||||
// // crossAxisAlignment: CrossAxisAlignment.start,
|
||||
// // children: [
|
||||
// // Text(
|
||||
// // SimpleSwapExchange.exchangeName,
|
||||
// // style: STextStyles.titleBold12(context).copyWith(
|
||||
// // color: Theme.of(context)
|
||||
// // .extension<StackColors>()!
|
||||
// // .textDark2,
|
||||
// // ),
|
||||
// // ),
|
||||
// // if (from != null &&
|
||||
// // to != null &&
|
||||
// // toAmount != null &&
|
||||
// // toAmount! > Decimal.zero &&
|
||||
// // fromAmount != null &&
|
||||
// // fromAmount! > Decimal.zero)
|
||||
// // FutureBuilder(
|
||||
// // future: SimpleSwapExchange().getEstimate(
|
||||
// // from!,
|
||||
// // to!,
|
||||
// // // reversed ? toAmount! : fromAmount!,
|
||||
// // fromAmount!,
|
||||
// // fixedRate,
|
||||
// // // reversed,
|
||||
// // false,
|
||||
// // ),
|
||||
// // builder: (context,
|
||||
// // AsyncSnapshot<ExchangeResponse<Estimate>>
|
||||
// // snapshot) {
|
||||
// // if (snapshot.connectionState ==
|
||||
// // ConnectionState.done &&
|
||||
// // snapshot.hasData) {
|
||||
// // final estimate = snapshot.data?.value;
|
||||
// // if (estimate != null) {
|
||||
// // Decimal rate = (estimate.estimatedAmount /
|
||||
// // fromAmount!)
|
||||
// // .toDecimal(
|
||||
// // scaleOnInfinitePrecision: 12);
|
||||
// //
|
||||
// // Coin coin;
|
||||
// // try {
|
||||
// // coin =
|
||||
// // coinFromTickerCaseInsensitive(to!);
|
||||
// // } catch (_) {
|
||||
// // coin = Coin.bitcoin;
|
||||
// // }
|
||||
// // return Text(
|
||||
// // "1 ${from!.toUpperCase()} ~ ${Format.localizedStringAsFixed(
|
||||
// // value: rate,
|
||||
// // locale: ref.watch(
|
||||
// // localeServiceChangeNotifierProvider
|
||||
// // .select(
|
||||
// // (value) => value.locale),
|
||||
// // ),
|
||||
// // decimalPlaces:
|
||||
// // Constants.decimalPlacesForCoin(
|
||||
// // coin),
|
||||
// // )} ${to!.toUpperCase()}",
|
||||
// // style:
|
||||
// // STextStyles.itemSubtitle12(context)
|
||||
// // .copyWith(
|
||||
// // color: Theme.of(context)
|
||||
// // .extension<StackColors>()!
|
||||
// // .textSubtitle1,
|
||||
// // ),
|
||||
// // );
|
||||
// // } else {
|
||||
// // Logging.instance.log(
|
||||
// // "$runtimeType failed to fetch rate for SimpleSwap: ${snapshot.data}",
|
||||
// // level: LogLevel.Warning,
|
||||
// // );
|
||||
// // return Text(
|
||||
// // "Failed to fetch rate",
|
||||
// // style:
|
||||
// // STextStyles.itemSubtitle12(context)
|
||||
// // .copyWith(
|
||||
// // color: Theme.of(context)
|
||||
// // .extension<StackColors>()!
|
||||
// // .textSubtitle1,
|
||||
// // ),
|
||||
// // );
|
||||
// // }
|
||||
// // } else {
|
||||
// // return AnimatedText(
|
||||
// // stringsToLoopThrough: const [
|
||||
// // "Loading",
|
||||
// // "Loading.",
|
||||
// // "Loading..",
|
||||
// // "Loading...",
|
||||
// // ],
|
||||
// // style: STextStyles.itemSubtitle12(context)
|
||||
// // .copyWith(
|
||||
// // color: Theme.of(context)
|
||||
// // .extension<StackColors>()!
|
||||
// // .textSubtitle1,
|
||||
// // ),
|
||||
// // );
|
||||
// // }
|
||||
// // },
|
||||
// // ),
|
||||
// // // if (!(from != null &&
|
||||
// // // to != null &&
|
||||
// // // (reversed
|
||||
// // // ? toAmount != null && toAmount! > Decimal.zero
|
||||
// // // : fromAmount != null &&
|
||||
// // // fromAmount! > Decimal.zero)))
|
||||
// // if (!(from != null &&
|
||||
// // to != null &&
|
||||
// // toAmount != null &&
|
||||
// // toAmount! > Decimal.zero &&
|
||||
// // fromAmount != null &&
|
||||
// // fromAmount! > Decimal.zero))
|
||||
// // Text(
|
||||
// // "n/a",
|
||||
// // style: STextStyles.itemSubtitle12(context)
|
||||
// // .copyWith(
|
||||
// // color: Theme.of(context)
|
||||
// // .extension<StackColors>()!
|
||||
// // .textSubtitle1,
|
||||
// // ),
|
||||
// // ),
|
||||
// // ],
|
||||
// // ),
|
||||
// // ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
|
|
@ -29,9 +29,7 @@ class RateTypeToggle extends ConsumerWidget {
|
|||
onChanged?.call(ExchangeRateType.estimated);
|
||||
}
|
||||
},
|
||||
isOn: ref.watch(exchangeFormStateProvider
|
||||
.select((value) => value.exchangeRateType)) ==
|
||||
ExchangeRateType.fixed,
|
||||
isOn: ref.watch(efRateTypeProvider) == ExchangeRateType.fixed,
|
||||
onColor: isDesktop
|
||||
? Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
|
|
|
@ -20,6 +20,7 @@ import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dar
|
|||
import 'package:stackwallet/services/exchange/exchange.dart';
|
||||
import 'package:stackwallet/services/exchange/majestic_bank/majestic_bank_exchange.dart';
|
||||
import 'package:stackwallet/services/exchange/simpleswap/simpleswap_exchange.dart';
|
||||
import 'package:stackwallet/services/exchange/trocador/trocador_exchange.dart';
|
||||
import 'package:stackwallet/utilities/amount/amount.dart';
|
||||
import 'package:stackwallet/utilities/assets.dart';
|
||||
import 'package:stackwallet/utilities/clipboard_interface.dart';
|
||||
|
@ -1205,6 +1206,13 @@ class _TradeDetailsViewState extends ConsumerState<TradeDetailsView> {
|
|||
url =
|
||||
"https://majesticbank.sc/track?trx=${trade.tradeId}";
|
||||
break;
|
||||
|
||||
default:
|
||||
if (trade.exchangeName
|
||||
.startsWith(TrocadorExchange.exchangeName)) {
|
||||
url =
|
||||
"https://trocador.app/en/checkout${trade.tradeId}";
|
||||
}
|
||||
}
|
||||
return ConditionalParent(
|
||||
condition: isDesktop,
|
||||
|
|
|
@ -54,7 +54,8 @@ class _WalletInitiatedExchangeViewState
|
|||
ExchangeDataLoadingService.instance.onLoadingComplete = () {
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
|
||||
await ExchangeDataLoadingService.instance.setCurrenciesIfEmpty(
|
||||
ref.read(exchangeFormStateProvider),
|
||||
ref.read(efCurrencyPairProvider),
|
||||
ref.read(efRateTypeProvider),
|
||||
);
|
||||
setState(() {
|
||||
_initialCachePopulationUnderway = false;
|
||||
|
@ -70,7 +71,8 @@ class _WalletInitiatedExchangeViewState
|
|||
ExchangeDataLoadingService.instance.onLoadingComplete = () {
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
|
||||
await ExchangeDataLoadingService.instance.setCurrenciesIfEmpty(
|
||||
ref.read(exchangeFormStateProvider),
|
||||
ref.read(efCurrencyPairProvider),
|
||||
ref.read(efRateTypeProvider),
|
||||
);
|
||||
setState(() {
|
||||
_initialCachePopulationUnderway = false;
|
||||
|
|
|
@ -55,7 +55,6 @@ class LockscreenView extends ConsumerStatefulWidget {
|
|||
final VoidCallback? onSuccess;
|
||||
final String customKeyLabel;
|
||||
|
||||
|
||||
@override
|
||||
ConsumerState<LockscreenView> createState() => _LockscreenViewState();
|
||||
}
|
||||
|
@ -205,49 +204,58 @@ class _LockscreenViewState extends ConsumerState<LockscreenView> {
|
|||
late Biometrics biometrics;
|
||||
|
||||
Widget get _body => Background(
|
||||
child: Scaffold(
|
||||
backgroundColor:
|
||||
Theme.of(context).extension<StackColors>()!.background,
|
||||
appBar: AppBar(
|
||||
leading: widget.showBackButton
|
||||
? AppBarBackButton(
|
||||
onPressed: () async {
|
||||
if (FocusScope.of(context).hasFocus) {
|
||||
FocusScope.of(context).unfocus();
|
||||
await Future<void>.delayed(
|
||||
const Duration(milliseconds: 70));
|
||||
}
|
||||
if (mounted) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
)
|
||||
: Container(),
|
||||
),
|
||||
body: SafeArea(
|
||||
child: Column(
|
||||
child: SafeArea(
|
||||
child: Scaffold(
|
||||
extendBodyBehindAppBar: true,
|
||||
backgroundColor:
|
||||
Theme.of(context).extension<StackColors>()!.background,
|
||||
appBar: AppBar(
|
||||
leading: widget.showBackButton
|
||||
? AppBarBackButton(
|
||||
onPressed: () async {
|
||||
if (FocusScope.of(context).hasFocus) {
|
||||
FocusScope.of(context).unfocus();
|
||||
await Future<void>.delayed(
|
||||
const Duration(milliseconds: 70));
|
||||
}
|
||||
if (mounted) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
)
|
||||
: Container(),
|
||||
actions: [
|
||||
// check prefs and hide if user has biometrics toggle off?
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
right: 16.0,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
if (ref
|
||||
.read(prefsChangeNotifierProvider)
|
||||
.useBiometrics ==
|
||||
true)
|
||||
CustomTextButton(
|
||||
text: "Use biometrics",
|
||||
onTap: () async {
|
||||
await _checkUseBiometrics();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
// check prefs and hide if user has biometrics toggle off?
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 40.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
if (ref.read(prefsChangeNotifierProvider).useBiometrics ==
|
||||
true)
|
||||
CustomTextButton(
|
||||
text: "Use biometrics",
|
||||
onTap: () async {
|
||||
await _checkUseBiometrics();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 55,
|
||||
),
|
||||
Shake(
|
||||
animationDuration: const Duration(milliseconds: 700),
|
||||
animationRange: 12,
|
||||
|
|
|
@ -1,15 +1,18 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:stackwallet/pages/settings_views/global_settings_view/advanced_views/debug_view.dart';
|
||||
import 'package:stackwallet/pages/settings_views/global_settings_view/advanced_views/manage_explorer_view.dart';
|
||||
import 'package:stackwallet/pages/stack_privacy_calls.dart';
|
||||
import 'package:stackwallet/providers/global/prefs_provider.dart';
|
||||
import 'package:stackwallet/utilities/constants.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/utilities/theme/stack_colors.dart';
|
||||
import 'package:stackwallet/widgets/background.dart';
|
||||
import 'package:stackwallet/widgets/choose_coin_view.dart';
|
||||
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
import 'package:stackwallet/widgets/custom_buttons/draggable_switch_button.dart';
|
||||
import 'package:stackwallet/widgets/rounded_white_container.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
class AdvancedSettingsView extends StatelessWidget {
|
||||
const AdvancedSettingsView({
|
||||
|
@ -221,6 +224,43 @@ class AdvancedSettingsView extends StatelessWidget {
|
|||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
RoundedWhiteContainer(
|
||||
padding: const EdgeInsets.all(0),
|
||||
child: RawMaterialButton(
|
||||
// splashColor: Theme.of(context).extension<StackColors>()!.highlight,
|
||||
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pushNamed(ChooseCoinView.routeName,
|
||||
arguments: const Tuple3<String, String, String>(
|
||||
"Manage block explorers",
|
||||
"block explorer",
|
||||
ManageExplorerView.routeName));
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 20,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
"Change block explorer",
|
||||
style: STextStyles.titleBold12(context),
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
@ -0,0 +1,117 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:stackwallet/utilities/block_explorers.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/utilities/theme/stack_colors.dart';
|
||||
import 'package:stackwallet/widgets/background.dart';
|
||||
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
import 'package:stackwallet/widgets/rounded_white_container.dart';
|
||||
|
||||
class ManageExplorerView extends ConsumerStatefulWidget {
|
||||
const ManageExplorerView({
|
||||
Key? key,
|
||||
required this.coin,
|
||||
}) : super(key: key);
|
||||
|
||||
static const String routeName = "/manageExplorer";
|
||||
|
||||
final Coin coin;
|
||||
|
||||
@override
|
||||
ConsumerState<ManageExplorerView> createState() => _ManageExplorerViewState();
|
||||
}
|
||||
|
||||
class _ManageExplorerViewState extends ConsumerState<ManageExplorerView> {
|
||||
late TextEditingController textEditingController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
textEditingController = TextEditingController(
|
||||
text:
|
||||
getBlockExplorerTransactionUrlFor(coin: widget.coin, txid: "[TXID]")
|
||||
.toString()
|
||||
.replaceAll("%5BTXID%5D", "[TXID]"));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Background(
|
||||
child: Scaffold(
|
||||
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
|
||||
appBar: AppBar(
|
||||
leading: AppBarBackButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
title: Text(
|
||||
"${widget.coin.prettyName} block explorer",
|
||||
style: STextStyles.navBarTitle(context),
|
||||
),
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
children: [
|
||||
TextField(
|
||||
controller: textEditingController,
|
||||
decoration: const InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
RoundedWhiteContainer(
|
||||
child: Center(
|
||||
child: Text(
|
||||
"Edit your block explorer above. Keep in mind that "
|
||||
"every block explorer has a slightly different URL "
|
||||
"scheme.\n\nPaste in your block explorer of choice,"
|
||||
" then edit in [TXID] where the transaction ID "
|
||||
"should go, and Stack Wallet will auto fill the "
|
||||
"transaction ID in that place of URL.",
|
||||
style: STextStyles.itemSubtitle(context),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
)),
|
||||
Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
minWidth: 480,
|
||||
minHeight: 70,
|
||||
),
|
||||
child: TextButton(
|
||||
style: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.getPrimaryEnabledButtonStyle(context),
|
||||
onPressed: () {
|
||||
textEditingController.text =
|
||||
textEditingController.text.trim();
|
||||
setBlockExplorerForCoin(
|
||||
coin: widget.coin,
|
||||
url: Uri.parse(textEditingController.text))
|
||||
.then((value) => Navigator.of(context).pop());
|
||||
},
|
||||
child: Text(
|
||||
"Save",
|
||||
style: STextStyles.button(context),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -94,7 +94,10 @@ class _TransactionsListState extends ConsumerState<TransactionsList> {
|
|||
TransactionCard(
|
||||
// this may mess with combined firo transactions
|
||||
key: isConfirmed
|
||||
? Key(tx.txid + tx.type.name + tx.address.value.toString())
|
||||
? Key(tx.txid +
|
||||
tx.type.name +
|
||||
tx.address.value.toString() +
|
||||
tx.height.toString())
|
||||
: UniqueKey(), //
|
||||
transaction: tx,
|
||||
walletId: widget.walletId,
|
||||
|
@ -191,7 +194,10 @@ class _TransactionsListState extends ConsumerState<TransactionsList> {
|
|||
child: TransactionCard(
|
||||
// this may mess with combined firo transactions
|
||||
key: isConfirmed
|
||||
? Key(tx.txid + tx.type.name + tx.address.value.toString())
|
||||
? Key(tx.txid +
|
||||
tx.type.name +
|
||||
tx.address.value.toString() +
|
||||
tx.height.toString())
|
||||
: UniqueKey(),
|
||||
transaction: tx,
|
||||
walletId: widget.walletId,
|
||||
|
|
|
@ -112,10 +112,11 @@ class _TransactionDetailsViewState
|
|||
super.dispose();
|
||||
}
|
||||
|
||||
String whatIsIt(TransactionType type, int height) {
|
||||
String whatIsIt(Transaction tx, int height) {
|
||||
final type = tx.type;
|
||||
if (coin == Coin.firo || coin == Coin.firoTestNet) {
|
||||
if (_transaction.subType == TransactionSubType.mint) {
|
||||
if (_transaction.isConfirmed(height, coin.requiredConfirmations)) {
|
||||
if (tx.subType == TransactionSubType.mint) {
|
||||
if (tx.isConfirmed(height, coin.requiredConfirmations)) {
|
||||
return "Minted";
|
||||
} else {
|
||||
return "Minting";
|
||||
|
@ -127,13 +128,13 @@ class _TransactionDetailsViewState
|
|||
// if (_transaction.isMinting) {
|
||||
// return "Minting";
|
||||
// } else
|
||||
if (_transaction.isConfirmed(height, coin.requiredConfirmations)) {
|
||||
if (tx.isConfirmed(height, coin.requiredConfirmations)) {
|
||||
return "Received";
|
||||
} else {
|
||||
return "Receiving";
|
||||
}
|
||||
} else if (type == TransactionType.outgoing) {
|
||||
if (_transaction.isConfirmed(height, coin.requiredConfirmations)) {
|
||||
if (tx.isConfirmed(height, coin.requiredConfirmations)) {
|
||||
return "Sent";
|
||||
} else {
|
||||
return "Sending";
|
||||
|
@ -428,7 +429,7 @@ class _TransactionDetailsViewState
|
|||
_transaction.isCancelled
|
||||
? "Cancelled"
|
||||
: whatIsIt(
|
||||
_transaction.type,
|
||||
_transaction,
|
||||
currentHeight,
|
||||
),
|
||||
style:
|
||||
|
@ -545,7 +546,7 @@ class _TransactionDetailsViewState
|
|||
_transaction.isCancelled
|
||||
? "Cancelled"
|
||||
: whatIsIt(
|
||||
_transaction.type,
|
||||
_transaction,
|
||||
currentHeight,
|
||||
),
|
||||
style: isDesktop
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:stackwallet/models/add_wallet_list_entity/sub_classes/coin_entity.dart';
|
||||
import 'package:stackwallet/models/isar/models/ethereum/eth_contract.dart';
|
||||
import 'package:stackwallet/pages/add_wallet_views/create_or_restore_wallet_view/create_or_restore_wallet_view.dart';
|
||||
import 'package:stackwallet/pages_desktop_specific/my_stack_view/dialogs/desktop_expanding_wallet_card.dart';
|
||||
import 'package:stackwallet/providers/db/main_db_provider.dart';
|
||||
import 'package:stackwallet/providers/providers.dart';
|
||||
|
@ -174,6 +176,27 @@ class _EthWalletsOverviewState extends ConsumerState<WalletsOverview> {
|
|||
"${widget.coin.prettyName} (${widget.coin.ticker}) wallets",
|
||||
style: STextStyles.navBarTitle(context),
|
||||
),
|
||||
actions: [
|
||||
AspectRatio(
|
||||
aspectRatio: 1,
|
||||
child: AppBarIconButton(
|
||||
icon: SvgPicture.asset(
|
||||
Assets.svg.plus,
|
||||
width: 18,
|
||||
height: 18,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.topNavIconPrimary,
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pushNamed(
|
||||
CreateOrRestoreWalletView.routeName,
|
||||
arguments: CoinEntity(widget.coin),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: SafeArea(
|
||||
child: Padding(
|
||||
|
|
|
@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
|
|||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:stackwallet/db/isar/main_db.dart';
|
||||
import 'package:stackwallet/models/exchange/change_now/exchange_transaction_status.dart';
|
||||
import 'package:stackwallet/models/exchange/response_objects/trade.dart';
|
||||
import 'package:stackwallet/models/isar/models/isar_models.dart';
|
||||
|
@ -29,8 +30,6 @@ import 'package:stackwallet/widgets/stack_text_field.dart';
|
|||
import 'package:stackwallet/widgets/textfield_icon_button.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
import '../../db/isar/main_db.dart';
|
||||
|
||||
class DesktopAllTradesView extends ConsumerStatefulWidget {
|
||||
const DesktopAllTradesView({Key? key}) : super(key: key);
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:stackwallet/pages/exchange_view/exchange_form.dart';
|
||||
import 'package:stackwallet/pages_desktop_specific/desktop_exchange/desktop_all_trades_view.dart';
|
||||
import 'package:stackwallet/pages_desktop_specific/desktop_exchange/subwidgets/desktop_trade_history.dart';
|
||||
import 'package:stackwallet/providers/exchange/exchange_form_state_provider.dart';
|
||||
import 'package:stackwallet/providers/global/prefs_provider.dart';
|
||||
|
@ -14,8 +15,6 @@ import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
|
|||
import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
|
||||
import 'package:stackwallet/widgets/rounded_white_container.dart';
|
||||
|
||||
import 'desktop_all_trades_view.dart';
|
||||
|
||||
class DesktopExchangeView extends ConsumerStatefulWidget {
|
||||
const DesktopExchangeView({Key? key}) : super(key: key);
|
||||
|
||||
|
@ -38,7 +37,8 @@ class _DesktopExchangeViewState extends ConsumerState<DesktopExchangeView> {
|
|||
ExchangeDataLoadingService.instance.onLoadingComplete = () {
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
|
||||
await ExchangeDataLoadingService.instance.setCurrenciesIfEmpty(
|
||||
ref.read(exchangeFormStateProvider),
|
||||
ref.read(efCurrencyPairProvider),
|
||||
ref.read(efRateTypeProvider),
|
||||
);
|
||||
setState(() {
|
||||
_initialCachePopulationUnderway = false;
|
||||
|
@ -54,7 +54,8 @@ class _DesktopExchangeViewState extends ConsumerState<DesktopExchangeView> {
|
|||
ExchangeDataLoadingService.instance.onLoadingComplete = () {
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
|
||||
await ExchangeDataLoadingService.instance.setCurrenciesIfEmpty(
|
||||
ref.read(exchangeFormStateProvider),
|
||||
ref.read(efCurrencyPairProvider),
|
||||
ref.read(efRateTypeProvider),
|
||||
);
|
||||
setState(() {
|
||||
_initialCachePopulationUnderway = false;
|
||||
|
@ -149,10 +150,14 @@ class _DesktopExchangeViewState extends ConsumerState<DesktopExchangeView> {
|
|||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Expanded(
|
||||
child: RoundedWhiteContainer(
|
||||
padding: EdgeInsets.all(24),
|
||||
child: ExchangeForm(),
|
||||
Expanded(
|
||||
child: ListView(
|
||||
children: const [
|
||||
RoundedWhiteContainer(
|
||||
padding: EdgeInsets.all(24),
|
||||
child: ExchangeForm(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
|
|
|
@ -84,8 +84,7 @@ class _StepScaffoldState extends ConsumerState<StepScaffold> {
|
|||
);
|
||||
|
||||
final ExchangeResponse<Trade> response = await ref
|
||||
.read(exchangeFormStateProvider)
|
||||
.exchange
|
||||
.read(efExchangeProvider)
|
||||
.createTrade(
|
||||
from: ref.read(desktopExchangeModelProvider)!.sendTicker,
|
||||
to: ref.read(desktopExchangeModelProvider)!.receiveTicker,
|
||||
|
@ -98,24 +97,24 @@ class _StepScaffoldState extends ConsumerState<StepScaffold> {
|
|||
extraId: null,
|
||||
addressRefund: ref.read(desktopExchangeModelProvider)!.refundAddress!,
|
||||
refundExtraId: "",
|
||||
rateId: ref.read(desktopExchangeModelProvider)!.rateId,
|
||||
estimate: ref.read(desktopExchangeModelProvider)!.estimate,
|
||||
reversed: ref.read(desktopExchangeModelProvider)!.reversed,
|
||||
);
|
||||
|
||||
if (response.value == null) {
|
||||
if (mounted) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
unawaited(
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
barrierDismissible: true,
|
||||
builder: (_) => SimpleDesktopDialog(
|
||||
title: "Failed to create trade",
|
||||
message: response.exception?.toString() ?? ""),
|
||||
),
|
||||
);
|
||||
unawaited(
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
barrierDismissible: true,
|
||||
builder: (_) => SimpleDesktopDialog(
|
||||
title: "Failed to create trade",
|
||||
message: response.exception?.toString() ?? ""),
|
||||
),
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -38,8 +38,7 @@ class DesktopStep1 extends ConsumerWidget {
|
|||
children: [
|
||||
DesktopStepItem(
|
||||
label: "Swap",
|
||||
value: ref.watch(exchangeFormStateProvider
|
||||
.select((value) => value.exchange.name)),
|
||||
value: ref.watch(efExchangeProviderNameProvider),
|
||||
),
|
||||
Container(
|
||||
height: 1,
|
||||
|
|
|
@ -36,8 +36,7 @@ class _DesktopStep3State extends ConsumerState<DesktopStep3> {
|
|||
children: [
|
||||
DesktopStepItem(
|
||||
label: "Swap",
|
||||
value: ref.watch(exchangeFormStateProvider
|
||||
.select((value) => value.exchange.name)),
|
||||
value: ref.watch(efExchangeProviderNameProvider),
|
||||
),
|
||||
Container(
|
||||
height: 1,
|
||||
|
|
|
@ -46,7 +46,7 @@ class _DesktopStep4State extends ConsumerState<DesktopStep4> {
|
|||
}
|
||||
|
||||
final statusResponse =
|
||||
await ref.read(exchangeFormStateProvider).exchange.updateTrade(trade);
|
||||
await ref.read(efExchangeProvider).updateTrade(trade);
|
||||
String status = "Waiting";
|
||||
if (statusResponse.value != null) {
|
||||
status = statusResponse.value!.status;
|
||||
|
|
|
@ -269,13 +269,6 @@ class _DesktopTradeHistoryState extends ConsumerState<DesktopTradeHistory> {
|
|||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Recent trades",
|
||||
style: STextStyles.desktopTextExtraExtraSmall(context),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
RoundedWhiteContainer(
|
||||
child: Center(
|
||||
child: Text(
|
||||
|
|
|
@ -1,5 +1,91 @@
|
|||
import 'package:decimal/decimal.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:stackwallet/models/exchange/exchange_form_state.dart';
|
||||
import 'package:stackwallet/models/exchange/active_pair.dart';
|
||||
import 'package:stackwallet/models/exchange/response_objects/estimate.dart';
|
||||
import 'package:stackwallet/models/exchange/response_objects/range.dart';
|
||||
import 'package:stackwallet/services/exchange/exchange.dart';
|
||||
import 'package:stackwallet/services/exchange/exchange_response.dart';
|
||||
import 'package:stackwallet/utilities/enums/exchange_rate_type_enum.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
final exchangeFormStateProvider =
|
||||
ChangeNotifierProvider<ExchangeFormState>((ref) => ExchangeFormState());
|
||||
final efEstimatesListProvider = StateProvider.family<
|
||||
Tuple2<ExchangeResponse<List<Estimate>>, Range?>?,
|
||||
String>((ref, exchangeName) => null);
|
||||
|
||||
final efRateTypeProvider =
|
||||
StateProvider<ExchangeRateType>((ref) => ExchangeRateType.estimated);
|
||||
|
||||
final efExchangeProvider =
|
||||
StateProvider<Exchange>((ref) => Exchange.defaultExchange);
|
||||
final efExchangeProviderNameProvider =
|
||||
StateProvider<String>((ref) => Exchange.defaultExchange.name);
|
||||
|
||||
final currentCombinedExchangeIdProvider = Provider<String>((ref) {
|
||||
return "${ref.watch(efExchangeProvider).name}"
|
||||
" (${ref.watch(efExchangeProviderNameProvider)})";
|
||||
});
|
||||
|
||||
final efSendAmountProvider = StateProvider<Decimal?>((ref) => null);
|
||||
final efReceiveAmountProvider = StateProvider<Decimal?>((ref) => null);
|
||||
|
||||
final efSendAmountStringProvider = StateProvider<String>((ref) {
|
||||
final refreshing = ref.watch(efRefreshingProvider);
|
||||
final reversed = ref.watch(efReversedProvider);
|
||||
if (refreshing && reversed) {
|
||||
return "-";
|
||||
} else {
|
||||
return ref.watch(efSendAmountProvider)?.toString() ?? "";
|
||||
}
|
||||
});
|
||||
final efReceiveAmountStringProvider = StateProvider<String>((ref) {
|
||||
final refreshing = ref.watch(efRefreshingProvider);
|
||||
final reversed = ref.watch(efReversedProvider);
|
||||
|
||||
if (refreshing && reversed == false) {
|
||||
return "-";
|
||||
} else {
|
||||
return ref.watch(efReceiveAmountProvider)?.toString() ?? "";
|
||||
}
|
||||
});
|
||||
|
||||
final efReversedProvider = StateProvider<bool>((ref) => false);
|
||||
|
||||
final efCurrencyPairProvider = ChangeNotifierProvider<ActivePair>(
|
||||
(ref) => ActivePair(),
|
||||
);
|
||||
|
||||
final efEstimateProvider = StateProvider<Estimate?>((ref) {
|
||||
final exchange = ref.watch(efExchangeProvider);
|
||||
final provider = ref.watch(efExchangeProviderNameProvider);
|
||||
final reversed = ref.watch(efReversedProvider);
|
||||
final fixedRate = ref.watch(efRateTypeProvider) == ExchangeRateType.fixed;
|
||||
|
||||
final matches = ref
|
||||
.watch(efEstimatesListProvider(exchange.name))
|
||||
?.item1
|
||||
.value
|
||||
?.where((e) {
|
||||
return e.exchangeProvider == provider &&
|
||||
e.fixedRate == fixedRate &&
|
||||
e.reversed == reversed;
|
||||
});
|
||||
|
||||
Estimate? result;
|
||||
|
||||
if (matches != null && matches.isNotEmpty) {
|
||||
result = matches.first;
|
||||
} else {
|
||||
result = null;
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
final efCanExchangeProvider = StateProvider<bool>((ref) {
|
||||
final Estimate? estimate = ref.watch(efEstimateProvider);
|
||||
final refreshing = ref.watch(efRefreshingProvider);
|
||||
|
||||
return !refreshing && estimate != null;
|
||||
});
|
||||
|
||||
final efRefreshingProvider = StateProvider<bool>((ref) => false);
|
||||
|
|
|
@ -63,6 +63,7 @@ import 'package:stackwallet/pages/send_view/token_send_view.dart';
|
|||
import 'package:stackwallet/pages/settings_views/global_settings_view/about_view.dart';
|
||||
import 'package:stackwallet/pages/settings_views/global_settings_view/advanced_views/advanced_settings_view.dart';
|
||||
import 'package:stackwallet/pages/settings_views/global_settings_view/advanced_views/debug_view.dart';
|
||||
import 'package:stackwallet/pages/settings_views/global_settings_view/advanced_views/manage_explorer_view.dart';
|
||||
import 'package:stackwallet/pages/settings_views/global_settings_view/appearance_settings/appearance_settings_view.dart';
|
||||
import 'package:stackwallet/pages/settings_views/global_settings_view/appearance_settings/system_brightness_theme_selection_view.dart';
|
||||
import 'package:stackwallet/pages/settings_views/global_settings_view/currency_view.dart';
|
||||
|
@ -99,6 +100,7 @@ import 'package:stackwallet/pages/settings_views/wallet_settings_view/wallet_set
|
|||
import 'package:stackwallet/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/rename_wallet_view.dart';
|
||||
import 'package:stackwallet/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/wallet_settings_wallet_settings_view.dart';
|
||||
import 'package:stackwallet/pages/stack_privacy_calls.dart';
|
||||
import 'package:stackwallet/widgets/choose_coin_view.dart';
|
||||
import 'package:stackwallet/pages/token_view/my_tokens_view.dart';
|
||||
import 'package:stackwallet/pages/token_view/token_contract_details_view.dart';
|
||||
import 'package:stackwallet/pages/token_view/token_view.dart';
|
||||
|
@ -206,6 +208,36 @@ class RouteGenerator {
|
|||
builder: (_) => const StackPrivacyCalls(isSettings: false),
|
||||
settings: RouteSettings(name: settings.name));
|
||||
|
||||
case ChooseCoinView.routeName:
|
||||
if (args is Tuple3<String, String, String>) {
|
||||
return getRoute(
|
||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
||||
builder: (_) => ChooseCoinView(
|
||||
title: args.item1,
|
||||
coinAdditional: args.item2,
|
||||
nextRouteName: args.item3,
|
||||
),
|
||||
settings: RouteSettings(
|
||||
name: settings.name,
|
||||
),
|
||||
);
|
||||
}
|
||||
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
||||
|
||||
case ManageExplorerView.routeName:
|
||||
if (args is Coin) {
|
||||
return getRoute(
|
||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
||||
builder: (_) => ManageExplorerView(
|
||||
coin: args,
|
||||
),
|
||||
settings: RouteSettings(
|
||||
name: settings.name,
|
||||
),
|
||||
);
|
||||
}
|
||||
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
||||
|
||||
case WalletsView.routeName:
|
||||
return getRoute(
|
||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
||||
|
|
|
@ -1958,7 +1958,8 @@ class BitcoinCashWallet extends CoinServiceAPI
|
|||
|
||||
if (storedTx == null ||
|
||||
storedTx.address.value == null ||
|
||||
storedTx.height == null
|
||||
storedTx.height == null ||
|
||||
(storedTx.height != null && storedTx.height! <= 0)
|
||||
// zero conf messes this up
|
||||
// !storedTx.isConfirmed(currentHeight, MINIMUM_CONFIRMATIONS)
|
||||
) {
|
||||
|
|
|
@ -452,8 +452,6 @@ class EpicCashWallet extends CoinServiceAPI
|
|||
|
||||
EpicBoxConfigModel epicboxConfig = await getEpicBoxConfig();
|
||||
|
||||
print("EPICBOX CONFIG HERE IS $epicboxConfig");
|
||||
|
||||
// TODO determine whether it is worth sending change to a change address.
|
||||
dynamic message;
|
||||
|
||||
|
@ -522,6 +520,11 @@ class EpicCashWallet extends CoinServiceAPI
|
|||
throw BadEpicHttpAddressException(message: sendTx);
|
||||
}
|
||||
|
||||
Map<String, String> txAddressInfo = {};
|
||||
txAddressInfo['from'] = await currentReceivingAddress;
|
||||
txAddressInfo['to'] = txData['addresss'] as String;
|
||||
await putSendToAddresses(sendTx, txAddressInfo);
|
||||
|
||||
Logging.instance.log("CONFIRM_RESULT_IS $sendTx", level: LogLevel.Info);
|
||||
|
||||
final decodeData = json.decode(sendTx);
|
||||
|
@ -1247,7 +1250,6 @@ class EpicCashWallet extends CoinServiceAPI
|
|||
await _secureStore.write(key: '${_walletId}_config', value: stringConfig);
|
||||
await _secureStore.write(key: '${_walletId}_password', value: password);
|
||||
|
||||
print("EPIC BOX MODEL IS ${epicboxConfig.toString()}");
|
||||
await _secureStore.write(
|
||||
key: '${_walletId}_epicboxConfig', value: epicboxConfig.toString());
|
||||
|
||||
|
@ -1390,7 +1392,8 @@ class EpicCashWallet extends CoinServiceAPI
|
|||
}
|
||||
}
|
||||
|
||||
Future<bool> putSendToAddresses(String slateMessage) async {
|
||||
Future<bool> putSendToAddresses(
|
||||
String slateMessage, Map<String, String> txAddressInfo) async {
|
||||
try {
|
||||
var slatesToCommits = await getSlatesToCommits();
|
||||
final slate0 = jsonDecode(slateMessage);
|
||||
|
@ -1400,19 +1403,19 @@ class EpicCashWallet extends CoinServiceAPI
|
|||
final slateId = part1[0]['tx_slate_id'];
|
||||
final commitId = part2['tx']['body']['outputs'][0]['commit'];
|
||||
|
||||
final toFromInfoString = jsonDecode(slateMessage);
|
||||
final toFromInfo = jsonDecode(toFromInfoString[1] as String);
|
||||
final from = toFromInfo['from'];
|
||||
final to = toFromInfo['to'];
|
||||
final from = txAddressInfo['from'];
|
||||
final to = txAddressInfo['to'];
|
||||
slatesToCommits[slateId] = {
|
||||
"commitId": commitId,
|
||||
"from": from,
|
||||
"to": to,
|
||||
};
|
||||
|
||||
await epicUpdateSlatesToCommits(slatesToCommits);
|
||||
return true;
|
||||
} catch (e, s) {
|
||||
Logging.instance.log("$e $s", level: LogLevel.Error);
|
||||
Logging.instance
|
||||
.log("ERROR STORING ADDRESS $e $s", level: LogLevel.Error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,389 +0,0 @@
|
|||
// import 'package:decimal/decimal.dart';
|
||||
// import 'package:flutter/foundation.dart';
|
||||
// import 'package:stackwallet/models/exchange/response_objects/currency.dart';
|
||||
// import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart';
|
||||
// import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart';
|
||||
// import 'package:stackwallet/services/exchange/exchange.dart';
|
||||
// import 'package:stackwallet/utilities/logger.dart';
|
||||
//
|
||||
// class ExchangeFormState extends ChangeNotifier {
|
||||
// ExchangeFormState(this.exchangeRateType);
|
||||
// final ExchangeRateType exchangeRateType;
|
||||
//
|
||||
// Exchange? _exchange;
|
||||
// Exchange get exchange =>
|
||||
// _exchange ??= ChangeNowExchange(); // default to change now
|
||||
// set exchange(Exchange value) {
|
||||
// _exchange = value;
|
||||
// _updateRangesAndEstimate(
|
||||
// shouldNotifyListeners: true,
|
||||
// );
|
||||
// }
|
||||
//
|
||||
// bool _reversed = false;
|
||||
// bool get reversed => _reversed;
|
||||
// // set reversed(bool reversed) {
|
||||
// // _reversed = reversed;
|
||||
// // //
|
||||
// // }
|
||||
//
|
||||
// Decimal? _rate;
|
||||
// Decimal? get rate => _rate;
|
||||
// // set rate(Decimal? rate) {
|
||||
// // _rate = rate;
|
||||
// // //
|
||||
// // }
|
||||
//
|
||||
// Decimal? _sendAmount;
|
||||
// Decimal? get sendAmount => _sendAmount;
|
||||
// // set sendAmount(Decimal? sendAmount) {
|
||||
// // _sendAmount = sendAmount;
|
||||
// // //
|
||||
// // }
|
||||
//
|
||||
// Decimal? _receiveAmount;
|
||||
// Decimal? get receiveAmount => _receiveAmount;
|
||||
// // set receiveAmount(Decimal? receiveAmount) {
|
||||
// // _receiveAmount = receiveAmount;
|
||||
// // //
|
||||
// // }
|
||||
//
|
||||
// Currency? _sendCurrency;
|
||||
// Currency? get sendCurrency => _sendCurrency;
|
||||
// // set sendCurrency(Currency? sendCurrency) {
|
||||
// // _sendCurrency = sendCurrency;
|
||||
// // //
|
||||
// // }
|
||||
//
|
||||
// Currency? _receiveCurrency;
|
||||
// Currency? get receiveCurrency => _receiveCurrency;
|
||||
// // set receiveCurrency(Currency? receiveCurrency) {
|
||||
// // _receiveCurrency = receiveCurrency;
|
||||
// // //
|
||||
// // }
|
||||
//
|
||||
// Decimal? _minSendAmount;
|
||||
// Decimal? get minSendAmount => _minSendAmount;
|
||||
// // set minSendAmount(Decimal? minSendAmount) {
|
||||
// // _minSendAmount = minSendAmount;
|
||||
// // //
|
||||
// // }
|
||||
//
|
||||
// Decimal? _minReceiveAmount;
|
||||
// Decimal? get minReceiveAmount => _minReceiveAmount;
|
||||
// // set minReceiveAmount(Decimal? minReceiveAmount) {
|
||||
// // _minReceiveAmount = minReceiveAmount;
|
||||
// // //
|
||||
// // }
|
||||
//
|
||||
// Decimal? _maxSendAmount;
|
||||
// Decimal? get maxSendAmount => _maxSendAmount;
|
||||
// // set maxSendAmount(Decimal? maxSendAmount) {
|
||||
// // _maxSendAmount = maxSendAmount;
|
||||
// // //
|
||||
// // }
|
||||
//
|
||||
// Decimal? _maxReceiveAmount;
|
||||
// Decimal? get maxReceiveAmount => _maxReceiveAmount;
|
||||
// // set maxReceiveAmount(Decimal? maxReceiveAmount) {
|
||||
// // _maxReceiveAmount = maxReceiveAmount;
|
||||
// // //
|
||||
// // }
|
||||
//
|
||||
// //============================================================================
|
||||
// // computed properties
|
||||
// //============================================================================
|
||||
//
|
||||
// String? get fromTicker => _sendCurrency?.ticker;
|
||||
//
|
||||
// String? get toTicker => _receiveCurrency?.ticker;
|
||||
//
|
||||
// String get warning {
|
||||
// if (reversed) {
|
||||
// if (_receiveCurrency != null && _receiveAmount != null) {
|
||||
// if (_minReceiveAmount != null &&
|
||||
// _receiveAmount! < _minReceiveAmount! &&
|
||||
// _receiveAmount! > Decimal.zero) {
|
||||
// return "Minimum amount ${_minReceiveAmount!.toString()} ${_receiveCurrency!.ticker.toUpperCase()}";
|
||||
// } else if (_maxReceiveAmount != null &&
|
||||
// _receiveAmount! > _maxReceiveAmount!) {
|
||||
// return "Maximum amount ${_maxReceiveAmount!.toString()} ${_receiveCurrency!.ticker.toUpperCase()}";
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
// if (_sendCurrency != null && _sendAmount != null) {
|
||||
// if (_minSendAmount != null &&
|
||||
// _sendAmount! < _minSendAmount! &&
|
||||
// _sendAmount! > Decimal.zero) {
|
||||
// return "Minimum amount ${_minSendAmount!.toString()} ${_sendCurrency!.ticker.toUpperCase()}";
|
||||
// } else if (_maxSendAmount != null && _sendAmount! > _maxSendAmount!) {
|
||||
// return "Maximum amount ${_maxSendAmount!.toString()} ${_sendCurrency!.ticker.toUpperCase()}";
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// return "";
|
||||
// }
|
||||
//
|
||||
// //============================================================================
|
||||
// // public state updaters
|
||||
// //============================================================================
|
||||
//
|
||||
// void reset(bool shouldNotifyListeners) {
|
||||
// _exchange = null;
|
||||
// _reversed = false;
|
||||
// _rate = null;
|
||||
// _sendAmount = null;
|
||||
// _receiveAmount = null;
|
||||
// _sendCurrency = null;
|
||||
// _receiveCurrency = null;
|
||||
// _minSendAmount = null;
|
||||
// _minReceiveAmount = null;
|
||||
// _maxSendAmount = null;
|
||||
// _maxReceiveAmount = null;
|
||||
//
|
||||
// if (shouldNotifyListeners) {
|
||||
// notifyListeners();
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Future<void> setFromAmountAndCalculateToAmount(
|
||||
// Decimal? newSendAmount,
|
||||
// bool shouldNotifyListeners,
|
||||
// ) async {
|
||||
// if (newSendAmount == null) {
|
||||
// // todo: check if this breaks things and stuff
|
||||
// _receiveAmount = null;
|
||||
// _sendAmount = null;
|
||||
// } else {
|
||||
// if (newSendAmount <= Decimal.zero) {
|
||||
// _receiveAmount = Decimal.zero;
|
||||
// }
|
||||
//
|
||||
// _sendAmount = newSendAmount;
|
||||
// _reversed = false;
|
||||
//
|
||||
// await _updateRangesAndEstimate(
|
||||
// shouldNotifyListeners: false,
|
||||
// );
|
||||
// }
|
||||
//
|
||||
// if (shouldNotifyListeners) {
|
||||
// notifyListeners();
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Future<void> setToAmountAndCalculateFromAmount(
|
||||
// Decimal? newReceiveAmount,
|
||||
// bool shouldNotifyListeners,
|
||||
// ) async {
|
||||
// if (newReceiveAmount == null) {
|
||||
// // todo: check if this breaks things and stuff
|
||||
// _receiveAmount = null;
|
||||
// _sendAmount = null;
|
||||
// } else {
|
||||
// if (newReceiveAmount <= Decimal.zero) {
|
||||
// _sendAmount = Decimal.zero;
|
||||
// }
|
||||
//
|
||||
// _receiveAmount = newReceiveAmount;
|
||||
// _reversed = true;
|
||||
//
|
||||
// await _updateRangesAndEstimate(
|
||||
// shouldNotifyListeners: false,
|
||||
// );
|
||||
// }
|
||||
//
|
||||
// if (shouldNotifyListeners) {
|
||||
// notifyListeners();
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Future<void> updateFrom(
|
||||
// Currency sendCurrency,
|
||||
// bool shouldNotifyListeners,
|
||||
// ) async {
|
||||
// try {
|
||||
// _sendCurrency = sendCurrency;
|
||||
// if (_receiveCurrency == null) {
|
||||
// _rate = null;
|
||||
// } else {
|
||||
// await _updateRangesAndEstimate(
|
||||
// shouldNotifyListeners: false,
|
||||
// );
|
||||
// }
|
||||
// } catch (e, s) {
|
||||
// Logging.instance.log("$e\n$s", level: LogLevel.Error);
|
||||
// }
|
||||
// if (shouldNotifyListeners) {
|
||||
// notifyListeners();
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Future<void> updateTo(
|
||||
// Currency receiveCurrency,
|
||||
// bool shouldNotifyListeners,
|
||||
// ) async {
|
||||
// try {
|
||||
// _receiveCurrency = receiveCurrency;
|
||||
//
|
||||
// if (_sendCurrency == null) {
|
||||
// _rate = null;
|
||||
// } else {
|
||||
// await _updateRangesAndEstimate(
|
||||
// shouldNotifyListeners: false,
|
||||
// );
|
||||
// }
|
||||
// } catch (e, s) {
|
||||
// Logging.instance.log("$e\n$s", level: LogLevel.Error);
|
||||
// }
|
||||
// if (shouldNotifyListeners) {
|
||||
// notifyListeners();
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Future<void> swap(
|
||||
// {required bool shouldNotifyListeners,}) async {
|
||||
// final Decimal? temp = sendAmount;
|
||||
// _sendAmount = receiveAmount;
|
||||
// _receiveAmount = temp;
|
||||
//
|
||||
// _minSendAmount = null;
|
||||
// _maxSendAmount = null;
|
||||
// _minReceiveAmount = null;
|
||||
// _maxReceiveAmount = null;
|
||||
//
|
||||
// final Currency? tmp = sendCurrency;
|
||||
// _sendCurrency = receiveCurrency;
|
||||
// _receiveCurrency = tmp;
|
||||
//
|
||||
// await _updateRangesAndEstimate(
|
||||
// shouldNotifyListeners: false,
|
||||
// );
|
||||
// }
|
||||
//
|
||||
// //============================================================================
|
||||
// // private state updaters
|
||||
// //============================================================================
|
||||
//
|
||||
// Future<void> _updateRangesAndEstimate(
|
||||
// {required bool shouldNotifyListeners,}) async {
|
||||
// await _updateRanges(shouldNotifyListeners: false);
|
||||
// await _updateEstimate(shouldNotifyListeners: false);
|
||||
// if (shouldNotifyListeners) {
|
||||
// notifyListeners();
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Future<void> _updateRanges({required bool shouldNotifyListeners,}) async {
|
||||
// // if (exchange?.name == SimpleSwapExchange.exchangeName) {
|
||||
// // reversed = false;
|
||||
// // }
|
||||
// final _send = sendCurrency;
|
||||
// final _receive = receiveCurrency;
|
||||
// if (_send == null || _receive == null) {
|
||||
// Logging.instance.log(
|
||||
// "Tried to $runtimeType.updateRanges where ( $_send || $_receive) for: $exchange",
|
||||
// level: LogLevel.Info,
|
||||
// );
|
||||
// return;
|
||||
// }
|
||||
// final response = await exchange.getRange(
|
||||
// _send.ticker,
|
||||
// _receive.ticker,
|
||||
// exchangeRateType == ExchangeRateType.fixed,
|
||||
// );
|
||||
//
|
||||
// if (response.value == null) {
|
||||
// Logging.instance.log(
|
||||
// "Tried to $runtimeType.updateRanges for: $exchange where response: $response",
|
||||
// level: LogLevel.Info,
|
||||
// );
|
||||
// return;
|
||||
// }
|
||||
// final responseReversed = await exchange.getRange(
|
||||
// _receive.ticker,
|
||||
// _send.ticker,
|
||||
// exchangeRateType == ExchangeRateType.fixed,
|
||||
// );
|
||||
//
|
||||
// if (responseReversed.value == null) {
|
||||
// Logging.instance.log(
|
||||
// "Tried to $runtimeType.updateRanges for: $exchange where response: $responseReversed",
|
||||
// level: LogLevel.Info,
|
||||
// );
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// final range = response.value!;
|
||||
// final rangeReversed = responseReversed.value!;
|
||||
//
|
||||
// _minSendAmount = range.min;
|
||||
// _maxSendAmount = range.max;
|
||||
// _minReceiveAmount = rangeReversed.min;
|
||||
// _maxReceiveAmount = rangeReversed.max;
|
||||
//
|
||||
// //todo: check if print needed
|
||||
// // debugPrint(
|
||||
// // "updated range for: $exchange for $_fromTicker-$_toTicker: $range");
|
||||
//
|
||||
// if (shouldNotifyListeners) {
|
||||
// notifyListeners();
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Future<void> _updateEstimate({
|
||||
// required bool shouldNotifyListeners,
|
||||
// }) async {
|
||||
// // if (exchange?.name == SimpleSwapExchange.exchangeName) {
|
||||
// // reversed = false;
|
||||
// // }
|
||||
// final amount = reversed ? receiveAmount : sendAmount;
|
||||
// if (sendCurrency == null ||
|
||||
// receiveCurrency == null ||
|
||||
// amount == null ||
|
||||
// amount <= Decimal.zero) {
|
||||
// Logging.instance.log(
|
||||
// "Tried to $runtimeType.updateEstimate for: $exchange where (from: $sendCurrency || to: $receiveCurrency || amount: $amount)",
|
||||
// level: LogLevel.Info,
|
||||
// );
|
||||
// return;
|
||||
// }
|
||||
// final response = await exchange.getEstimate(
|
||||
// sendCurrency!.ticker,
|
||||
// receiveCurrency!.ticker,
|
||||
// amount,
|
||||
// exchangeRateType == ExchangeRateType.fixed,
|
||||
// reversed,
|
||||
// );
|
||||
//
|
||||
// if (response.value == null) {
|
||||
// Logging.instance.log(
|
||||
// "Tried to $runtimeType.updateEstimate for: $exchange where response: $response",
|
||||
// level: LogLevel.Info,
|
||||
// );
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// final estimate = response.value!;
|
||||
//
|
||||
// if (reversed) {
|
||||
// _sendAmount = estimate.estimatedAmount;
|
||||
// } else {
|
||||
// _receiveAmount = estimate.estimatedAmount;
|
||||
// }
|
||||
//
|
||||
// _rate =
|
||||
// (receiveAmount! / sendAmount!).toDecimal(scaleOnInfinitePrecision: 12);
|
||||
//
|
||||
// //todo: check if print needed
|
||||
// // debugPrint(
|
||||
// // "updated estimate for: $exchange for $fromTicker-$toTicker: $estimate");
|
||||
//
|
||||
// if (shouldNotifyListeners) {
|
||||
// notifyListeners();
|
||||
// }
|
||||
// }
|
||||
//
|
||||
//
|
||||
// }
|
|
@ -100,12 +100,18 @@ class ChangeNowAPI {
|
|||
body: jsonEncode(body),
|
||||
);
|
||||
|
||||
final parsed = jsonDecode(response.body);
|
||||
try {
|
||||
final parsed = jsonDecode(response.body);
|
||||
|
||||
return parsed;
|
||||
return parsed;
|
||||
} catch (_) {
|
||||
Logging.instance.log("ChangeNOW api failed to parse: ${response.body}",
|
||||
level: LogLevel.Error);
|
||||
rethrow;
|
||||
}
|
||||
} catch (e, s) {
|
||||
Logging.instance
|
||||
.log("_makeRequest($uri) threw: $e\n$s", level: LogLevel.Error);
|
||||
.log("_makePostRequest($uri) threw: $e\n$s", level: LogLevel.Error);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
@ -483,6 +489,7 @@ class ChangeNowAPI {
|
|||
reversed: false,
|
||||
rateId: value.rateId,
|
||||
warningMessage: value.warningMessage,
|
||||
exchangeProvider: ChangeNowExchange.exchangeName,
|
||||
),
|
||||
);
|
||||
} catch (_) {
|
||||
|
@ -566,6 +573,7 @@ class ChangeNowAPI {
|
|||
reversed: reversed,
|
||||
rateId: value.rateId,
|
||||
warningMessage: value.warningMessage,
|
||||
exchangeProvider: ChangeNowExchange.exchangeName,
|
||||
),
|
||||
);
|
||||
} catch (_) {
|
||||
|
|
|
@ -31,7 +31,7 @@ class ChangeNowExchange extends Exchange {
|
|||
String? extraId,
|
||||
required String addressRefund,
|
||||
required String refundExtraId,
|
||||
String? rateId,
|
||||
Estimate? estimate,
|
||||
required bool reversed,
|
||||
}) async {
|
||||
late final ExchangeResponse<ExchangeTransaction> response;
|
||||
|
@ -41,7 +41,7 @@ class ChangeNowExchange extends Exchange {
|
|||
toTicker: to,
|
||||
receivingAddress: addressTo,
|
||||
amount: amount,
|
||||
rateId: rateId!,
|
||||
rateId: estimate!.rateId!,
|
||||
extraId: extraId ?? "",
|
||||
refundAddress: addressRefund,
|
||||
refundExtraId: refundExtraId,
|
||||
|
@ -128,7 +128,7 @@ class ChangeNowExchange extends Exchange {
|
|||
}
|
||||
|
||||
@override
|
||||
Future<ExchangeResponse<Estimate>> getEstimate(
|
||||
Future<ExchangeResponse<List<Estimate>>> getEstimates(
|
||||
String from,
|
||||
String to,
|
||||
Decimal amount,
|
||||
|
@ -151,7 +151,10 @@ class ChangeNowExchange extends Exchange {
|
|||
fromAmount: amount,
|
||||
);
|
||||
}
|
||||
return response;
|
||||
return ExchangeResponse(
|
||||
value: response.value == null ? null : [response.value!],
|
||||
exception: response.exception,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -8,6 +8,7 @@ import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dar
|
|||
import 'package:stackwallet/services/exchange/exchange_response.dart';
|
||||
import 'package:stackwallet/services/exchange/majestic_bank/majestic_bank_exchange.dart';
|
||||
import 'package:stackwallet/services/exchange/simpleswap/simpleswap_exchange.dart';
|
||||
import 'package:stackwallet/services/exchange/trocador/trocador_exchange.dart';
|
||||
|
||||
abstract class Exchange {
|
||||
static Exchange get defaultExchange => ChangeNowExchange.instance;
|
||||
|
@ -20,7 +21,14 @@ abstract class Exchange {
|
|||
return SimpleSwapExchange.instance;
|
||||
case MajesticBankExchange.exchangeName:
|
||||
return MajesticBankExchange.instance;
|
||||
case TrocadorExchange.exchangeName:
|
||||
return TrocadorExchange.instance;
|
||||
default:
|
||||
final split = name.split(" ");
|
||||
if (split.length >= 2) {
|
||||
// silly way to check for 'Trocador ($providerName)'
|
||||
return fromName(split.first);
|
||||
}
|
||||
throw ArgumentError("Unknown exchange name");
|
||||
}
|
||||
}
|
||||
|
@ -52,7 +60,7 @@ abstract class Exchange {
|
|||
bool fixedRate,
|
||||
);
|
||||
|
||||
Future<ExchangeResponse<Estimate>> getEstimate(
|
||||
Future<ExchangeResponse<List<Estimate>>> getEstimates(
|
||||
String from,
|
||||
String to,
|
||||
Decimal amount,
|
||||
|
@ -69,7 +77,7 @@ abstract class Exchange {
|
|||
String? extraId,
|
||||
required String addressRefund,
|
||||
required String refundExtraId,
|
||||
String? rateId,
|
||||
Estimate? estimate,
|
||||
required bool reversed,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:stackwallet/db/hive/db.dart';
|
||||
import 'package:stackwallet/models/exchange/active_pair.dart';
|
||||
import 'package:stackwallet/models/exchange/aggregate_currency.dart';
|
||||
import 'package:stackwallet/models/exchange/exchange_form_state.dart';
|
||||
import 'package:stackwallet/models/isar/exchange_cache/currency.dart';
|
||||
import 'package:stackwallet/models/isar/exchange_cache/pair.dart';
|
||||
import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart';
|
||||
import 'package:stackwallet/services/exchange/majestic_bank/majestic_bank_exchange.dart';
|
||||
import 'package:stackwallet/services/exchange/trocador/trocador_exchange.dart';
|
||||
import 'package:stackwallet/utilities/enums/exchange_rate_type_enum.dart';
|
||||
import 'package:stackwallet/utilities/logger.dart';
|
||||
import 'package:stackwallet/utilities/stack_file_system.dart';
|
||||
|
@ -56,20 +57,29 @@ class ExchangeDataLoadingService {
|
|||
);
|
||||
}
|
||||
|
||||
Future<void> setCurrenciesIfEmpty(ExchangeFormState state) async {
|
||||
if (state.sendCurrency == null && state.receiveCurrency == null) {
|
||||
Future<void> setCurrenciesIfEmpty(
|
||||
ActivePair? pair,
|
||||
ExchangeRateType rateType,
|
||||
) async {
|
||||
if (pair?.send == null && pair?.receive == null) {
|
||||
if (await isar.currencies.count() > 0) {
|
||||
final sendCurrency = await getAggregateCurrency(
|
||||
"BTC",
|
||||
state.exchangeRateType,
|
||||
null,
|
||||
pair?.setSend(
|
||||
await getAggregateCurrency(
|
||||
"BTC",
|
||||
rateType,
|
||||
null,
|
||||
),
|
||||
notifyListeners: false,
|
||||
);
|
||||
final receiveCurrency = await getAggregateCurrency(
|
||||
"XMR",
|
||||
state.exchangeRateType,
|
||||
null,
|
||||
|
||||
pair?.setReceive(
|
||||
await getAggregateCurrency(
|
||||
"XMR",
|
||||
rateType,
|
||||
null,
|
||||
),
|
||||
notifyListeners: false,
|
||||
);
|
||||
state.setCurrencies(sendCurrency, receiveCurrency);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -128,6 +138,7 @@ class ExchangeDataLoadingService {
|
|||
// loadSimpleswapFixedRateCurrencies(ref),
|
||||
// loadSimpleswapFloatingRateCurrencies(ref),
|
||||
loadMajesticBankCurrencies(),
|
||||
loadTrocadorCurrencies(),
|
||||
]);
|
||||
|
||||
// quicker to load available currencies on the fly for a specific base currency
|
||||
|
@ -302,6 +313,28 @@ class ExchangeDataLoadingService {
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> loadTrocadorCurrencies() async {
|
||||
final exchange = TrocadorExchange.instance;
|
||||
final responseCurrencies = await exchange.getAllCurrencies(false);
|
||||
|
||||
if (responseCurrencies.value != null) {
|
||||
await isar.writeTxn(() async {
|
||||
final idsToDelete = await isar.currencies
|
||||
.where()
|
||||
.exchangeNameEqualTo(TrocadorExchange.exchangeName)
|
||||
.idProperty()
|
||||
.findAll();
|
||||
await isar.currencies.deleteAll(idsToDelete);
|
||||
await isar.currencies.putAll(responseCurrencies.value!);
|
||||
});
|
||||
} else {
|
||||
Logging.instance.log(
|
||||
"loadTrocadorCurrencies: $responseCurrencies",
|
||||
level: LogLevel.Warning,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Future<void> loadMajesticBankPairs() async {
|
||||
// final exchange = MajesticBankExchange.instance;
|
||||
//
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
class ExchangeService {}
|
|
@ -44,7 +44,7 @@ class MajesticBankExchange extends Exchange {
|
|||
String? extraId,
|
||||
required String addressRefund,
|
||||
required String refundExtraId,
|
||||
String? rateId,
|
||||
Estimate? estimate,
|
||||
required bool reversed,
|
||||
}) async {
|
||||
ExchangeResponse<MBOrder>? response;
|
||||
|
@ -170,7 +170,7 @@ class MajesticBankExchange extends Exchange {
|
|||
}
|
||||
|
||||
@override
|
||||
Future<ExchangeResponse<Estimate>> getEstimate(
|
||||
Future<ExchangeResponse<List<Estimate>>> getEstimates(
|
||||
String from,
|
||||
String to,
|
||||
Decimal amount,
|
||||
|
@ -192,8 +192,9 @@ class MajesticBankExchange extends Exchange {
|
|||
estimatedAmount: reversed ? calc.fromAmount : calc.receiveAmount,
|
||||
fixedRate: fixedRate,
|
||||
reversed: reversed,
|
||||
exchangeProvider: MajesticBankExchange.exchangeName,
|
||||
);
|
||||
return ExchangeResponse(value: estimate);
|
||||
return ExchangeResponse(value: [estimate]);
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -30,7 +30,7 @@ class SimpleSwapExchange extends Exchange {
|
|||
String? extraId,
|
||||
required String addressRefund,
|
||||
required String refundExtraId,
|
||||
String? rateId,
|
||||
Estimate? estimate,
|
||||
required bool reversed,
|
||||
}) async {
|
||||
return await SimpleSwapAPI.instance.createNewExchange(
|
||||
|
@ -89,7 +89,7 @@ class SimpleSwapExchange extends Exchange {
|
|||
}
|
||||
|
||||
@override
|
||||
Future<ExchangeResponse<Estimate>> getEstimate(
|
||||
Future<ExchangeResponse<List<Estimate>>> getEstimates(
|
||||
String from,
|
||||
String to,
|
||||
Decimal amount,
|
||||
|
@ -109,11 +109,14 @@ class SimpleSwapExchange extends Exchange {
|
|||
}
|
||||
|
||||
return ExchangeResponse(
|
||||
value: Estimate(
|
||||
estimatedAmount: Decimal.parse(response.value!),
|
||||
fixedRate: fixedRate,
|
||||
reversed: reversed,
|
||||
),
|
||||
value: [
|
||||
Estimate(
|
||||
estimatedAmount: Decimal.parse(response.value!),
|
||||
fixedRate: fixedRate,
|
||||
reversed: reversed,
|
||||
exchangeProvider: SimpleSwapExchange.exchangeName,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
import 'package:decimal/decimal.dart';
|
||||
|
||||
class TrocadorCoin {
|
||||
final String name;
|
||||
final String ticker;
|
||||
final String network;
|
||||
final bool memo;
|
||||
final String image;
|
||||
final Decimal minimum;
|
||||
final Decimal maximum;
|
||||
|
||||
TrocadorCoin({
|
||||
required this.name,
|
||||
required this.ticker,
|
||||
required this.network,
|
||||
required this.memo,
|
||||
required this.image,
|
||||
required this.minimum,
|
||||
required this.maximum,
|
||||
});
|
||||
|
||||
factory TrocadorCoin.fromMap(Map<String, dynamic> json) => TrocadorCoin(
|
||||
name: json['name'] as String,
|
||||
ticker: json['ticker'] as String,
|
||||
network: json['network'] as String,
|
||||
memo: json['memo'] as bool,
|
||||
image: json['image'] as String,
|
||||
minimum: Decimal.parse(json['minimum'].toString()),
|
||||
maximum: Decimal.parse(json['maximum'].toString()),
|
||||
);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'TrocadorCoin( '
|
||||
'name: $name, '
|
||||
'ticker: $ticker, '
|
||||
'network: $network, '
|
||||
'memo: $memo, '
|
||||
'image: $image, '
|
||||
'minimum: $minimum, '
|
||||
'maximum: $maximum '
|
||||
')';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
import 'package:decimal/decimal.dart';
|
||||
|
||||
class TrocadorQuote {
|
||||
final String provider;
|
||||
final String kycRating;
|
||||
final int insurance;
|
||||
final bool fixed;
|
||||
final Decimal? amountTo;
|
||||
final Decimal? amountFrom;
|
||||
final Decimal waste;
|
||||
|
||||
TrocadorQuote({
|
||||
required this.provider,
|
||||
required this.kycRating,
|
||||
required this.insurance,
|
||||
required this.fixed,
|
||||
required this.amountTo,
|
||||
required this.amountFrom,
|
||||
required this.waste,
|
||||
});
|
||||
|
||||
factory TrocadorQuote.fromMap(Map<String, dynamic> map) {
|
||||
return TrocadorQuote(
|
||||
provider: map['provider'] as String,
|
||||
kycRating: map['kycrating'] as String,
|
||||
insurance: map['insurance'] as int,
|
||||
// wtf trocador?
|
||||
fixed: map['fixed'] == "True",
|
||||
amountTo: Decimal.tryParse(map['amount_to'].toString()),
|
||||
amountFrom: Decimal.tryParse(map['amount_from'].toString()),
|
||||
waste: Decimal.parse(map['waste'].toString()),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'TrocadorQuote( '
|
||||
'provider: $provider, '
|
||||
'kycRating: $kycRating, '
|
||||
'insurance: $insurance, '
|
||||
'fixed: $fixed, '
|
||||
'amountTo: $amountTo, '
|
||||
'amountFrom: $amountFrom, '
|
||||
'waste: $waste '
|
||||
')';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
import 'package:decimal/decimal.dart';
|
||||
import 'package:stackwallet/services/exchange/trocador/response_objects/trocador_quote.dart';
|
||||
|
||||
class TrocadorRate {
|
||||
final String tradeId;
|
||||
final DateTime date;
|
||||
final String tickerFrom;
|
||||
final String tickerTo;
|
||||
final String coinFrom;
|
||||
final String coinTo;
|
||||
final String networkFrom;
|
||||
final String networkTo;
|
||||
final Decimal amountFrom;
|
||||
final Decimal amountTo;
|
||||
final String provider;
|
||||
final bool fixed;
|
||||
final bool payment;
|
||||
final String status;
|
||||
final List<TrocadorQuote> quotes;
|
||||
|
||||
TrocadorRate({
|
||||
required this.tradeId,
|
||||
required this.date,
|
||||
required this.tickerFrom,
|
||||
required this.tickerTo,
|
||||
required this.coinFrom,
|
||||
required this.coinTo,
|
||||
required this.networkFrom,
|
||||
required this.networkTo,
|
||||
required this.amountFrom,
|
||||
required this.amountTo,
|
||||
required this.provider,
|
||||
required this.fixed,
|
||||
required this.payment,
|
||||
required this.status,
|
||||
required this.quotes,
|
||||
});
|
||||
|
||||
factory TrocadorRate.fromMap(Map<String, dynamic> map) {
|
||||
final list =
|
||||
List<Map<String, dynamic>>.from(map['quotes']['quotes'] as List);
|
||||
final quotes = list.map((quote) => TrocadorQuote.fromMap(quote)).toList();
|
||||
|
||||
return TrocadorRate(
|
||||
tradeId: map['trade_id'] as String,
|
||||
date: DateTime.parse(map['date'] as String),
|
||||
tickerFrom: map['ticker_from'] as String,
|
||||
tickerTo: map['ticker_to'] as String,
|
||||
coinFrom: map['coin_from'] as String,
|
||||
coinTo: map['coin_to'] as String,
|
||||
networkFrom: map['network_from'] as String,
|
||||
networkTo: map['network_to'] as String,
|
||||
amountFrom: Decimal.parse(map['amount_from'].toString()),
|
||||
amountTo: Decimal.parse(map['amount_to'].toString()),
|
||||
provider: map['provider'] as String,
|
||||
fixed: map['fixed'] as bool,
|
||||
payment: map['payment'] as bool,
|
||||
status: map['status'] as String,
|
||||
quotes: quotes,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
final quotesString = quotes.map((quote) => quote.toString()).join(', ');
|
||||
return 'TrocadorRate('
|
||||
'tradeId: $tradeId, '
|
||||
'date: $date, '
|
||||
'tickerFrom: $tickerFrom, '
|
||||
'tickerTo: $tickerTo, '
|
||||
'coinFrom: $coinFrom, '
|
||||
'coinTo: $coinTo, '
|
||||
'networkFrom: $networkFrom, '
|
||||
'networkTo: $networkTo, '
|
||||
'amountFrom: $amountFrom, '
|
||||
'amountTo: $amountTo, '
|
||||
'provider: $provider, '
|
||||
'fixed: $fixed, '
|
||||
'payment: $payment, '
|
||||
'status: $status, '
|
||||
'quotes: [$quotesString] '
|
||||
')';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
import 'package:decimal/decimal.dart';
|
||||
|
||||
class TrocadorTrade {
|
||||
final String tradeId;
|
||||
final DateTime date;
|
||||
final String tickerFrom;
|
||||
final String tickerTo;
|
||||
final String coinFrom;
|
||||
final String coinTo;
|
||||
final String networkFrom;
|
||||
final String networkTo;
|
||||
final Decimal amountFrom;
|
||||
final Decimal amountTo;
|
||||
final String provider;
|
||||
final bool fixed;
|
||||
final String status;
|
||||
final String addressProvider;
|
||||
final String addressProviderMemo;
|
||||
final String addressUser;
|
||||
final String addressUserMemo;
|
||||
final String refundAddress;
|
||||
final String refundAddressMemo;
|
||||
final String password;
|
||||
final String idProvider;
|
||||
|
||||
// dynamic is the devil but this could be anything... because json.
|
||||
final dynamic quotes;
|
||||
|
||||
final bool payment;
|
||||
|
||||
TrocadorTrade({
|
||||
required this.tradeId,
|
||||
required this.date,
|
||||
required this.tickerFrom,
|
||||
required this.tickerTo,
|
||||
required this.coinFrom,
|
||||
required this.coinTo,
|
||||
required this.networkFrom,
|
||||
required this.networkTo,
|
||||
required this.amountFrom,
|
||||
required this.amountTo,
|
||||
required this.provider,
|
||||
required this.fixed,
|
||||
required this.status,
|
||||
required this.addressProvider,
|
||||
required this.addressProviderMemo,
|
||||
required this.addressUser,
|
||||
required this.addressUserMemo,
|
||||
required this.refundAddress,
|
||||
required this.refundAddressMemo,
|
||||
required this.password,
|
||||
required this.idProvider,
|
||||
required this.quotes,
|
||||
required this.payment,
|
||||
});
|
||||
|
||||
factory TrocadorTrade.fromMap(Map<String, dynamic> map) {
|
||||
return TrocadorTrade(
|
||||
tradeId: map['trade_id'] as String,
|
||||
date: DateTime.parse(map['date'] as String),
|
||||
tickerFrom: map['ticker_from'] as String,
|
||||
tickerTo: map['ticker_to'] as String,
|
||||
coinFrom: map['coin_from'] as String,
|
||||
coinTo: map['coin_to'] as String,
|
||||
networkFrom: map['network_from'] as String,
|
||||
networkTo: map['network_to'] as String,
|
||||
amountFrom: Decimal.parse(map['amount_from'].toString()),
|
||||
amountTo: Decimal.parse(map['amount_to'].toString()),
|
||||
provider: map['provider'] as String,
|
||||
fixed: map['fixed'] as bool,
|
||||
status: map['status'] as String,
|
||||
addressProvider: map['address_provider'] as String,
|
||||
addressProviderMemo: map['address_provider_memo'] as String,
|
||||
addressUser: map['address_user'] as String,
|
||||
addressUserMemo: map['address_user_memo'] as String,
|
||||
refundAddress: map['refund_address'] as String,
|
||||
refundAddressMemo: map['refund_address_memo'] as String,
|
||||
password: map['password'] as String,
|
||||
idProvider: map['id_provider'] as String,
|
||||
quotes: map['quotes'],
|
||||
payment: map['payment'] as bool,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'TrocadorTrade( '
|
||||
'tradeId: $tradeId, '
|
||||
'date: $date, '
|
||||
'tickerFrom: $tickerFrom, '
|
||||
'tickerTo: $tickerTo, '
|
||||
'coinFrom: $coinFrom, '
|
||||
'coinTo: $coinTo, '
|
||||
'networkFrom: $networkFrom, '
|
||||
'networkTo: $networkTo, '
|
||||
'amountFrom: $amountFrom, '
|
||||
'amountTo: $amountTo, '
|
||||
'provider: $provider, '
|
||||
'fixed: $fixed, '
|
||||
'status: $status, '
|
||||
'addressProvider: $addressProvider, '
|
||||
'addressProviderMemo: $addressProviderMemo, '
|
||||
'addressUser: $addressUser, '
|
||||
'addressUserMemo: $addressUserMemo, '
|
||||
'refundAddress: $refundAddress, '
|
||||
'refundAddressMemo: $refundAddressMemo, '
|
||||
'password: $password, '
|
||||
'idProvider: $idProvider, '
|
||||
'quotes: $quotes, '
|
||||
'payment: $payment '
|
||||
')';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
import 'package:decimal/decimal.dart';
|
||||
|
||||
class TrocadorTradeNew {
|
||||
final String tradeId;
|
||||
final DateTime date;
|
||||
final String tickerFrom;
|
||||
final String tickerTo;
|
||||
final String coinFrom;
|
||||
final String coinTo;
|
||||
final String networkFrom;
|
||||
final String networkTo;
|
||||
final Decimal amountFrom;
|
||||
final Decimal amountTo;
|
||||
final String provider;
|
||||
final bool fixed;
|
||||
final String status;
|
||||
final String addressProvider;
|
||||
final String addressProviderMemo;
|
||||
final String addressUser;
|
||||
final String addressUserMemo;
|
||||
final String refundAddress;
|
||||
final String refundAddressMemo;
|
||||
final String password;
|
||||
final String idProvider;
|
||||
final bool payment;
|
||||
|
||||
TrocadorTradeNew({
|
||||
required this.tradeId,
|
||||
required this.date,
|
||||
required this.tickerFrom,
|
||||
required this.tickerTo,
|
||||
required this.coinFrom,
|
||||
required this.coinTo,
|
||||
required this.networkFrom,
|
||||
required this.networkTo,
|
||||
required this.amountFrom,
|
||||
required this.amountTo,
|
||||
required this.provider,
|
||||
required this.fixed,
|
||||
required this.status,
|
||||
required this.addressProvider,
|
||||
required this.addressProviderMemo,
|
||||
required this.addressUser,
|
||||
required this.addressUserMemo,
|
||||
required this.refundAddress,
|
||||
required this.refundAddressMemo,
|
||||
required this.password,
|
||||
required this.idProvider,
|
||||
required this.payment,
|
||||
});
|
||||
|
||||
factory TrocadorTradeNew.fromMap(Map<String, dynamic> map) {
|
||||
return TrocadorTradeNew(
|
||||
tradeId: map['trade_id'] as String,
|
||||
date: DateTime.parse(map['date'] as String),
|
||||
tickerFrom: map['ticker_from'] as String,
|
||||
tickerTo: map['ticker_to'] as String,
|
||||
coinFrom: map['coin_from'] as String,
|
||||
coinTo: map['coin_to'] as String,
|
||||
networkFrom: map['network_from'] as String,
|
||||
networkTo: map['network_to'] as String,
|
||||
amountFrom: Decimal.parse(map['amount_from'].toString()),
|
||||
amountTo: Decimal.parse(map['amount_to'].toString()),
|
||||
provider: map['provider'] as String,
|
||||
fixed: map['fixed'] as bool,
|
||||
status: map['status'] as String,
|
||||
addressProvider: map['address_provider'] as String,
|
||||
addressProviderMemo: map['address_provider_memo'] as String,
|
||||
addressUser: map['address_user'] as String,
|
||||
addressUserMemo: map['address_user_memo'] as String,
|
||||
refundAddress: map['refund_address'] as String,
|
||||
refundAddressMemo: map['refund_address_memo'] as String,
|
||||
password: map['password'] as String,
|
||||
idProvider: map['id_provider'] as String,
|
||||
payment: map['payment'] as bool,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'TrocadorTradeNew( '
|
||||
'tradeId: $tradeId, '
|
||||
'date: $date, '
|
||||
'tickerFrom: $tickerFrom, '
|
||||
'tickerTo: $tickerTo, '
|
||||
'coinFrom: $coinFrom, '
|
||||
'coinTo: $coinTo, '
|
||||
'networkFrom: $networkFrom, '
|
||||
'networkTo: $networkTo, '
|
||||
'amountFrom: $amountFrom, '
|
||||
'amountTo: $amountTo, '
|
||||
'provider: $provider, '
|
||||
'fixed: $fixed, '
|
||||
'status: $status, '
|
||||
'addressProvider: $addressProvider, '
|
||||
'addressProviderMemo: $addressProviderMemo, '
|
||||
'addressUser: $addressUser, '
|
||||
'addressUserMemo: $addressUserMemo, '
|
||||
'refundAddress: $refundAddress, '
|
||||
'refundAddressMemo: $refundAddressMemo, '
|
||||
'password: $password, '
|
||||
'idProvider: $idProvider, '
|
||||
'payment: $payment '
|
||||
')';
|
||||
}
|
||||
}
|
339
lib/services/exchange/trocador/trocador_api.dart
Normal file
339
lib/services/exchange/trocador/trocador_api.dart
Normal file
|
@ -0,0 +1,339 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_native_splash/cli_commands.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:stackwallet/exceptions/exchange/exchange_exception.dart';
|
||||
import 'package:stackwallet/services/exchange/exchange_response.dart';
|
||||
import 'package:stackwallet/services/exchange/trocador/response_objects/trocador_coin.dart';
|
||||
import 'package:stackwallet/services/exchange/trocador/response_objects/trocador_rate.dart';
|
||||
import 'package:stackwallet/services/exchange/trocador/response_objects/trocador_trade.dart';
|
||||
import 'package:stackwallet/services/exchange/trocador/response_objects/trocador_trade_new.dart';
|
||||
import 'package:stackwallet/utilities/logger.dart';
|
||||
|
||||
const kTrocadorApiKey = "8rFqf7QLxX1mUBiNPEMaLUpV2biz6n";
|
||||
const kTrocadorRefCode = "9eHm9BkQfS";
|
||||
|
||||
abstract class TrocadorAPI {
|
||||
static const String authority = "trocador.app";
|
||||
static const String onionAuthority =
|
||||
"trocadorfyhlu27aefre5u7zri66gudtzdyelymftvr4yjwcxhfaqsid.onion";
|
||||
|
||||
static const String markup = "1";
|
||||
static const String minKYCRating = "C";
|
||||
|
||||
static Uri _buildUri({
|
||||
required String method,
|
||||
required bool isOnion,
|
||||
Map<String, String>? params,
|
||||
}) {
|
||||
return isOnion
|
||||
? Uri.http(onionAuthority, "api/$method", params)
|
||||
: Uri.https(authority, "api/$method", params);
|
||||
}
|
||||
|
||||
static Future<dynamic> _makeGetRequest(Uri uri) async {
|
||||
int code = -1;
|
||||
try {
|
||||
debugPrint("URI: $uri");
|
||||
final response = await http.get(
|
||||
uri,
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
);
|
||||
|
||||
code = response.statusCode;
|
||||
|
||||
debugPrint("CODE: $code");
|
||||
debugPrint("BODY: ${response.body}");
|
||||
|
||||
final json = jsonDecode(response.body);
|
||||
|
||||
return json;
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"_makeRequest($uri) HTTP:$code threw: $e\n$s",
|
||||
level: LogLevel.Error,
|
||||
);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// fetch all supported coins
|
||||
static Future<ExchangeResponse<List<TrocadorCoin>>> getCoins({
|
||||
required bool isOnion,
|
||||
}) async {
|
||||
final uri = _buildUri(
|
||||
isOnion: isOnion,
|
||||
method: "coins",
|
||||
params: {
|
||||
"api_key": kTrocadorApiKey,
|
||||
"ref": kTrocadorRefCode,
|
||||
},
|
||||
);
|
||||
|
||||
try {
|
||||
final json = await _makeGetRequest(uri);
|
||||
|
||||
if (json is List) {
|
||||
final list = List<Map<String, dynamic>>.from(json);
|
||||
final List<TrocadorCoin> coins = list
|
||||
.map(
|
||||
(e) => TrocadorCoin.fromMap(e),
|
||||
)
|
||||
.toList();
|
||||
|
||||
return ExchangeResponse(value: coins);
|
||||
} else {
|
||||
throw Exception("unexpected json: $json");
|
||||
}
|
||||
} catch (e, s) {
|
||||
Logging.instance.log("getCoins exception: $e\n$s", level: LogLevel.Error);
|
||||
return ExchangeResponse(
|
||||
exception: ExchangeException(
|
||||
e.toString(),
|
||||
ExchangeExceptionType.generic,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// get trade info
|
||||
static Future<ExchangeResponse<TrocadorTrade>> getTrade({
|
||||
required bool isOnion,
|
||||
required String tradeId,
|
||||
}) async {
|
||||
final uri = _buildUri(
|
||||
isOnion: isOnion,
|
||||
method: "trade",
|
||||
params: {
|
||||
"api_key": kTrocadorApiKey,
|
||||
"ref": kTrocadorRefCode,
|
||||
"id": tradeId,
|
||||
},
|
||||
);
|
||||
|
||||
try {
|
||||
final json = await _makeGetRequest(uri);
|
||||
final map = Map<String, dynamic>.from((json as List).first as Map);
|
||||
|
||||
return ExchangeResponse(value: TrocadorTrade.fromMap(map));
|
||||
} catch (e, s) {
|
||||
Logging.instance.log("getTrade exception: $e\n$s", level: LogLevel.Error);
|
||||
return ExchangeResponse(
|
||||
exception: ExchangeException(
|
||||
e.toString(),
|
||||
ExchangeExceptionType.generic,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// get standard/floating rate
|
||||
static Future<ExchangeResponse<TrocadorRate>> getNewStandardRate({
|
||||
required bool isOnion,
|
||||
required String fromTicker,
|
||||
required String fromNetwork,
|
||||
required String toTicker,
|
||||
required String toNetwork,
|
||||
required String fromAmount,
|
||||
}) async {
|
||||
final params = {
|
||||
"api_key": kTrocadorApiKey,
|
||||
"ref": kTrocadorRefCode,
|
||||
"ticker_from": fromTicker.toLowerCase(),
|
||||
"network_from": fromNetwork,
|
||||
"ticker_to": toTicker.toLowerCase(),
|
||||
"network_to": toNetwork,
|
||||
"amount_from": fromAmount,
|
||||
"payment": "false",
|
||||
"min_kycrating": minKYCRating,
|
||||
"markup": markup,
|
||||
};
|
||||
|
||||
return await _getNewRate(isOnion: isOnion, params: params);
|
||||
}
|
||||
|
||||
/// get fixed rate/payment rate
|
||||
static Future<ExchangeResponse<TrocadorRate>> getNewPaymentRate({
|
||||
required bool isOnion,
|
||||
required String fromTicker,
|
||||
required String fromNetwork,
|
||||
required String toTicker,
|
||||
required String toNetwork,
|
||||
required String toAmount,
|
||||
}) async {
|
||||
final params = {
|
||||
"api_key": kTrocadorApiKey,
|
||||
"ref": kTrocadorRefCode,
|
||||
"ticker_from": fromTicker.toLowerCase(),
|
||||
"network_from": fromNetwork,
|
||||
"ticker_to": toTicker.toLowerCase(),
|
||||
"network_to": toNetwork,
|
||||
"amount_to": toAmount,
|
||||
"payment": "true",
|
||||
"min_kycrating": minKYCRating,
|
||||
"markup": markup,
|
||||
};
|
||||
|
||||
return await _getNewRate(isOnion: isOnion, params: params);
|
||||
}
|
||||
|
||||
static Future<ExchangeResponse<TrocadorRate>> _getNewRate({
|
||||
required bool isOnion,
|
||||
required Map<String, String> params,
|
||||
}) async {
|
||||
final uri = _buildUri(
|
||||
isOnion: isOnion,
|
||||
method: "new_rate",
|
||||
params: params,
|
||||
);
|
||||
|
||||
try {
|
||||
final json = await _makeGetRequest(uri);
|
||||
final map = Map<String, dynamic>.from(json as Map);
|
||||
|
||||
return ExchangeResponse(value: TrocadorRate.fromMap(map));
|
||||
} catch (e, s) {
|
||||
Logging.instance
|
||||
.log("getNewRate exception: $e\n$s", level: LogLevel.Error);
|
||||
return ExchangeResponse(
|
||||
exception: ExchangeException(
|
||||
e.toString(),
|
||||
ExchangeExceptionType.generic,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// create new floating rate/standard trade
|
||||
static Future<ExchangeResponse<TrocadorTradeNew>> createNewStandardRateTrade({
|
||||
required bool isOnion,
|
||||
required String? rateId,
|
||||
required String fromTicker,
|
||||
required String fromNetwork,
|
||||
required String toTicker,
|
||||
required String toNetwork,
|
||||
required String fromAmount,
|
||||
required String receivingAddress,
|
||||
required String? receivingMemo,
|
||||
required String refundAddress,
|
||||
required String? refundMemo,
|
||||
required String exchangeProvider,
|
||||
required bool isFixedRate,
|
||||
}) async {
|
||||
final Map<String, String> params = {
|
||||
"api_key": kTrocadorApiKey,
|
||||
"ref": kTrocadorRefCode,
|
||||
"ticker_from": fromTicker.toLowerCase(),
|
||||
"network_from": fromNetwork,
|
||||
"ticker_to": toTicker.toLowerCase(),
|
||||
"network_to": toNetwork,
|
||||
"amount_from": fromAmount,
|
||||
"address": receivingAddress,
|
||||
"address_memo": receivingMemo ?? "0",
|
||||
"refund": refundAddress,
|
||||
"refund_memo": refundMemo ?? "0",
|
||||
"provider": exchangeProvider,
|
||||
"fixed": isFixedRate.toString().capitalize(),
|
||||
"payment": "False",
|
||||
"min_kycrating": minKYCRating,
|
||||
"markup": markup,
|
||||
};
|
||||
|
||||
if (rateId != null) {
|
||||
params["id"] = rateId;
|
||||
}
|
||||
|
||||
return await _getNewTrade(isOnion: isOnion, params: params);
|
||||
}
|
||||
|
||||
static Future<ExchangeResponse<TrocadorTradeNew>> createNewPaymentRateTrade({
|
||||
required bool isOnion,
|
||||
required String? rateId,
|
||||
required String fromTicker,
|
||||
required String fromNetwork,
|
||||
required String toTicker,
|
||||
required String toNetwork,
|
||||
required String toAmount,
|
||||
required String receivingAddress,
|
||||
required String? receivingMemo,
|
||||
required String refundAddress,
|
||||
required String? refundMemo,
|
||||
required String exchangeProvider,
|
||||
required bool isFixedRate,
|
||||
}) async {
|
||||
final params = {
|
||||
"api_key": kTrocadorApiKey,
|
||||
"ref": kTrocadorRefCode,
|
||||
"ticker_from": fromTicker.toLowerCase(),
|
||||
"network_from": fromNetwork,
|
||||
"ticker_to": toTicker.toLowerCase(),
|
||||
"network_to": toNetwork,
|
||||
"amount_to": toAmount,
|
||||
"address": receivingAddress,
|
||||
"address_memo": receivingMemo ?? "0",
|
||||
"refund": refundAddress,
|
||||
"refund_memo": refundMemo ?? "0",
|
||||
"provider": exchangeProvider,
|
||||
"fixed": isFixedRate.toString().capitalize(),
|
||||
"payment": "True",
|
||||
"min_kycrating": minKYCRating,
|
||||
"markup": markup,
|
||||
};
|
||||
|
||||
if (rateId != null) {
|
||||
params["id"] = rateId;
|
||||
}
|
||||
|
||||
return await _getNewTrade(isOnion: isOnion, params: params);
|
||||
}
|
||||
|
||||
static Future<ExchangeResponse<TrocadorTradeNew>> _getNewTrade({
|
||||
required bool isOnion,
|
||||
required Map<String, String> params,
|
||||
}) async {
|
||||
final uri = _buildUri(
|
||||
isOnion: isOnion,
|
||||
method: "new_trade",
|
||||
params: params,
|
||||
);
|
||||
|
||||
try {
|
||||
final json = await _makeGetRequest(uri);
|
||||
final map = Map<String, dynamic>.from(json as Map);
|
||||
|
||||
try {
|
||||
return ExchangeResponse(value: TrocadorTradeNew.fromMap(map));
|
||||
} catch (e, s) {
|
||||
String error = map["error"] as String? ?? json.toString();
|
||||
if (error ==
|
||||
"trade could not be generated, some unknown error happened") {
|
||||
error =
|
||||
"This trade couldn't be completed. Please select another provider.";
|
||||
}
|
||||
|
||||
Logging.instance.log(
|
||||
"_getNewTrade failed to parse response: $json\n$e\n$s",
|
||||
level: LogLevel.Error,
|
||||
);
|
||||
return ExchangeResponse(
|
||||
exception: ExchangeException(
|
||||
error,
|
||||
ExchangeExceptionType.serializeResponseError,
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"_getNewTrade exception: $e\n$s",
|
||||
level: LogLevel.Error,
|
||||
);
|
||||
return ExchangeResponse(
|
||||
exception: ExchangeException(
|
||||
e.toString(),
|
||||
ExchangeExceptionType.generic,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
394
lib/services/exchange/trocador/trocador_exchange.dart
Normal file
394
lib/services/exchange/trocador/trocador_exchange.dart
Normal file
|
@ -0,0 +1,394 @@
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:decimal/decimal.dart';
|
||||
import 'package:stackwallet/exceptions/exchange/exchange_exception.dart';
|
||||
import 'package:stackwallet/models/exchange/response_objects/estimate.dart';
|
||||
import 'package:stackwallet/models/exchange/response_objects/range.dart';
|
||||
import 'package:stackwallet/models/exchange/response_objects/trade.dart';
|
||||
import 'package:stackwallet/models/isar/exchange_cache/currency.dart';
|
||||
import 'package:stackwallet/models/isar/exchange_cache/pair.dart';
|
||||
import 'package:stackwallet/services/exchange/exchange.dart';
|
||||
import 'package:stackwallet/services/exchange/exchange_response.dart';
|
||||
import 'package:stackwallet/services/exchange/trocador/response_objects/trocador_coin.dart';
|
||||
import 'package:stackwallet/services/exchange/trocador/response_objects/trocador_quote.dart';
|
||||
import 'package:stackwallet/services/exchange/trocador/trocador_api.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
class TrocadorExchange extends Exchange {
|
||||
TrocadorExchange._();
|
||||
|
||||
static TrocadorExchange? _instance;
|
||||
static TrocadorExchange get instance => _instance ??= TrocadorExchange._();
|
||||
|
||||
static const exchangeName = "Trocador";
|
||||
|
||||
static const onlySupportedNetwork = "Mainnet";
|
||||
|
||||
@override
|
||||
Future<ExchangeResponse<Trade>> createTrade({
|
||||
required String from,
|
||||
required String to,
|
||||
required bool fixedRate,
|
||||
required Decimal amount,
|
||||
required String addressTo,
|
||||
String? extraId,
|
||||
required String addressRefund,
|
||||
required String refundExtraId,
|
||||
Estimate? estimate,
|
||||
required bool reversed,
|
||||
}) async {
|
||||
final response = reversed
|
||||
? await TrocadorAPI.createNewPaymentRateTrade(
|
||||
isOnion: false,
|
||||
rateId: estimate?.rateId,
|
||||
fromTicker: from.toLowerCase(),
|
||||
fromNetwork: onlySupportedNetwork,
|
||||
toTicker: to.toLowerCase(),
|
||||
toNetwork: onlySupportedNetwork,
|
||||
toAmount: amount.toString(),
|
||||
receivingAddress: addressTo,
|
||||
receivingMemo: null,
|
||||
refundAddress: addressRefund,
|
||||
refundMemo: null,
|
||||
exchangeProvider: estimate!.exchangeProvider!,
|
||||
isFixedRate: fixedRate,
|
||||
)
|
||||
: await TrocadorAPI.createNewStandardRateTrade(
|
||||
isOnion: false,
|
||||
rateId: estimate?.rateId,
|
||||
fromTicker: from.toLowerCase(),
|
||||
fromNetwork: onlySupportedNetwork,
|
||||
toTicker: to.toLowerCase(),
|
||||
toNetwork: onlySupportedNetwork,
|
||||
fromAmount: amount.toString(),
|
||||
receivingAddress: addressTo,
|
||||
receivingMemo: null,
|
||||
refundAddress: addressRefund,
|
||||
refundMemo: null,
|
||||
exchangeProvider: estimate!.exchangeProvider!,
|
||||
isFixedRate: fixedRate,
|
||||
);
|
||||
|
||||
if (response.value == null) {
|
||||
return ExchangeResponse(exception: response.exception);
|
||||
}
|
||||
|
||||
final trade = response.value!;
|
||||
|
||||
return ExchangeResponse(
|
||||
value: Trade(
|
||||
uuid: const Uuid().v1(),
|
||||
tradeId: trade.tradeId,
|
||||
rateType: fixedRate ? "fixed" : "floating",
|
||||
direction: reversed ? "reversed" : "direct",
|
||||
timestamp: trade.date,
|
||||
updatedAt: trade.date,
|
||||
payInCurrency: trade.coinFrom,
|
||||
payInAmount: trade.amountFrom.toString(),
|
||||
payInAddress: trade.addressProvider,
|
||||
payInNetwork: trade.networkFrom,
|
||||
payInExtraId: trade.addressProviderMemo,
|
||||
payInTxid: "",
|
||||
payOutCurrency: trade.coinTo,
|
||||
payOutAmount: trade.amountTo.toString(),
|
||||
payOutAddress: trade.addressUser,
|
||||
payOutNetwork: trade.networkTo,
|
||||
payOutExtraId: trade.addressUserMemo,
|
||||
payOutTxid: "",
|
||||
refundAddress: trade.refundAddress,
|
||||
refundExtraId: trade.refundAddressMemo,
|
||||
status: trade.status,
|
||||
exchangeName: "$exchangeName (${trade.provider})",
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<TrocadorCoin>? _cachedCurrencies;
|
||||
|
||||
@override
|
||||
Future<ExchangeResponse<List<Currency>>> getAllCurrencies(
|
||||
bool fixedRate) async {
|
||||
_cachedCurrencies ??= (await TrocadorAPI.getCoins(isOnion: false)).value;
|
||||
|
||||
_cachedCurrencies?.removeWhere((e) => e.network != onlySupportedNetwork);
|
||||
|
||||
final value = _cachedCurrencies
|
||||
?.map(
|
||||
(e) => Currency(
|
||||
exchangeName: exchangeName,
|
||||
ticker: e.ticker,
|
||||
name: e.name,
|
||||
network: e.network,
|
||||
image: e.image,
|
||||
isFiat: false,
|
||||
rateType: SupportedRateType.both,
|
||||
isStackCoin: Currency.checkIsStackCoin(e.ticker),
|
||||
tokenContract: null,
|
||||
isAvailable: true,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
|
||||
if (value == null) {
|
||||
return ExchangeResponse(
|
||||
exception: ExchangeException(
|
||||
"Failed to fetch trocador coins",
|
||||
ExchangeExceptionType.generic,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return ExchangeResponse(value: value);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<ExchangeResponse<List<Pair>>> getAllPairs(bool fixedRate) async {
|
||||
final response = await getAllCurrencies(fixedRate);
|
||||
|
||||
if (response.value == null) {
|
||||
return ExchangeResponse(exception: response.exception);
|
||||
}
|
||||
|
||||
final List<Pair> pairs = [];
|
||||
|
||||
for (int i = 0; i < response.value!.length; i++) {
|
||||
final a = response.value![i];
|
||||
|
||||
for (int j = i + 1; j < response.value!.length; j++) {
|
||||
final b = response.value![j];
|
||||
|
||||
pairs.add(
|
||||
Pair(
|
||||
exchangeName: exchangeName,
|
||||
from: a.ticker,
|
||||
to: b.ticker,
|
||||
rateType: SupportedRateType.both,
|
||||
),
|
||||
);
|
||||
pairs.add(
|
||||
Pair(
|
||||
exchangeName: exchangeName,
|
||||
to: a.ticker,
|
||||
from: b.ticker,
|
||||
rateType: SupportedRateType.both,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return ExchangeResponse(value: pairs);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<ExchangeResponse<List<Estimate>>> getEstimates(
|
||||
String from,
|
||||
String to,
|
||||
Decimal amount,
|
||||
bool fixedRate,
|
||||
bool reversed,
|
||||
) async {
|
||||
final response = reversed
|
||||
? await TrocadorAPI.getNewPaymentRate(
|
||||
isOnion: false,
|
||||
fromTicker: from,
|
||||
fromNetwork: onlySupportedNetwork,
|
||||
toTicker: to,
|
||||
toNetwork: onlySupportedNetwork,
|
||||
toAmount: amount.toString(),
|
||||
)
|
||||
: await TrocadorAPI.getNewStandardRate(
|
||||
isOnion: false,
|
||||
fromTicker: from,
|
||||
fromNetwork: onlySupportedNetwork,
|
||||
toTicker: to,
|
||||
toNetwork: onlySupportedNetwork,
|
||||
fromAmount: amount.toString(),
|
||||
);
|
||||
|
||||
if (response.value == null) {
|
||||
return ExchangeResponse(exception: response.exception);
|
||||
}
|
||||
|
||||
final List<Estimate> estimates = [];
|
||||
final List<TrocadorQuote> cOrLowerQuotes = [];
|
||||
|
||||
for (final quote in response.value!.quotes) {
|
||||
if (quote.fixed == fixedRate &&
|
||||
quote.provider.toLowerCase() != "changenow") {
|
||||
final rating = quote.kycRating.toLowerCase();
|
||||
if (rating == "a" || rating == "b") {
|
||||
estimates.add(
|
||||
Estimate(
|
||||
estimatedAmount: reversed ? quote.amountFrom! : quote.amountTo!,
|
||||
fixedRate: quote.fixed,
|
||||
reversed: reversed,
|
||||
exchangeProvider: quote.provider,
|
||||
rateId: response.value!.tradeId,
|
||||
kycRating: quote.kycRating,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
cOrLowerQuotes.add(quote);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cOrLowerQuotes.sort((a, b) => b.waste.compareTo(a.waste));
|
||||
|
||||
for (int i = 0; i < min(3, cOrLowerQuotes.length); i++) {
|
||||
final quote = cOrLowerQuotes[i];
|
||||
estimates.add(
|
||||
Estimate(
|
||||
estimatedAmount: reversed ? quote.amountFrom! : quote.amountTo!,
|
||||
fixedRate: quote.fixed,
|
||||
reversed: reversed,
|
||||
exchangeProvider: quote.provider,
|
||||
rateId: response.value!.tradeId,
|
||||
kycRating: quote.kycRating,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return ExchangeResponse(
|
||||
value: estimates
|
||||
..sort((a, b) => b.estimatedAmount.compareTo(a.estimatedAmount)),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<ExchangeResponse<List<Currency>>> getPairedCurrencies(
|
||||
String forCurrency, bool fixedRate) async {
|
||||
// TODO: implement getPairedCurrencies
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<ExchangeResponse<List<Pair>>> getPairsFor(
|
||||
String currency,
|
||||
bool fixedRate,
|
||||
) async {
|
||||
final response = await getAllPairs(fixedRate);
|
||||
if (response.value == null) {
|
||||
return ExchangeResponse(exception: response.exception);
|
||||
}
|
||||
|
||||
final pairs = response.value!.where(
|
||||
(e) =>
|
||||
e.from.toUpperCase() == currency.toUpperCase() ||
|
||||
e.to.toUpperCase() == currency.toUpperCase(),
|
||||
);
|
||||
|
||||
return ExchangeResponse(value: pairs.toList());
|
||||
}
|
||||
|
||||
@override
|
||||
Future<ExchangeResponse<Range>> getRange(
|
||||
String from,
|
||||
String to,
|
||||
bool fixedRate,
|
||||
) async {
|
||||
if (_cachedCurrencies == null) {
|
||||
await getAllCurrencies(fixedRate);
|
||||
}
|
||||
if (_cachedCurrencies == null) {
|
||||
return ExchangeResponse(
|
||||
exception: ExchangeException(
|
||||
"Failed to updated trocador cached coins to get min/max range",
|
||||
ExchangeExceptionType.generic,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final fromCoin = _cachedCurrencies!
|
||||
.firstWhere((e) => e.ticker.toLowerCase() == from.toLowerCase());
|
||||
|
||||
return ExchangeResponse(
|
||||
value: Range(
|
||||
max: fromCoin.maximum,
|
||||
min: fromCoin.minimum,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<ExchangeResponse<Trade>> getTrade(String tradeId) async {
|
||||
// TODO: implement getTrade
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<ExchangeResponse<List<Trade>>> getTrades() async {
|
||||
// TODO: implement getTrades
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
String get name => exchangeName;
|
||||
|
||||
@override
|
||||
Future<ExchangeResponse<Trade>> updateTrade(Trade trade) async {
|
||||
final response = await TrocadorAPI.getTrade(
|
||||
isOnion: false,
|
||||
tradeId: trade.tradeId,
|
||||
);
|
||||
|
||||
if (response.value != null) {
|
||||
final updated = response.value!;
|
||||
final updatedTrade = Trade(
|
||||
uuid: trade.uuid,
|
||||
tradeId: updated.tradeId,
|
||||
rateType: trade.rateType,
|
||||
direction: trade.direction,
|
||||
timestamp: trade.timestamp,
|
||||
updatedAt: DateTime.now(),
|
||||
payInCurrency: updated.coinFrom,
|
||||
payInAmount: updated.amountFrom.toString(),
|
||||
payInAddress: updated.addressProvider,
|
||||
payInNetwork: trade.payInNetwork,
|
||||
payInExtraId: trade.payInExtraId,
|
||||
payInTxid: trade.payInTxid,
|
||||
payOutCurrency: updated.coinTo,
|
||||
payOutAmount: updated.amountTo.toString(),
|
||||
payOutAddress: updated.addressUser,
|
||||
payOutNetwork: trade.payOutNetwork,
|
||||
payOutExtraId: trade.payOutExtraId,
|
||||
payOutTxid: trade.payOutTxid,
|
||||
refundAddress: trade.refundAddress,
|
||||
refundExtraId: trade.refundExtraId,
|
||||
status: updated.status,
|
||||
exchangeName: "$exchangeName (${updated.provider})",
|
||||
);
|
||||
|
||||
return ExchangeResponse(value: updatedTrade);
|
||||
} else {
|
||||
if (response.exception?.type == ExchangeExceptionType.orderNotFound) {
|
||||
final updatedTrade = Trade(
|
||||
uuid: trade.uuid,
|
||||
tradeId: trade.tradeId,
|
||||
rateType: trade.rateType,
|
||||
direction: trade.direction,
|
||||
timestamp: trade.timestamp,
|
||||
updatedAt: DateTime.now(),
|
||||
payInCurrency: trade.payInCurrency,
|
||||
payInAmount: trade.payInAmount,
|
||||
payInAddress: trade.payInAddress,
|
||||
payInNetwork: trade.payInNetwork,
|
||||
payInExtraId: trade.payInExtraId,
|
||||
payInTxid: trade.payInTxid,
|
||||
payOutCurrency: trade.payOutCurrency,
|
||||
payOutAmount: trade.payOutAmount,
|
||||
payOutAddress: trade.payOutAddress,
|
||||
payOutNetwork: trade.payOutNetwork,
|
||||
payOutExtraId: trade.payOutExtraId,
|
||||
payOutTxid: trade.payOutTxid,
|
||||
refundAddress: trade.refundAddress,
|
||||
refundExtraId: trade.refundExtraId,
|
||||
status: "Unknown",
|
||||
exchangeName: trade.exchangeName,
|
||||
);
|
||||
return ExchangeResponse(value: updatedTrade);
|
||||
}
|
||||
return ExchangeResponse(exception: response.exception);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,9 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart';
|
||||
import 'package:stackwallet/services/exchange/majestic_bank/majestic_bank_exchange.dart';
|
||||
import 'package:stackwallet/services/exchange/simpleswap/simpleswap_exchange.dart';
|
||||
import 'package:stackwallet/services/exchange/trocador/trocador_exchange.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
import 'package:stackwallet/utilities/theme/color_theme.dart';
|
||||
import 'package:stackwallet/utilities/theme/stack_colors.dart';
|
||||
|
@ -63,6 +67,23 @@ class _EXCHANGE {
|
|||
String get simpleSwap => "${_path}simpleswap-icon.svg";
|
||||
String get majesticBankBlue => "${_path}mb_blue.svg";
|
||||
String get majesticBankGreen => "${_path}mb_green.svg";
|
||||
String get trocador => "${_path}trocador.svg";
|
||||
|
||||
String getIconFor({required String exchangeName}) {
|
||||
switch (exchangeName) {
|
||||
case SimpleSwapExchange.exchangeName:
|
||||
return simpleSwap;
|
||||
case ChangeNowExchange.exchangeName:
|
||||
return changeNow;
|
||||
case MajesticBankExchange.exchangeName:
|
||||
return majesticBankBlue;
|
||||
case TrocadorExchange.exchangeName:
|
||||
return trocador;
|
||||
default:
|
||||
throw ArgumentError("Invalid exchange name passed to "
|
||||
"Assets.exchange.getIconFor()");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _BUY {
|
||||
|
@ -285,6 +306,11 @@ class _SVG {
|
|||
String get list => "assets/svg/list-ul.svg";
|
||||
String get unclaimedPaynym => "assets/svg/unclaimed.png";
|
||||
|
||||
String get trocadorRatingA => "assets/svg/trocador_rating_a.svg";
|
||||
String get trocadorRatingB => "assets/svg/trocador_rating_b.svg";
|
||||
String get trocadorRatingC => "assets/svg/trocador_rating_c.svg";
|
||||
String get trocadorRatingD => "assets/svg/trocador_rating_d.svg";
|
||||
|
||||
// TODO provide proper assets
|
||||
String get bitcoinTestnet => "assets/svg/coin_icons/Bitcoin.svg";
|
||||
String get bitcoincashTestnet => "assets/svg/coin_icons/Bitcoincash.svg";
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import 'package:stackwallet/db/isar/main_db.dart';
|
||||
import 'package:stackwallet/models/isar/models/block_explorer.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
|
||||
Uri getBlockExplorerTransactionUrlFor({
|
||||
Uri getDefaultBlockExplorerUrlFor({
|
||||
required Coin coin,
|
||||
required String txid,
|
||||
}) {
|
||||
|
@ -41,3 +43,29 @@ Uri getBlockExplorerTransactionUrlFor({
|
|||
return Uri.parse("https://chainz.cryptoid.info/part/tx.dws?$txid.htm");
|
||||
}
|
||||
}
|
||||
|
||||
/// returns internal Isar ID for the inserted object/record
|
||||
Future<int> setBlockExplorerForCoin({
|
||||
required Coin coin,
|
||||
required Uri url,
|
||||
}) async {
|
||||
return await MainDB.instance.putTransactionBlockExplorer(
|
||||
TransactionBlockExplorer(
|
||||
ticker: coin.ticker,
|
||||
url: url.toString(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Uri getBlockExplorerTransactionUrlFor({
|
||||
required Coin coin,
|
||||
required String txid,
|
||||
}) {
|
||||
String? url = MainDB.instance.getTransactionBlockExplorer(coin: coin)?.url;
|
||||
if (url == null) {
|
||||
return getDefaultBlockExplorerUrlFor(coin: coin, txid: txid);
|
||||
} else {
|
||||
url = url.replaceAll("%5BTXID%5D", txid);
|
||||
return Uri.parse(url);
|
||||
}
|
||||
}
|
||||
|
|
138
lib/widgets/choose_coin_view.dart
Normal file
138
lib/widgets/choose_coin_view.dart
Normal file
|
@ -0,0 +1,138 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:stackwallet/providers/providers.dart';
|
||||
import 'package:stackwallet/utilities/assets.dart';
|
||||
import 'package:stackwallet/utilities/constants.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/utilities/theme/stack_colors.dart';
|
||||
import 'package:stackwallet/widgets/background.dart';
|
||||
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
import 'package:stackwallet/widgets/rounded_white_container.dart';
|
||||
|
||||
class ChooseCoinView extends ConsumerStatefulWidget {
|
||||
const ChooseCoinView({
|
||||
Key? key,
|
||||
required this.title,
|
||||
required this.coinAdditional,
|
||||
required this.nextRouteName,
|
||||
}) : super(key: key);
|
||||
|
||||
static const String routeName = "/chooseCoin";
|
||||
|
||||
final String title;
|
||||
final String coinAdditional;
|
||||
final String nextRouteName;
|
||||
|
||||
@override
|
||||
ConsumerState<ChooseCoinView> createState() => _ChooseCoinViewState();
|
||||
}
|
||||
|
||||
class _ChooseCoinViewState extends ConsumerState<ChooseCoinView> {
|
||||
List<Coin> _coins = [...Coin.values];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_coins = _coins.toList();
|
||||
_coins.remove(Coin.firoTestNet);
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
bool showTestNet = ref.watch(
|
||||
prefsChangeNotifierProvider.select((value) => value.showTestNetCoins),
|
||||
);
|
||||
|
||||
List<Coin> coins = showTestNet
|
||||
? _coins
|
||||
: _coins.sublist(0, _coins.length - kTestNetCoinCount);
|
||||
|
||||
return Background(
|
||||
child: Scaffold(
|
||||
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
|
||||
appBar: AppBar(
|
||||
leading: AppBarBackButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
title: Text(
|
||||
widget.title,
|
||||
style: STextStyles.navBarTitle(context),
|
||||
),
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 12,
|
||||
left: 12,
|
||||
right: 12,
|
||||
),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
...coins.map(
|
||||
(coin) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: RoundedWhiteContainer(
|
||||
padding: const EdgeInsets.all(0),
|
||||
child: RawMaterialButton(
|
||||
// splashColor: Theme.of(context).extension<StackColors>()!.highlight,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
),
|
||||
materialTapTargetSize:
|
||||
MaterialTapTargetSize.shrinkWrap,
|
||||
onPressed: () {
|
||||
Navigator.of(context).pushNamed(
|
||||
widget.nextRouteName,
|
||||
arguments: coin,
|
||||
);
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Row(
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
Assets.svg.iconFor(coin: coin),
|
||||
width: 24,
|
||||
height: 24,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 12,
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"${coin.prettyName} ${widget.coinAdditional}",
|
||||
style: STextStyles.titleBold12(context),
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
54
lib/widgets/exchange/trocador/trocador_kyc_icon.dart
Normal file
54
lib/widgets/exchange/trocador/trocador_kyc_icon.dart
Normal file
|
@ -0,0 +1,54 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:stackwallet/utilities/assets.dart';
|
||||
import 'package:stackwallet/utilities/theme/stack_colors.dart';
|
||||
import 'package:stackwallet/widgets/exchange/trocador/trocador_rating_type_enum.dart';
|
||||
|
||||
class TrocadorKYCIcon extends StatelessWidget {
|
||||
const TrocadorKYCIcon({
|
||||
Key? key,
|
||||
required this.kycType,
|
||||
this.width = 18,
|
||||
this.height = 18,
|
||||
}) : super(key: key);
|
||||
|
||||
final TrocadorKYCType kycType;
|
||||
final double width;
|
||||
final double height;
|
||||
|
||||
String _getAssetName(TrocadorKYCType type) {
|
||||
switch (type) {
|
||||
case TrocadorKYCType.a:
|
||||
return Assets.svg.trocadorRatingA;
|
||||
case TrocadorKYCType.b:
|
||||
return Assets.svg.trocadorRatingB;
|
||||
case TrocadorKYCType.c:
|
||||
return Assets.svg.trocadorRatingC;
|
||||
case TrocadorKYCType.d:
|
||||
return Assets.svg.trocadorRatingD;
|
||||
}
|
||||
}
|
||||
|
||||
Color _getColor(TrocadorKYCType type, BuildContext context) {
|
||||
switch (type) {
|
||||
case TrocadorKYCType.a:
|
||||
return Theme.of(context).extension<StackColors>()!.accentColorGreen;
|
||||
case TrocadorKYCType.b:
|
||||
return const Color(0xFF7AA500);
|
||||
case TrocadorKYCType.c:
|
||||
return Theme.of(context).extension<StackColors>()!.accentColorYellow;
|
||||
case TrocadorKYCType.d:
|
||||
return const Color(0xFFF37B58);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SvgPicture.asset(
|
||||
_getAssetName(kycType),
|
||||
width: width,
|
||||
height: height,
|
||||
color: _getColor(kycType, context),
|
||||
);
|
||||
}
|
||||
}
|
28
lib/widgets/exchange/trocador/trocador_kyc_info_button.dart
Normal file
28
lib/widgets/exchange/trocador/trocador_kyc_info_button.dart
Normal file
|
@ -0,0 +1,28 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:stackwallet/widgets/exchange/trocador/trocador_kyc_icon.dart';
|
||||
import 'package:stackwallet/widgets/exchange/trocador/trocador_rating_type_enum.dart';
|
||||
import 'package:stackwallet/widgets/trocador_kyc_rating_info.dart';
|
||||
|
||||
class TrocadorKYCInfoButton extends StatelessWidget {
|
||||
const TrocadorKYCInfoButton({
|
||||
Key? key,
|
||||
required this.kycType,
|
||||
}) : super(key: key);
|
||||
|
||||
final TrocadorKYCType kycType;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return IconButton(
|
||||
onPressed: () {
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
builder: (context) => const TrocadorKYCRatingInfo(),
|
||||
);
|
||||
},
|
||||
icon: TrocadorKYCIcon(
|
||||
kycType: kycType,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
15
lib/widgets/exchange/trocador/trocador_rating_type_enum.dart
Normal file
15
lib/widgets/exchange/trocador/trocador_rating_type_enum.dart
Normal file
|
@ -0,0 +1,15 @@
|
|||
enum TrocadorKYCType {
|
||||
a,
|
||||
b,
|
||||
c,
|
||||
d;
|
||||
|
||||
static TrocadorKYCType fromString(String type) {
|
||||
for (final result in values) {
|
||||
if (result.name == type.toLowerCase()) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
throw ArgumentError("Invalid trocador kyc type: $type");
|
||||
}
|
||||
}
|
174
lib/widgets/trocador_kyc_rating_info.dart
Normal file
174
lib/widgets/trocador_kyc_rating_info.dart
Normal file
|
@ -0,0 +1,174 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/widgets/conditional_parent.dart';
|
||||
import 'package:stackwallet/widgets/desktop/desktop_dialog.dart';
|
||||
import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart';
|
||||
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
||||
import 'package:stackwallet/widgets/desktop/secondary_button.dart';
|
||||
import 'package:stackwallet/widgets/exchange/trocador/trocador_kyc_icon.dart';
|
||||
import 'package:stackwallet/widgets/exchange/trocador/trocador_rating_type_enum.dart';
|
||||
import 'package:stackwallet/widgets/stack_dialog.dart';
|
||||
|
||||
class TrocadorKYCRatingInfo extends StatelessWidget {
|
||||
const TrocadorKYCRatingInfo({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final small = MediaQuery.of(context).size.width <= 500;
|
||||
|
||||
return ConditionalParent(
|
||||
condition: !small,
|
||||
builder: (child) => DesktopDialog(
|
||||
maxHeight: double.infinity,
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 32),
|
||||
child: Text(
|
||||
"Trocador KYC Rating",
|
||||
style: STextStyles.desktopH3(context),
|
||||
),
|
||||
),
|
||||
const DesktopDialogCloseButton(),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 32,
|
||||
),
|
||||
child: child,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(32),
|
||||
child: Row(
|
||||
children: [
|
||||
const Spacer(),
|
||||
const SizedBox(
|
||||
width: 16,
|
||||
),
|
||||
Expanded(
|
||||
child: PrimaryButton(
|
||||
label: "Ok",
|
||||
buttonHeight: ButtonHeight.l,
|
||||
onPressed: Navigator.of(context).pop,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
child: ConditionalParent(
|
||||
condition: small,
|
||||
builder: (child) {
|
||||
return StackDialogBase(
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
child: Column(
|
||||
children: [
|
||||
if (small)
|
||||
Text(
|
||||
"Trocador KYC Rating",
|
||||
style: STextStyles.pageTitleH2(context),
|
||||
),
|
||||
if (small)
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
const _Rating(
|
||||
kycType: TrocadorKYCType.a,
|
||||
text: "Never asks for user verification.",
|
||||
),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
const _Rating(
|
||||
kycType: TrocadorKYCType.b,
|
||||
text: "Rarely asks for verification. Refunds if refused.",
|
||||
),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
const _Rating(
|
||||
kycType: TrocadorKYCType.c,
|
||||
text:
|
||||
"Rarely asks for verification. Refunds if refused, unless a "
|
||||
"legal order prevents it.",
|
||||
),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
const _Rating(
|
||||
kycType: TrocadorKYCType.d,
|
||||
text:
|
||||
"Rarely asks for verification. In case of refusal may block "
|
||||
"funds indefinitely without a legal order.",
|
||||
),
|
||||
if (small)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 16,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
const Spacer(),
|
||||
const SizedBox(
|
||||
width: 16,
|
||||
),
|
||||
Expanded(
|
||||
child: SecondaryButton(
|
||||
label: "Close",
|
||||
onPressed: Navigator.of(context).pop,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _Rating extends StatelessWidget {
|
||||
const _Rating({
|
||||
Key? key,
|
||||
required this.kycType,
|
||||
required this.text,
|
||||
}) : super(key: key);
|
||||
|
||||
final TrocadorKYCType kycType;
|
||||
final String text;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
TrocadorKYCIcon(
|
||||
kycType: kycType,
|
||||
width: 20,
|
||||
height: 20,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
Flexible(
|
||||
child: Text(
|
||||
text,
|
||||
style: STextStyles.subtitle(context),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
|
@ -11,7 +11,7 @@ description: Stack Wallet
|
|||
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
||||
# Read more about iOS versioning at
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
version: 1.7.4+165
|
||||
version: 1.7.7+169
|
||||
|
||||
environment:
|
||||
sdk: ">=2.17.0 <3.0.0"
|
||||
|
@ -327,6 +327,10 @@ flutter:
|
|||
- assets/svg/framed-gear.svg
|
||||
- assets/svg/list-ul.svg
|
||||
- assets/svg/cc.svg
|
||||
- assets/svg/trocador_rating_a.svg
|
||||
- assets/svg/trocador_rating_b.svg
|
||||
- assets/svg/trocador_rating_c.svg
|
||||
- assets/svg/trocador_rating_d.svg
|
||||
|
||||
|
||||
# coin icons
|
||||
|
|
9
scripts/setup.sh
Normal file → Executable file
9
scripts/setup.sh
Normal file → Executable file
|
@ -12,7 +12,7 @@ sudo apt install -y unzip pkg-config clang cmake ninja-build libgtk-3-dev
|
|||
cd $DEVELOPMENT
|
||||
git clone https://github.com/flutter/flutter.git
|
||||
cd flutter
|
||||
git checkout 3.7.6
|
||||
git checkout 3.7.12
|
||||
export FLUTTER_DIR=$(pwd)/bin
|
||||
echo 'export PATH="$PATH:'${FLUTTER_DIR}'"' >> ~/.bashrc
|
||||
source ~/.bashrc
|
||||
|
@ -25,14 +25,14 @@ cd stack_wallet
|
|||
export STACK_WALLET=$(pwd)
|
||||
git submodule update --init --recursive
|
||||
|
||||
# Create template lib/external_api_keys.dart file if it doesn't already exist
|
||||
# create template lib/external_api_keys.dart file if it doesn't already exist
|
||||
KEYS="$HOME/projects/stack_wallet/lib/external_api_keys.dart"
|
||||
if ! test -f "$KEYS"; then
|
||||
echo 'prebuild.sh: creating template lib/external_api_keys.dart file'
|
||||
printf 'const kChangeNowApiKey = "";\nconst kSimpleSwapApiKey = "";' > $KEYS
|
||||
fi
|
||||
|
||||
#install stack wallet dependencies
|
||||
# install stack wallet dependencies
|
||||
sudo apt-get install -y unzip automake build-essential file pkg-config git python libtool libtinfo5 cmake openjdk-8-jre-headless libgit2-dev clang libncurses5-dev libncursesw5-dev zlib1g-dev llvm
|
||||
|
||||
sudo apt-get install -y debhelper libclang-dev cargo rustc opencl-headers libssl-dev ocl-icd-opencl-dev
|
||||
|
@ -42,6 +42,7 @@ sudo apt-get install -y unzip automake build-essential file pkg-config git pytho
|
|||
sudo apt install -y libc6-dev-i386
|
||||
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
|
||||
source "$HOME/.cargo/env"
|
||||
cargo install cargo-ndk
|
||||
rustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-android x86_64-linux-android
|
||||
|
||||
|
@ -49,3 +50,5 @@ rustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-andro
|
|||
cd $STACK_WALLET
|
||||
cd scripts/android
|
||||
./build_all.sh
|
||||
cd ../linux
|
||||
./build_all.sh
|
||||
|
|
|
@ -510,6 +510,19 @@ class MockPrefs extends _i1.Mock implements _i5.Prefs {
|
|||
returnValueForMissingStub: null,
|
||||
);
|
||||
@override
|
||||
bool get randomizePIN => (super.noSuchMethod(
|
||||
Invocation.getter(#randomizePIN),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
set randomizePIN(bool? randomizePIN) => super.noSuchMethod(
|
||||
Invocation.setter(
|
||||
#randomizePIN,
|
||||
randomizePIN,
|
||||
),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
@override
|
||||
bool get useBiometrics => (super.noSuchMethod(
|
||||
Invocation.getter(#useBiometrics),
|
||||
returnValue: false,
|
||||
|
|
|
@ -231,6 +231,19 @@ class MockPrefs extends _i1.Mock implements _i4.Prefs {
|
|||
returnValueForMissingStub: null,
|
||||
);
|
||||
@override
|
||||
bool get randomizePIN => (super.noSuchMethod(
|
||||
Invocation.getter(#randomizePIN),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
set randomizePIN(bool? randomizePIN) => super.noSuchMethod(
|
||||
Invocation.setter(
|
||||
#randomizePIN,
|
||||
randomizePIN,
|
||||
),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
@override
|
||||
bool get useBiometrics => (super.noSuchMethod(
|
||||
Invocation.getter(#useBiometrics),
|
||||
returnValue: false,
|
||||
|
|
|
@ -2350,6 +2350,19 @@ class MockPrefs extends _i1.Mock implements _i24.Prefs {
|
|||
returnValueForMissingStub: null,
|
||||
);
|
||||
@override
|
||||
bool get randomizePIN => (super.noSuchMethod(
|
||||
Invocation.getter(#randomizePIN),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
set randomizePIN(bool? randomizePIN) => super.noSuchMethod(
|
||||
Invocation.setter(
|
||||
#randomizePIN,
|
||||
randomizePIN,
|
||||
),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
@override
|
||||
bool get useBiometrics => (super.noSuchMethod(
|
||||
Invocation.getter(#useBiometrics),
|
||||
returnValue: false,
|
||||
|
|
|
@ -181,6 +181,19 @@ class MockPrefs extends _i1.Mock implements _i3.Prefs {
|
|||
returnValueForMissingStub: null,
|
||||
);
|
||||
@override
|
||||
bool get randomizePIN => (super.noSuchMethod(
|
||||
Invocation.getter(#randomizePIN),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
set randomizePIN(bool? randomizePIN) => super.noSuchMethod(
|
||||
Invocation.setter(
|
||||
#randomizePIN,
|
||||
randomizePIN,
|
||||
),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
@override
|
||||
bool get useBiometrics => (super.noSuchMethod(
|
||||
Invocation.getter(#useBiometrics),
|
||||
returnValue: false,
|
||||
|
|
|
@ -419,6 +419,19 @@ class MockPrefs extends _i1.Mock implements _i12.Prefs {
|
|||
returnValueForMissingStub: null,
|
||||
);
|
||||
@override
|
||||
bool get randomizePIN => (super.noSuchMethod(
|
||||
Invocation.getter(#randomizePIN),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
set randomizePIN(bool? randomizePIN) => super.noSuchMethod(
|
||||
Invocation.setter(
|
||||
#randomizePIN,
|
||||
randomizePIN,
|
||||
),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
@override
|
||||
bool get useBiometrics => (super.noSuchMethod(
|
||||
Invocation.getter(#useBiometrics),
|
||||
returnValue: false,
|
||||
|
|
|
@ -2282,6 +2282,19 @@ class MockPrefs extends _i1.Mock implements _i19.Prefs {
|
|||
returnValueForMissingStub: null,
|
||||
);
|
||||
@override
|
||||
bool get randomizePIN => (super.noSuchMethod(
|
||||
Invocation.getter(#randomizePIN),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
set randomizePIN(bool? randomizePIN) => super.noSuchMethod(
|
||||
Invocation.setter(
|
||||
#randomizePIN,
|
||||
randomizePIN,
|
||||
),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
@override
|
||||
bool get useBiometrics => (super.noSuchMethod(
|
||||
Invocation.getter(#useBiometrics),
|
||||
returnValue: false,
|
||||
|
|
Loading…
Reference in a new issue