mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2024-11-16 17:27:37 +00:00
Cw 78 ethereum (#862)
* Add initial flow for ethereum * Add initial create Eth wallet flow * Complete Ethereum wallet creation flow * Fix web3dart versioning issue * Add primary receive address extracted from private key * Implement open wallet functionality * Implement restore wallet from seed functionality * Fixate web3dart version as higher versions cause some issues * Add Initial Transaction priorities for eth Add estimated gas price * Rename priority value to tip * Re-order wallet types * Change ethereum node Fix connection issues * Fix estimating gas for priority * Add case for ethereum to fetch it's seeds * Add case for ethereum to request node * Fix Exchange screen initial pairs * Add initial send transaction flow * Add missing configure for ethereum class * Add Eth address initial setup * Fix Private key for Ethereum wallets * Change sign/send transaction flow * - Fix Conflicts with main - Remove unused function from Haven configure.dart * Add build command for ethereum package * Add missing Node list file to pubspec * - Fix balance display - Fix parsing of Ethereum amount - Add more Ethereum Nodes * - Fix extracting Ethereum Private key from seeds - Integrate signing/sending transaction with the send view model * - Update and Fix Conflicts with main * Add Balances for ERC20 tokens * Fix conflicts with main * Add erc20 abi json * Add send erc20 tokens initial function * add missing getHeightByDate in Haven * Allow contacts and wallets from the same tag * Add Shiba Inu icon * Add send ERC-20 tokens initial flow * Add missing import in generated file * Add initial approach for transaction sending for ERC-20 tokens * Refactor signing/sending transactions * Add initial flow for transactions subscription * Refactor signing/sending transactions * Add home settings icon * Fix conflicts with main * Initial flow for home settings * Add logic flow for adding erc20 tokens * Fix initial UI * Finalize UI for Tokens * Integrate UI with Ethereum flow * Add "Enable/Disable" feature for ERC20 tokens * Add initial Erc20 tokens * Add Sorting and Pin Native Token features * Fix price sorting * Sort tokens list as well when Sort criteria changes * - Improve sorting balances flow - Add initial add token from search bar flow * Fix Accounts Popup UI * Fix Pin native token * Fix Enabling/Disabling tokens Fix sorting by fiat once app is opened Improve token availability mechanism * Fix deleting token Fix renaming tokens * Fix issue with search * Add more tokens * - Fix scroll issue - Add ERC20 tokens placeholder image in picker * - Separate and organize default erc20 tokens - Fix scrolling - Add token placeholder images in picker - Sort disabled tokens alphabetically * Change BNB token initial availability * Fix Conflicts with main * Fix Conflicts with main * Add Verse ERC20 token to the initial tokens list * Add rename wallet to Ethereum * Integrate EtherScan API for fetching address transactions Generate Ethereum specific secrets in Ethereum package * Adjust transactions fiat price for ERC20 tokens * Free Up GitHub Actions Ubuntu Runner Disk Space * Free Up GitHub Actions Ubuntu Runner Disk space (trial 2) * Fix Transaction Fee display * Save transaction history * Enhance loading time for erc20 tokens transactions * Minor Fixes and Enhancements * Fix sending erc20 fix block explorer issue * Fix int overflow * Fix transaction amount conversions * Minor: `slow` -> `Slow` * Update build guide * Fix fetching fiat rate taking a lot of time by only fetching enabled tokens only and making the API calls in parallel not sequential * Update transactions on a periodic basis * For fee, use ETH spot price, not ERC-20 spot price * Add Etherscan History privacy option to enable/disable Etherscan API * Show estimated fee amounts in the send screen * fix send fiat fields parsing issue * Fix transactions estimated fee less than actual fee * handle balance sorting when balance is disabled Handle empty transactions list * Fix Delete Ethereum wallet Fix balance < 0.01 * Fix Decimal place for Ethereum amount Fix sending amount issue * Change words count * Remove balance hint and Full balance row from Ethereum wallets * support changing the asset type in send templates * Fix Templates for ERC tokens issues * Fix conflicts in send templates * Disable batch sending in Ethereum * Fix Fee calculation with different priorities * Fix Conflicts with main * Add offline error to ignored exceptions --------- Co-authored-by: Justin Ehrenhofer <justin.ehrenhofer@gmail.com>
This commit is contained in:
parent
4120394121
commit
3ce4000dcf
137 changed files with 7164 additions and 1492 deletions
2
.github/workflows/pr_test_build.yml
vendored
2
.github/workflows/pr_test_build.yml
vendored
|
@ -92,6 +92,7 @@ jobs:
|
||||||
cd cw_monero && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
|
cd cw_monero && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
|
||||||
cd cw_bitcoin && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
|
cd cw_bitcoin && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
|
||||||
cd cw_haven && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
|
cd cw_haven && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
|
||||||
|
cd cw_ethereum && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
|
||||||
flutter packages pub run build_runner build --delete-conflicting-outputs
|
flutter packages pub run build_runner build --delete-conflicting-outputs
|
||||||
|
|
||||||
- name: Add secrets
|
- name: Add secrets
|
||||||
|
@ -124,6 +125,7 @@ jobs:
|
||||||
echo "const anonPayReferralCode = '${{ secrets.ANON_PAY_REFERRAL_CODE }}';" >> lib/.secrets.g.dart
|
echo "const anonPayReferralCode = '${{ secrets.ANON_PAY_REFERRAL_CODE }}';" >> lib/.secrets.g.dart
|
||||||
echo "const fiatApiKey = '${{ secrets.FIAT_API_KEY }}';" >> lib/.secrets.g.dart
|
echo "const fiatApiKey = '${{ secrets.FIAT_API_KEY }}';" >> lib/.secrets.g.dart
|
||||||
echo "const payfuraApiKey = '${{ secrets.PAYFURA_API_KEY }}';" >> lib/.secrets.g.dart
|
echo "const payfuraApiKey = '${{ secrets.PAYFURA_API_KEY }}';" >> lib/.secrets.g.dart
|
||||||
|
echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> cw_ethereum/lib/.secrets.g.dart
|
||||||
|
|
||||||
- name: Rename app
|
- name: Rename app
|
||||||
run: echo -e "id=com.cakewallet.test\nname=$GITHUB_HEAD_REF" > /opt/android/cake_wallet/android/app.properties
|
run: echo -e "id=com.cakewallet.test\nname=$GITHUB_HEAD_REF" > /opt/android/cake_wallet/android/app.properties
|
||||||
|
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -90,7 +90,9 @@ android/key.properties
|
||||||
**/tool/.secrets-prod.json
|
**/tool/.secrets-prod.json
|
||||||
**/tool/.secrets-test.json
|
**/tool/.secrets-test.json
|
||||||
**/tool/.secrets-config.json
|
**/tool/.secrets-config.json
|
||||||
|
**/tool/.ethereum-secrets-config.json
|
||||||
**/lib/.secrets.g.dart
|
**/lib/.secrets.g.dart
|
||||||
|
**/cw_ethereum/lib/.secrets.g.dart
|
||||||
|
|
||||||
vendor/
|
vendor/
|
||||||
|
|
||||||
|
@ -121,6 +123,7 @@ cw_haven/android/.cxx/
|
||||||
lib/bitcoin/bitcoin.dart
|
lib/bitcoin/bitcoin.dart
|
||||||
lib/monero/monero.dart
|
lib/monero/monero.dart
|
||||||
lib/haven/haven.dart
|
lib/haven/haven.dart
|
||||||
|
lib/ethereum/ethereum.dart
|
||||||
|
|
||||||
ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_180.png
|
ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_180.png
|
||||||
ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_120.png
|
ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_120.png
|
||||||
|
|
10
assets/ethereum_server_list.yml
Normal file
10
assets/ethereum_server_list.yml
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
-
|
||||||
|
uri: ethereum.publicnode.com
|
||||||
|
-
|
||||||
|
uri: eth.llamarpc.com
|
||||||
|
-
|
||||||
|
uri: rpc.flashbots.net
|
||||||
|
-
|
||||||
|
uri: eth-mainnet.public.blastapi.io
|
||||||
|
-
|
||||||
|
uri: ethereum.publicnode.com
|
BIN
assets/images/home_screen_settings_icon.png
Normal file
BIN
assets/images/home_screen_settings_icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 394 B |
10
configure_cake_wallet_android.sh
Normal file
10
configure_cake_wallet_android.sh
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
cd scripts/android
|
||||||
|
source ./app_env.sh cakewallet
|
||||||
|
./app_config.sh
|
||||||
|
cd ../.. && flutter pub get
|
||||||
|
cd cw_core && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
|
||||||
|
cd cw_monero && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
|
||||||
|
cd cw_bitcoin && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
|
||||||
|
cd cw_haven && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
|
||||||
|
cd cw_ethereum && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
|
||||||
|
flutter packages pub run build_runner build --delete-conflicting-outputs
|
|
@ -1,7 +1,6 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:cw_core/pathForWallet.dart';
|
import 'package:cw_core/pathForWallet.dart';
|
||||||
import 'package:cw_core/wallet_info.dart';
|
import 'package:cw_core/wallet_info.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
import 'package:cw_core/transaction_history.dart';
|
import 'package:cw_core/transaction_history.dart';
|
||||||
import 'package:cw_bitcoin/file.dart';
|
import 'package:cw_bitcoin/file.dart';
|
||||||
|
@ -67,7 +66,7 @@ abstract class ElectrumTransactionHistoryBase
|
||||||
Future<void> _load() async {
|
Future<void> _load() async {
|
||||||
try {
|
try {
|
||||||
final content = await _read();
|
final content = await _read();
|
||||||
final txs = content['transactions'] as Map<String, dynamic> ?? {};
|
final txs = content['transactions'] as Map<String, dynamic>? ?? {};
|
||||||
|
|
||||||
txs.entries.forEach((entry) {
|
txs.entries.forEach((entry) {
|
||||||
final val = entry.value;
|
final val = entry.value;
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
||||||
import 'package:bitcoin_flutter/src/payments/index.dart' show PaymentData;
|
import 'package:bitcoin_flutter/src/payments/index.dart' show PaymentData;
|
||||||
import 'package:cw_bitcoin/address_from_output.dart';
|
import 'package:cw_bitcoin/address_from_output.dart';
|
||||||
|
@ -217,9 +216,9 @@ class ElectrumTransactionInfo extends TransactionInfo {
|
||||||
height: info.height,
|
height: info.height,
|
||||||
amount: info.amount,
|
amount: info.amount,
|
||||||
fee: info.fee,
|
fee: info.fee,
|
||||||
direction: direction ?? info.direction,
|
direction: direction,
|
||||||
date: date ?? info.date,
|
date: date,
|
||||||
isPending: isPending ?? info.isPending,
|
isPending: isPending,
|
||||||
confirmations: info.confirmations);
|
confirmations: info.confirmations);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -431,6 +431,7 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
|
||||||
await transactionHistory.save();
|
await transactionHistory.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
Future<void> renameWalletFiles(String newWalletName) async {
|
Future<void> renameWalletFiles(String newWalletName) async {
|
||||||
final currentWalletPath = await pathForWallet(name: walletInfo.name, type: type);
|
final currentWalletPath = await pathForWallet(name: walletInfo.name, type: type);
|
||||||
final currentWalletFile = File(currentWalletPath);
|
final currentWalletFile = File(currentWalletPath);
|
||||||
|
|
|
@ -27,7 +27,7 @@ dependencies:
|
||||||
unorm_dart: ^0.2.0
|
unorm_dart: ^0.2.0
|
||||||
cryptography: ^2.0.5
|
cryptography: ^2.0.5
|
||||||
encrypt: ^5.0.1
|
encrypt: ^5.0.1
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|
|
@ -11,6 +11,8 @@ CryptoCurrency currencyForWalletType(WalletType type) {
|
||||||
return CryptoCurrency.ltc;
|
return CryptoCurrency.ltc;
|
||||||
case WalletType.haven:
|
case WalletType.haven:
|
||||||
return CryptoCurrency.xhv;
|
return CryptoCurrency.xhv;
|
||||||
|
case WalletType.ethereum:
|
||||||
|
return CryptoCurrency.eth;
|
||||||
default:
|
default:
|
||||||
throw Exception('Unexpected wallet type: ${type.toString()} for CryptoCurrency currencyForWalletType');
|
throw Exception('Unexpected wallet type: ${type.toString()} for CryptoCurrency currencyForWalletType');
|
||||||
}
|
}
|
||||||
|
|
64
cw_core/lib/erc20_token.dart
Normal file
64
cw_core/lib/erc20_token.dart
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
|
|
||||||
|
part 'erc20_token.g.dart';
|
||||||
|
|
||||||
|
@HiveType(typeId: Erc20Token.typeId)
|
||||||
|
class Erc20Token extends CryptoCurrency with HiveObjectMixin {
|
||||||
|
@HiveField(0)
|
||||||
|
final String name;
|
||||||
|
@HiveField(1)
|
||||||
|
final String symbol;
|
||||||
|
@HiveField(2)
|
||||||
|
final String contractAddress;
|
||||||
|
@HiveField(3)
|
||||||
|
final int decimal;
|
||||||
|
@HiveField(4, defaultValue: true)
|
||||||
|
bool _enabled;
|
||||||
|
@HiveField(5)
|
||||||
|
final String? iconPath;
|
||||||
|
|
||||||
|
bool get enabled => _enabled;
|
||||||
|
|
||||||
|
set enabled(bool value) => _enabled = value;
|
||||||
|
|
||||||
|
Erc20Token({
|
||||||
|
required this.name,
|
||||||
|
required this.symbol,
|
||||||
|
required this.contractAddress,
|
||||||
|
required this.decimal,
|
||||||
|
bool enabled = true,
|
||||||
|
this.iconPath,
|
||||||
|
}) : _enabled = enabled,
|
||||||
|
super(
|
||||||
|
name: symbol.toLowerCase(),
|
||||||
|
title: symbol.toUpperCase(),
|
||||||
|
fullName: name,
|
||||||
|
tag: "ETH",
|
||||||
|
iconPath: iconPath,
|
||||||
|
);
|
||||||
|
|
||||||
|
Erc20Token.copyWith(Erc20Token other, String? icon)
|
||||||
|
: this.name = other.name,
|
||||||
|
this.symbol = other.symbol,
|
||||||
|
this.contractAddress = other.contractAddress,
|
||||||
|
this.decimal = other.decimal,
|
||||||
|
this._enabled = other.enabled,
|
||||||
|
this.iconPath = icon,
|
||||||
|
super(
|
||||||
|
name: other.name,
|
||||||
|
title: other.symbol.toUpperCase(),
|
||||||
|
fullName: other.name,
|
||||||
|
tag: "ETH",
|
||||||
|
iconPath: icon,
|
||||||
|
);
|
||||||
|
|
||||||
|
static const typeId = 12;
|
||||||
|
static const boxName = 'Erc20Tokens';
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(other) => other is Erc20Token && other.contractAddress == contractAddress;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => contractAddress.hashCode;
|
||||||
|
}
|
|
@ -75,6 +75,8 @@ class Node extends HiveObject with Keyable {
|
||||||
return createUriFromElectrumAddress(uriRaw);
|
return createUriFromElectrumAddress(uriRaw);
|
||||||
case WalletType.haven:
|
case WalletType.haven:
|
||||||
return Uri.http(uriRaw, '');
|
return Uri.http(uriRaw, '');
|
||||||
|
case WalletType.ethereum:
|
||||||
|
return Uri.https(uriRaw, '');
|
||||||
default:
|
default:
|
||||||
throw Exception('Unexpected type ${type.toString()} for Node uri');
|
throw Exception('Unexpected type ${type.toString()} for Node uri');
|
||||||
}
|
}
|
||||||
|
@ -124,6 +126,8 @@ class Node extends HiveObject with Keyable {
|
||||||
return requestElectrumServer();
|
return requestElectrumServer();
|
||||||
case WalletType.haven:
|
case WalletType.haven:
|
||||||
return requestMoneroNode();
|
return requestMoneroNode();
|
||||||
|
case WalletType.ethereum:
|
||||||
|
return requestElectrumServer();
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -166,7 +170,7 @@ class Node extends HiveObject with Keyable {
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> requestNodeWithProxy(String proxy) async {
|
Future<bool> requestNodeWithProxy(String proxy) async {
|
||||||
|
|
||||||
|
@ -193,4 +197,17 @@ class Node extends HiveObject with Keyable {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<bool> requestEthereumServer() async {
|
||||||
|
try {
|
||||||
|
final response = await http.get(
|
||||||
|
uri,
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
);
|
||||||
|
|
||||||
|
return response.statusCode >= 200 && response.statusCode < 300;
|
||||||
|
} catch (_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,4 +75,6 @@ abstract class WalletBase<
|
||||||
Future<void>? updateBalance();
|
Future<void>? updateBalance();
|
||||||
|
|
||||||
void setExceptionHandler(void Function(FlutterErrorDetails) onError) => null;
|
void setExceptionHandler(void Function(FlutterErrorDetails) onError) => null;
|
||||||
|
|
||||||
|
Future<void> renameWalletFiles(String newWalletName);
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,5 +18,5 @@ abstract class WalletService<N extends WalletCredentials,
|
||||||
|
|
||||||
Future<void> remove(String wallet);
|
Future<void> remove(String wallet);
|
||||||
|
|
||||||
Future<void> rename(String name, String password, String newName);
|
Future<void> rename(String currentName, String password, String newName);
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,8 @@ const walletTypes = [
|
||||||
WalletType.monero,
|
WalletType.monero,
|
||||||
WalletType.bitcoin,
|
WalletType.bitcoin,
|
||||||
WalletType.litecoin,
|
WalletType.litecoin,
|
||||||
WalletType.haven
|
WalletType.haven,
|
||||||
|
WalletType.ethereum,
|
||||||
];
|
];
|
||||||
const walletTypeTypeId = 5;
|
const walletTypeTypeId = 5;
|
||||||
|
|
||||||
|
@ -27,6 +28,9 @@ enum WalletType {
|
||||||
|
|
||||||
@HiveField(4)
|
@HiveField(4)
|
||||||
haven,
|
haven,
|
||||||
|
|
||||||
|
@HiveField(5)
|
||||||
|
ethereum,
|
||||||
}
|
}
|
||||||
|
|
||||||
int serializeToInt(WalletType type) {
|
int serializeToInt(WalletType type) {
|
||||||
|
@ -39,6 +43,8 @@ int serializeToInt(WalletType type) {
|
||||||
return 2;
|
return 2;
|
||||||
case WalletType.haven:
|
case WalletType.haven:
|
||||||
return 3;
|
return 3;
|
||||||
|
case WalletType.ethereum:
|
||||||
|
return 4;
|
||||||
default:
|
default:
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
@ -54,6 +60,8 @@ WalletType deserializeFromInt(int raw) {
|
||||||
return WalletType.litecoin;
|
return WalletType.litecoin;
|
||||||
case 3:
|
case 3:
|
||||||
return WalletType.haven;
|
return WalletType.haven;
|
||||||
|
case 4:
|
||||||
|
return WalletType.ethereum;
|
||||||
default:
|
default:
|
||||||
throw Exception('Unexpected token: $raw for WalletType deserializeFromInt');
|
throw Exception('Unexpected token: $raw for WalletType deserializeFromInt');
|
||||||
}
|
}
|
||||||
|
@ -69,6 +77,8 @@ String walletTypeToString(WalletType type) {
|
||||||
return 'Litecoin';
|
return 'Litecoin';
|
||||||
case WalletType.haven:
|
case WalletType.haven:
|
||||||
return 'Haven';
|
return 'Haven';
|
||||||
|
case WalletType.ethereum:
|
||||||
|
return 'Ethereum';
|
||||||
default:
|
default:
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
@ -84,6 +94,8 @@ String walletTypeToDisplayName(WalletType type) {
|
||||||
return 'Litecoin (LTC)';
|
return 'Litecoin (LTC)';
|
||||||
case WalletType.haven:
|
case WalletType.haven:
|
||||||
return 'Haven (XHV)';
|
return 'Haven (XHV)';
|
||||||
|
case WalletType.ethereum:
|
||||||
|
return 'Ethereum (ETH)';
|
||||||
default:
|
default:
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
@ -99,6 +111,8 @@ CryptoCurrency walletTypeToCryptoCurrency(WalletType type) {
|
||||||
return CryptoCurrency.ltc;
|
return CryptoCurrency.ltc;
|
||||||
case WalletType.haven:
|
case WalletType.haven:
|
||||||
return CryptoCurrency.xhv;
|
return CryptoCurrency.xhv;
|
||||||
|
case WalletType.ethereum:
|
||||||
|
return CryptoCurrency.eth;
|
||||||
default:
|
default:
|
||||||
throw Exception('Unexpected wallet type: ${type.toString()} for CryptoCurrency walletTypeToCryptoCurrency');
|
throw Exception('Unexpected wallet type: ${type.toString()} for CryptoCurrency walletTypeToCryptoCurrency');
|
||||||
}
|
}
|
||||||
|
|
30
cw_ethereum/.gitignore
vendored
Normal file
30
cw_ethereum/.gitignore
vendored
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
# Miscellaneous
|
||||||
|
*.class
|
||||||
|
*.log
|
||||||
|
*.pyc
|
||||||
|
*.swp
|
||||||
|
.DS_Store
|
||||||
|
.atom/
|
||||||
|
.buildlog/
|
||||||
|
.history
|
||||||
|
.svn/
|
||||||
|
migrate_working_dir/
|
||||||
|
|
||||||
|
# IntelliJ related
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
*.iws
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# The .vscode folder contains launch configuration and tasks you configure in
|
||||||
|
# VS Code which you may wish to be included in version control, so this line
|
||||||
|
# is commented out by default.
|
||||||
|
#.vscode/
|
||||||
|
|
||||||
|
# Flutter/Dart/Pub related
|
||||||
|
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
|
||||||
|
/pubspec.lock
|
||||||
|
**/doc/api/
|
||||||
|
.dart_tool/
|
||||||
|
.packages
|
||||||
|
build/
|
10
cw_ethereum/.metadata
Normal file
10
cw_ethereum/.metadata
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
# This file tracks properties of this Flutter project.
|
||||||
|
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||||
|
#
|
||||||
|
# This file should be version controlled and should not be manually edited.
|
||||||
|
|
||||||
|
version:
|
||||||
|
revision: eb6d86ee27deecba4a83536aa20f366a6044895c
|
||||||
|
channel: stable
|
||||||
|
|
||||||
|
project_type: package
|
3
cw_ethereum/CHANGELOG.md
Normal file
3
cw_ethereum/CHANGELOG.md
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
## 0.0.1
|
||||||
|
|
||||||
|
* TODO: Describe initial release.
|
1
cw_ethereum/LICENSE
Normal file
1
cw_ethereum/LICENSE
Normal file
|
@ -0,0 +1 @@
|
||||||
|
TODO: Add your license here.
|
39
cw_ethereum/README.md
Normal file
39
cw_ethereum/README.md
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
<!--
|
||||||
|
This README describes the package. If you publish this package to pub.dev,
|
||||||
|
this README's contents appear on the landing page for your package.
|
||||||
|
|
||||||
|
For information about how to write a good package README, see the guide for
|
||||||
|
[writing package pages](https://dart.dev/guides/libraries/writing-package-pages).
|
||||||
|
|
||||||
|
For general information about developing packages, see the Dart guide for
|
||||||
|
[creating packages](https://dart.dev/guides/libraries/create-library-packages)
|
||||||
|
and the Flutter guide for
|
||||||
|
[developing packages and plugins](https://flutter.dev/developing-packages).
|
||||||
|
-->
|
||||||
|
|
||||||
|
TODO: Put a short description of the package here that helps potential users
|
||||||
|
know whether this package might be useful for them.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
TODO: List what your package can do. Maybe include images, gifs, or videos.
|
||||||
|
|
||||||
|
## Getting started
|
||||||
|
|
||||||
|
TODO: List prerequisites and provide or point to information on how to
|
||||||
|
start using the package.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
TODO: Include short and useful examples for package users. Add longer examples
|
||||||
|
to `/example` folder.
|
||||||
|
|
||||||
|
```dart
|
||||||
|
const like = 'sample';
|
||||||
|
```
|
||||||
|
|
||||||
|
## Additional information
|
||||||
|
|
||||||
|
TODO: Tell users more about the package: where to find more information, how to
|
||||||
|
contribute to the package, how to file issues, what response they can expect
|
||||||
|
from the package authors, and more.
|
4
cw_ethereum/analysis_options.yaml
Normal file
4
cw_ethereum/analysis_options.yaml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
include: package:flutter_lints/flutter.yaml
|
||||||
|
|
||||||
|
# Additional information about this file can be found at
|
||||||
|
# https://dart.dev/guides/language/analysis-options
|
7
cw_ethereum/lib/cw_ethereum.dart
Normal file
7
cw_ethereum/lib/cw_ethereum.dart
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
library cw_ethereum;
|
||||||
|
|
||||||
|
/// A Calculator.
|
||||||
|
class Calculator {
|
||||||
|
/// Returns [value] plus 1.
|
||||||
|
int addOne(int value) => value + 1;
|
||||||
|
}
|
302
cw_ethereum/lib/default_erc20_tokens.dart
Normal file
302
cw_ethereum/lib/default_erc20_tokens.dart
Normal file
|
@ -0,0 +1,302 @@
|
||||||
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
|
import 'package:cw_core/erc20_token.dart';
|
||||||
|
|
||||||
|
class DefaultErc20Tokens {
|
||||||
|
final List<Erc20Token> _defaultTokens = [
|
||||||
|
Erc20Token(
|
||||||
|
name: "USD Coin",
|
||||||
|
symbol: "USDC",
|
||||||
|
contractAddress: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
|
||||||
|
decimal: 6,
|
||||||
|
enabled: true,
|
||||||
|
),
|
||||||
|
Erc20Token(
|
||||||
|
name: "USDT Tether",
|
||||||
|
symbol: "USDT",
|
||||||
|
contractAddress: "0xdac17f958d2ee523a2206206994597c13d831ec7",
|
||||||
|
decimal: 6,
|
||||||
|
enabled: true,
|
||||||
|
),
|
||||||
|
Erc20Token(
|
||||||
|
name: "Dai",
|
||||||
|
symbol: "DAI",
|
||||||
|
contractAddress: "0x6B175474E89094C44Da98b954EedeAC495271d0F",
|
||||||
|
decimal: 18,
|
||||||
|
enabled: true,
|
||||||
|
),
|
||||||
|
Erc20Token(
|
||||||
|
name: "Wrapped Ether",
|
||||||
|
symbol: "WETH",
|
||||||
|
contractAddress: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
|
||||||
|
decimal: 18,
|
||||||
|
enabled: false,
|
||||||
|
),
|
||||||
|
Erc20Token(
|
||||||
|
name: "Pepe",
|
||||||
|
symbol: "PEPE",
|
||||||
|
contractAddress: "0x6982508145454ce325ddbe47a25d4ec3d2311933",
|
||||||
|
decimal: 18,
|
||||||
|
enabled: false,
|
||||||
|
),
|
||||||
|
Erc20Token(
|
||||||
|
name: "SHIBA INU",
|
||||||
|
symbol: "SHIB",
|
||||||
|
contractAddress: "0x95ad61b0a150d79219dcf64e1e6cc01f0b64c4ce",
|
||||||
|
decimal: 18,
|
||||||
|
enabled: false,
|
||||||
|
),
|
||||||
|
Erc20Token(
|
||||||
|
name: "ApeCoin",
|
||||||
|
symbol: "APE",
|
||||||
|
contractAddress: "0x4d224452801aced8b2f0aebe155379bb5d594381",
|
||||||
|
decimal: 18,
|
||||||
|
enabled: false,
|
||||||
|
),
|
||||||
|
Erc20Token(
|
||||||
|
name: "Matic Token",
|
||||||
|
symbol: "MATIC",
|
||||||
|
contractAddress: "0x7D1AfA7B718fb893dB30A3aBc0Cfc608AaCfeBB0",
|
||||||
|
decimal: 18,
|
||||||
|
enabled: false,
|
||||||
|
),
|
||||||
|
Erc20Token(
|
||||||
|
name: "Wrapped BTC",
|
||||||
|
symbol: "WBTC",
|
||||||
|
contractAddress: "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599",
|
||||||
|
decimal: 8,
|
||||||
|
enabled: false,
|
||||||
|
),
|
||||||
|
Erc20Token(
|
||||||
|
name: "Gitcoin",
|
||||||
|
symbol: "GTC",
|
||||||
|
contractAddress: "0xde30da39c46104798bb5aa3fe8b9e0e1f348163f",
|
||||||
|
decimal: 18,
|
||||||
|
enabled: false,
|
||||||
|
),
|
||||||
|
Erc20Token(
|
||||||
|
name: "Compound",
|
||||||
|
symbol: "COMP",
|
||||||
|
contractAddress: "0xc00e94cb662c3520282e6f5717214004a7f26888",
|
||||||
|
decimal: 18,
|
||||||
|
enabled: false,
|
||||||
|
),
|
||||||
|
Erc20Token(
|
||||||
|
name: "Aave Token",
|
||||||
|
symbol: "AAVE",
|
||||||
|
contractAddress: "0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9",
|
||||||
|
decimal: 18,
|
||||||
|
enabled: false,
|
||||||
|
),
|
||||||
|
Erc20Token(
|
||||||
|
name: "Uniswap",
|
||||||
|
symbol: "UNI",
|
||||||
|
contractAddress: "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984",
|
||||||
|
decimal: 18,
|
||||||
|
enabled: false,
|
||||||
|
),
|
||||||
|
Erc20Token(
|
||||||
|
name: "Decentraland",
|
||||||
|
symbol: "MANA",
|
||||||
|
contractAddress: "0x0F5D2fB29fb7d3CFeE444a200298f468908cC942",
|
||||||
|
decimal: 18,
|
||||||
|
enabled: false,
|
||||||
|
),
|
||||||
|
Erc20Token(
|
||||||
|
name: "Storj",
|
||||||
|
symbol: "STORJ",
|
||||||
|
contractAddress: "0xb64ef51c888972c908cfacf59b47c1afbc0ab8ac",
|
||||||
|
decimal: 8,
|
||||||
|
enabled: false,
|
||||||
|
),
|
||||||
|
Erc20Token(
|
||||||
|
name: "Maker",
|
||||||
|
symbol: "MKR",
|
||||||
|
contractAddress: "0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2",
|
||||||
|
decimal: 18,
|
||||||
|
enabled: false,
|
||||||
|
),
|
||||||
|
Erc20Token(
|
||||||
|
name: "Orchid",
|
||||||
|
symbol: "OXT",
|
||||||
|
contractAddress: "0x4575f41308EC1483f3d399aa9a2826d74Da13Deb",
|
||||||
|
decimal: 18,
|
||||||
|
enabled: false,
|
||||||
|
),
|
||||||
|
Erc20Token(
|
||||||
|
name: "Paxos Gold",
|
||||||
|
symbol: "PAXG",
|
||||||
|
contractAddress: "0x45804880De22913dAFE09f4980848ECE6EcbAf78",
|
||||||
|
decimal: 18,
|
||||||
|
enabled: false,
|
||||||
|
),
|
||||||
|
Erc20Token(
|
||||||
|
name: "Binance Coin",
|
||||||
|
symbol: "BNB",
|
||||||
|
contractAddress: "0xB8c77482e45F1F44dE1745F52C74426C631bDD52",
|
||||||
|
decimal: 18,
|
||||||
|
enabled: false,
|
||||||
|
),
|
||||||
|
Erc20Token(
|
||||||
|
name: "stETH",
|
||||||
|
symbol: "stETH",
|
||||||
|
contractAddress: "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84",
|
||||||
|
decimal: 18,
|
||||||
|
enabled: false,
|
||||||
|
),
|
||||||
|
Erc20Token(
|
||||||
|
name: "Lido DAO",
|
||||||
|
symbol: "LDO",
|
||||||
|
contractAddress: "0x5A98FcBEA516Cf06857215779Fd812CA3beF1B32",
|
||||||
|
decimal: 18,
|
||||||
|
enabled: false,
|
||||||
|
),
|
||||||
|
Erc20Token(
|
||||||
|
name: "Arbitrum",
|
||||||
|
symbol: "ARB",
|
||||||
|
contractAddress: "0xB50721BCf8d664c30412Cfbc6cf7a15145234ad1",
|
||||||
|
decimal: 18,
|
||||||
|
enabled: false,
|
||||||
|
),
|
||||||
|
Erc20Token(
|
||||||
|
name: "Graph Token",
|
||||||
|
symbol: "GRT",
|
||||||
|
contractAddress: "0xc944E90C64B2c07662A292be6244BDf05Cda44a7",
|
||||||
|
decimal: 18,
|
||||||
|
enabled: false,
|
||||||
|
),
|
||||||
|
Erc20Token(
|
||||||
|
name: "Frax",
|
||||||
|
symbol: "FRAX",
|
||||||
|
contractAddress: "0x853d955aCEf822Db058eb8505911ED77F175b99e",
|
||||||
|
decimal: 18,
|
||||||
|
enabled: false,
|
||||||
|
),
|
||||||
|
Erc20Token(
|
||||||
|
name: "Gemini dollar",
|
||||||
|
symbol: "GUSD",
|
||||||
|
contractAddress: "0x056Fd409E1d7A124BD7017459dFEa2F387b6d5Cd",
|
||||||
|
decimal: 2,
|
||||||
|
enabled: false,
|
||||||
|
),
|
||||||
|
Erc20Token(
|
||||||
|
name: "Compound Ether",
|
||||||
|
symbol: "cETH",
|
||||||
|
contractAddress: "0x4Ddc2D193948926D02f9B1fE9e1daa0718270ED5",
|
||||||
|
decimal: 8,
|
||||||
|
enabled: false,
|
||||||
|
),
|
||||||
|
Erc20Token(
|
||||||
|
name: "Binance USD",
|
||||||
|
symbol: "BUSD",
|
||||||
|
contractAddress: "0x4Fabb145d64652a948d72533023f6E7A623C7C53",
|
||||||
|
decimal: 18,
|
||||||
|
enabled: false,
|
||||||
|
),
|
||||||
|
Erc20Token(
|
||||||
|
name: "TrueUSD",
|
||||||
|
symbol: "TUSD",
|
||||||
|
contractAddress: "0x0000000000085d4780B73119b644AE5ecd22b376",
|
||||||
|
decimal: 18,
|
||||||
|
enabled: false,
|
||||||
|
),
|
||||||
|
Erc20Token(
|
||||||
|
name: "Cronos Coin",
|
||||||
|
symbol: "CRO",
|
||||||
|
contractAddress: "0xA0b73E1Ff0B80914AB6fe0444E65848C4C34450b",
|
||||||
|
decimal: 8,
|
||||||
|
enabled: false,
|
||||||
|
),
|
||||||
|
Erc20Token(
|
||||||
|
name: "Pax Dollar",
|
||||||
|
symbol: "USDP",
|
||||||
|
contractAddress: "0x8E870D67F660D95d5be530380D0eC0bd388289E1",
|
||||||
|
decimal: 18,
|
||||||
|
enabled: false,
|
||||||
|
),
|
||||||
|
Erc20Token(
|
||||||
|
name: "Fantom Token",
|
||||||
|
symbol: "FTM",
|
||||||
|
contractAddress: "0x4E15361FD6b4BB609Fa63C81A2be19d873717870",
|
||||||
|
decimal: 18,
|
||||||
|
enabled: false,
|
||||||
|
),
|
||||||
|
Erc20Token(
|
||||||
|
name: "BitTorrent",
|
||||||
|
symbol: "BTT",
|
||||||
|
contractAddress: "0xC669928185DbCE49d2230CC9B0979BE6DC797957",
|
||||||
|
decimal: 18,
|
||||||
|
enabled: false,
|
||||||
|
),
|
||||||
|
Erc20Token(
|
||||||
|
name: "Nexo",
|
||||||
|
symbol: "NEXO",
|
||||||
|
contractAddress: "0xB62132e35a6c13ee1EE0f84dC5d40bad8d815206",
|
||||||
|
decimal: 18,
|
||||||
|
enabled: false,
|
||||||
|
),
|
||||||
|
Erc20Token(
|
||||||
|
name: "dYdX",
|
||||||
|
symbol: "DYDX",
|
||||||
|
contractAddress: "0x92D6C1e31e14520e676a687F0a93788B716BEff5",
|
||||||
|
decimal: 18,
|
||||||
|
enabled: false,
|
||||||
|
),
|
||||||
|
Erc20Token(
|
||||||
|
name: "PancakeSwap Token",
|
||||||
|
symbol: "Cake",
|
||||||
|
contractAddress: "0x152649eA73beAb28c5b49B26eb48f7EAD6d4c898",
|
||||||
|
decimal: 18,
|
||||||
|
enabled: false,
|
||||||
|
),
|
||||||
|
Erc20Token(
|
||||||
|
name: "BAT",
|
||||||
|
symbol: "BAT",
|
||||||
|
contractAddress: "0x0D8775F648430679A709E98d2b0Cb6250d2887EF",
|
||||||
|
decimal: 18,
|
||||||
|
enabled: false,
|
||||||
|
),
|
||||||
|
Erc20Token(
|
||||||
|
name: "1INCH Token",
|
||||||
|
symbol: "1INCH",
|
||||||
|
contractAddress: "0x111111111117dC0aa78b770fA6A738034120C302",
|
||||||
|
decimal: 18,
|
||||||
|
enabled: false,
|
||||||
|
),
|
||||||
|
Erc20Token(
|
||||||
|
name: "Ethereum Name Service",
|
||||||
|
symbol: "ENS",
|
||||||
|
contractAddress: "0xC18360217D8F7Ab5e7c516566761Ea12Ce7F9D72",
|
||||||
|
decimal: 18,
|
||||||
|
enabled: false,
|
||||||
|
),
|
||||||
|
Erc20Token(
|
||||||
|
name: "ZRX",
|
||||||
|
symbol: "ZRX",
|
||||||
|
contractAddress: "0xE41d2489571d322189246DaFA5ebDe1F4699F498",
|
||||||
|
decimal: 18,
|
||||||
|
enabled: false,
|
||||||
|
),
|
||||||
|
Erc20Token(
|
||||||
|
name: "Verse",
|
||||||
|
symbol: "VERSE",
|
||||||
|
contractAddress: "0x249cA82617eC3DfB2589c4c17ab7EC9765350a18",
|
||||||
|
decimal: 18,
|
||||||
|
enabled: false,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
List<Erc20Token> get initialErc20Tokens => _defaultTokens.map((token) {
|
||||||
|
String? iconPath;
|
||||||
|
try {
|
||||||
|
iconPath = CryptoCurrency.all
|
||||||
|
.firstWhere((element) => element.title.toUpperCase() == token.symbol.toUpperCase())
|
||||||
|
.iconPath;
|
||||||
|
} catch (_) {}
|
||||||
|
|
||||||
|
if (iconPath != null) {
|
||||||
|
return Erc20Token.copyWith(token, iconPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
return token;
|
||||||
|
}).toList();
|
||||||
|
}
|
47
cw_ethereum/lib/erc20_balance.dart
Normal file
47
cw_ethereum/lib/erc20_balance.dart
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:cw_core/balance.dart';
|
||||||
|
|
||||||
|
class ERC20Balance extends Balance {
|
||||||
|
ERC20Balance(this.balance, {this.exponent = 18})
|
||||||
|
: super(balance.toInt(),
|
||||||
|
balance.toInt());
|
||||||
|
|
||||||
|
final BigInt balance;
|
||||||
|
final int exponent;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get formattedAdditionalBalance {
|
||||||
|
final String formattedBalance = (balance / BigInt.from(10).pow(exponent)).toString();
|
||||||
|
return formattedBalance.substring(0, min(12, formattedBalance.length));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get formattedAvailableBalance {
|
||||||
|
final String formattedBalance = (balance / BigInt.from(10).pow(exponent)).toString();
|
||||||
|
return formattedBalance.substring(0, min(12, formattedBalance.length));
|
||||||
|
}
|
||||||
|
|
||||||
|
String toJSON() => json.encode({
|
||||||
|
'balanceInWei': balance.toString(),
|
||||||
|
'exponent': exponent,
|
||||||
|
});
|
||||||
|
|
||||||
|
static ERC20Balance? fromJSON(String? jsonSource) {
|
||||||
|
if (jsonSource == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final decoded = json.decode(jsonSource) as Map;
|
||||||
|
|
||||||
|
try {
|
||||||
|
return ERC20Balance(
|
||||||
|
BigInt.parse(decoded['balanceInWei']),
|
||||||
|
exponent: decoded['exponent'],
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
return ERC20Balance(BigInt.zero);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
230
cw_ethereum/lib/ethereum_client.dart
Normal file
230
cw_ethereum/lib/ethereum_client.dart
Normal file
|
@ -0,0 +1,230 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
|
import 'package:cw_ethereum/erc20_balance.dart';
|
||||||
|
import 'package:cw_core/erc20_token.dart';
|
||||||
|
import 'package:cw_ethereum/ethereum_transaction_model.dart';
|
||||||
|
import 'package:cw_ethereum/pending_ethereum_transaction.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:http/http.dart';
|
||||||
|
import 'package:web3dart/web3dart.dart';
|
||||||
|
import 'package:web3dart/contracts/erc20.dart';
|
||||||
|
import 'package:cw_core/node.dart';
|
||||||
|
import 'package:cw_ethereum/ethereum_transaction_priority.dart';
|
||||||
|
import 'package:cw_ethereum/.secrets.g.dart' as secrets;
|
||||||
|
|
||||||
|
class EthereumClient {
|
||||||
|
final _httpClient = Client();
|
||||||
|
Web3Client? _client;
|
||||||
|
|
||||||
|
bool connect(Node node) {
|
||||||
|
try {
|
||||||
|
_client = Web3Client(node.uri.toString(), _httpClient);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setListeners(EthereumAddress userAddress, Function() onNewTransaction) async {
|
||||||
|
// _client?.pendingTransactions().listen((transactionHash) async {
|
||||||
|
// final transaction = await _client!.getTransactionByHash(transactionHash);
|
||||||
|
//
|
||||||
|
// if (transaction.from.hex == userAddress || transaction.to?.hex == userAddress) {
|
||||||
|
// onNewTransaction();
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<EtherAmount> getBalance(EthereumAddress address) async =>
|
||||||
|
await _client!.getBalance(address);
|
||||||
|
|
||||||
|
Future<int> getGasUnitPrice() async {
|
||||||
|
final gasPrice = await _client!.getGasPrice();
|
||||||
|
return gasPrice.getInWei.toInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<int> getEstimatedGas() async {
|
||||||
|
final estimatedGas = await _client!.estimateGas();
|
||||||
|
return estimatedGas.toInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<PendingEthereumTransaction> signTransaction({
|
||||||
|
required EthPrivateKey privateKey,
|
||||||
|
required String toAddress,
|
||||||
|
required String amount,
|
||||||
|
required int gas,
|
||||||
|
required EthereumTransactionPriority priority,
|
||||||
|
required CryptoCurrency currency,
|
||||||
|
required int exponent,
|
||||||
|
String? contractAddress,
|
||||||
|
}) async {
|
||||||
|
assert(currency == CryptoCurrency.eth || contractAddress != null);
|
||||||
|
|
||||||
|
bool _isEthereum = currency == CryptoCurrency.eth;
|
||||||
|
|
||||||
|
final price = await _client!.getGasPrice();
|
||||||
|
|
||||||
|
final Transaction transaction = Transaction(
|
||||||
|
from: privateKey.address,
|
||||||
|
to: EthereumAddress.fromHex(toAddress),
|
||||||
|
maxGas: gas,
|
||||||
|
gasPrice: price,
|
||||||
|
maxPriorityFeePerGas: EtherAmount.fromUnitAndValue(EtherUnit.gwei, priority.tip),
|
||||||
|
value: _isEthereum ? EtherAmount.inWei(BigInt.parse(amount)) : EtherAmount.zero(),
|
||||||
|
);
|
||||||
|
|
||||||
|
final signedTransaction = await _client!.signTransaction(privateKey, transaction);
|
||||||
|
|
||||||
|
final Function _sendTransaction;
|
||||||
|
|
||||||
|
if (_isEthereum) {
|
||||||
|
_sendTransaction = () async => await sendTransaction(signedTransaction);
|
||||||
|
} else {
|
||||||
|
final erc20 = Erc20(
|
||||||
|
client: _client!,
|
||||||
|
address: EthereumAddress.fromHex(contractAddress!),
|
||||||
|
);
|
||||||
|
|
||||||
|
_sendTransaction = () async {
|
||||||
|
await erc20.transfer(
|
||||||
|
EthereumAddress.fromHex(toAddress),
|
||||||
|
BigInt.parse(amount),
|
||||||
|
credentials: privateKey,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return PendingEthereumTransaction(
|
||||||
|
signedTransaction: signedTransaction,
|
||||||
|
amount: amount,
|
||||||
|
fee: BigInt.from(gas) * price.getInWei,
|
||||||
|
sendTransaction: _sendTransaction,
|
||||||
|
exponent: exponent,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> sendTransaction(Uint8List signedTransaction) async =>
|
||||||
|
await _client!.sendRawTransaction(signedTransaction);
|
||||||
|
|
||||||
|
Future getTransactionDetails(String transactionHash) async {
|
||||||
|
// Wait for the transaction receipt to become available
|
||||||
|
TransactionReceipt? receipt;
|
||||||
|
while (receipt == null) {
|
||||||
|
receipt = await _client!.getTransactionReceipt(transactionHash);
|
||||||
|
await Future.delayed(Duration(seconds: 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print the receipt information
|
||||||
|
print('Transaction Hash: ${receipt.transactionHash}');
|
||||||
|
print('Block Hash: ${receipt.blockHash}');
|
||||||
|
print('Block Number: ${receipt.blockNumber}');
|
||||||
|
print('Gas Used: ${receipt.gasUsed}');
|
||||||
|
|
||||||
|
/*
|
||||||
|
Transaction Hash: [112, 244, 4, 238, 89, 199, 171, 191, 210, 236, 110, 42, 185, 202, 220, 21, 27, 132, 123, 221, 137, 90, 77, 13, 23, 43, 12, 230, 93, 63, 221, 116]
|
||||||
|
I/flutter ( 4474): Block Hash: [149, 44, 250, 119, 111, 104, 82, 98, 17, 89, 30, 190, 25, 44, 218, 118, 127, 189, 241, 35, 213, 106, 25, 95, 195, 37, 55, 131, 185, 180, 246, 200]
|
||||||
|
I/flutter ( 4474): Block Number: 17120242
|
||||||
|
I/flutter ( 4474): Gas Used: 21000
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Wait for the transaction receipt to become available
|
||||||
|
TransactionInformation? transactionInformation;
|
||||||
|
while (transactionInformation == null) {
|
||||||
|
print("********************************");
|
||||||
|
transactionInformation = await _client!.getTransactionByHash(transactionHash);
|
||||||
|
await Future.delayed(Duration(seconds: 1));
|
||||||
|
}
|
||||||
|
// Print the receipt information
|
||||||
|
print('Transaction Hash: ${transactionInformation.hash}');
|
||||||
|
print('Block Hash: ${transactionInformation.blockHash}');
|
||||||
|
print('Block Number: ${transactionInformation.blockNumber}');
|
||||||
|
print('Gas Used: ${transactionInformation.gas}');
|
||||||
|
|
||||||
|
/*
|
||||||
|
Transaction Hash: 0x70f404ee59c7abbfd2ec6e2ab9cadc151b847bdd895a4d0d172b0ce65d3fdd74
|
||||||
|
I/flutter ( 4474): Block Hash: 0x952cfa776f68526211591ebe192cda767fbdf123d56a195fc3253783b9b4f6c8
|
||||||
|
I/flutter ( 4474): Block Number: 17120242
|
||||||
|
I/flutter ( 4474): Gas Used: 53000
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<ERC20Balance> fetchERC20Balances(
|
||||||
|
EthereumAddress userAddress, String contractAddress) async {
|
||||||
|
final erc20 = Erc20(address: EthereumAddress.fromHex(contractAddress), client: _client!);
|
||||||
|
final balance = await erc20.balanceOf(userAddress);
|
||||||
|
|
||||||
|
int exponent = (await erc20.decimals()).toInt();
|
||||||
|
|
||||||
|
return ERC20Balance(balance, exponent: exponent);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Erc20Token?> getErc20Token(String contractAddress) async {
|
||||||
|
try {
|
||||||
|
final erc20 = Erc20(address: EthereumAddress.fromHex(contractAddress), client: _client!);
|
||||||
|
final name = await erc20.name();
|
||||||
|
final symbol = await erc20.symbol();
|
||||||
|
final decimal = await erc20.decimals();
|
||||||
|
|
||||||
|
return Erc20Token(
|
||||||
|
name: name,
|
||||||
|
symbol: symbol,
|
||||||
|
contractAddress: contractAddress,
|
||||||
|
decimal: decimal.toInt(),
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void stop() {
|
||||||
|
_client?.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<EthereumTransactionModel>> fetchTransactions(String address,
|
||||||
|
{String? contractAddress}) async {
|
||||||
|
try {
|
||||||
|
final response = await _httpClient.get(Uri.https("api.etherscan.io", "/api", {
|
||||||
|
"module": "account",
|
||||||
|
"action": contractAddress != null ? "tokentx" : "txlist",
|
||||||
|
if (contractAddress != null) "contractaddress": contractAddress,
|
||||||
|
"address": address,
|
||||||
|
"apikey": secrets.etherScanApiKey,
|
||||||
|
}));
|
||||||
|
|
||||||
|
final _jsonResponse = json.decode(response.body) as Map<String, dynamic>;
|
||||||
|
|
||||||
|
if (response.statusCode >= 200 && response.statusCode < 300 && _jsonResponse['status'] != 0) {
|
||||||
|
return (_jsonResponse['result'] as List)
|
||||||
|
.map((e) => EthereumTransactionModel.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
} catch (e) {
|
||||||
|
print(e);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Future<int> _getDecimalPlacesForContract(DeployedContract contract) async {
|
||||||
|
// final String abi = await rootBundle.loadString("assets/abi_json/erc20_abi.json");
|
||||||
|
// final contractAbi = ContractAbi.fromJson(abi, "ERC20");
|
||||||
|
//
|
||||||
|
// final contract = DeployedContract(
|
||||||
|
// contractAbi,
|
||||||
|
// EthereumAddress.fromHex(_erc20Currencies[erc20Currency]!),
|
||||||
|
// );
|
||||||
|
// final decimalsFunction = contract.function('decimals');
|
||||||
|
// final decimals = await _client!.call(
|
||||||
|
// contract: contract,
|
||||||
|
// function: decimalsFunction,
|
||||||
|
// params: [],
|
||||||
|
// );
|
||||||
|
//
|
||||||
|
// int exponent = int.parse(decimals.first.toString());
|
||||||
|
// return exponent;
|
||||||
|
// }
|
||||||
|
}
|
11
cw_ethereum/lib/ethereum_exceptions.dart
Normal file
11
cw_ethereum/lib/ethereum_exceptions.dart
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
|
|
||||||
|
class EthereumTransactionCreationException implements Exception {
|
||||||
|
final String exceptionMessage;
|
||||||
|
|
||||||
|
EthereumTransactionCreationException(CryptoCurrency currency) :
|
||||||
|
this.exceptionMessage = 'Wrong balance. Not enough ${currency.title} on your balance.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => exceptionMessage;
|
||||||
|
}
|
25
cw_ethereum/lib/ethereum_formatter.dart
Normal file
25
cw_ethereum/lib/ethereum_formatter.dart
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
|
const ethereumAmountLength = 12;
|
||||||
|
const ethereumAmountDivider = 1000000000000;
|
||||||
|
final ethereumAmountFormat = NumberFormat()
|
||||||
|
..maximumFractionDigits = ethereumAmountLength
|
||||||
|
..minimumFractionDigits = 1;
|
||||||
|
|
||||||
|
class EthereumFormatter {
|
||||||
|
static int parseEthereumAmount(String amount) {
|
||||||
|
try {
|
||||||
|
return (double.parse(amount) * ethereumAmountDivider).round();
|
||||||
|
} catch (_) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static double parseEthereumAmountToDouble(int amount) {
|
||||||
|
try {
|
||||||
|
return amount / ethereumAmountDivider;
|
||||||
|
} catch (_) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
2058
cw_ethereum/lib/ethereum_mnemonics.dart
Normal file
2058
cw_ethereum/lib/ethereum_mnemonics.dart
Normal file
File diff suppressed because it is too large
Load diff
17
cw_ethereum/lib/ethereum_transaction_credentials.dart
Normal file
17
cw_ethereum/lib/ethereum_transaction_credentials.dart
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
|
import 'package:cw_core/output_info.dart';
|
||||||
|
import 'package:cw_ethereum/ethereum_transaction_priority.dart';
|
||||||
|
|
||||||
|
class EthereumTransactionCredentials {
|
||||||
|
EthereumTransactionCredentials(
|
||||||
|
this.outputs, {
|
||||||
|
required this.priority,
|
||||||
|
required this.currency,
|
||||||
|
this.feeRate,
|
||||||
|
});
|
||||||
|
|
||||||
|
final List<OutputInfo> outputs;
|
||||||
|
final EthereumTransactionPriority? priority;
|
||||||
|
final int? feeRate;
|
||||||
|
final CryptoCurrency currency;
|
||||||
|
}
|
77
cw_ethereum/lib/ethereum_transaction_history.dart
Normal file
77
cw_ethereum/lib/ethereum_transaction_history.dart
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:core';
|
||||||
|
import 'package:cw_core/pathForWallet.dart';
|
||||||
|
import 'package:cw_core/wallet_info.dart';
|
||||||
|
import 'package:cw_ethereum/file.dart';
|
||||||
|
import 'package:mobx/mobx.dart';
|
||||||
|
import 'package:cw_core/transaction_history.dart';
|
||||||
|
import 'package:cw_ethereum/ethereum_transaction_info.dart';
|
||||||
|
|
||||||
|
part 'ethereum_transaction_history.g.dart';
|
||||||
|
|
||||||
|
const transactionsHistoryFileName = 'transactions.json';
|
||||||
|
|
||||||
|
class EthereumTransactionHistory = EthereumTransactionHistoryBase with _$EthereumTransactionHistory;
|
||||||
|
|
||||||
|
abstract class EthereumTransactionHistoryBase
|
||||||
|
extends TransactionHistoryBase<EthereumTransactionInfo> with Store {
|
||||||
|
EthereumTransactionHistoryBase({required this.walletInfo, required String password})
|
||||||
|
: _password = password {
|
||||||
|
transactions = ObservableMap<String, EthereumTransactionInfo>();
|
||||||
|
}
|
||||||
|
|
||||||
|
final WalletInfo walletInfo;
|
||||||
|
String _password;
|
||||||
|
|
||||||
|
Future<void> init() async => await _load();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> save() async {
|
||||||
|
try {
|
||||||
|
final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
|
||||||
|
final path = '$dirPath/$transactionsHistoryFileName';
|
||||||
|
final data = json.encode({'transactions': transactions});
|
||||||
|
await writeData(path: path, password: _password, data: data);
|
||||||
|
} catch (e, s) {
|
||||||
|
print('Error while save ethereum transaction history: ${e.toString()}');
|
||||||
|
print(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void addOne(EthereumTransactionInfo transaction) => transactions[transaction.id] = transaction;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void addMany(Map<String, EthereumTransactionInfo> transactions) =>
|
||||||
|
this.transactions.addAll(transactions);
|
||||||
|
|
||||||
|
Future<Map<String, dynamic>> _read() async {
|
||||||
|
final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
|
||||||
|
final path = '$dirPath/$transactionsHistoryFileName';
|
||||||
|
final content = await read(path: path, password: _password);
|
||||||
|
if (content.isEmpty) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return json.decode(content) as Map<String, dynamic>;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _load() async {
|
||||||
|
try {
|
||||||
|
final content = await _read();
|
||||||
|
final txs = content['transactions'] as Map<String, dynamic>? ?? {};
|
||||||
|
|
||||||
|
txs.entries.forEach((entry) {
|
||||||
|
final val = entry.value;
|
||||||
|
|
||||||
|
if (val is Map<String, dynamic>) {
|
||||||
|
final tx = EthereumTransactionInfo.fromJson(val);
|
||||||
|
_update(tx);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _update(EthereumTransactionInfo transaction) => transactions[transaction.id] = transaction;
|
||||||
|
}
|
74
cw_ethereum/lib/ethereum_transaction_info.dart
Normal file
74
cw_ethereum/lib/ethereum_transaction_info.dart
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
import 'package:cw_core/format_amount.dart';
|
||||||
|
import 'package:cw_core/transaction_direction.dart';
|
||||||
|
import 'package:cw_core/transaction_info.dart';
|
||||||
|
|
||||||
|
class EthereumTransactionInfo extends TransactionInfo {
|
||||||
|
EthereumTransactionInfo({
|
||||||
|
required this.id,
|
||||||
|
required this.height,
|
||||||
|
required this.ethAmount,
|
||||||
|
required this.ethFee,
|
||||||
|
this.tokenSymbol = "ETH",
|
||||||
|
this.exponent = 18,
|
||||||
|
required this.direction,
|
||||||
|
required this.isPending,
|
||||||
|
required this.date,
|
||||||
|
required this.confirmations,
|
||||||
|
}) : this.amount = ethAmount.toInt(),
|
||||||
|
this.fee = ethFee.toInt();
|
||||||
|
|
||||||
|
final String id;
|
||||||
|
final int height;
|
||||||
|
final int amount;
|
||||||
|
final BigInt ethAmount;
|
||||||
|
final int exponent;
|
||||||
|
final TransactionDirection direction;
|
||||||
|
final DateTime date;
|
||||||
|
final bool isPending;
|
||||||
|
final int fee;
|
||||||
|
final BigInt ethFee;
|
||||||
|
final int confirmations;
|
||||||
|
final String tokenSymbol;
|
||||||
|
String? _fiatAmount;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String amountFormatted() =>
|
||||||
|
'${formatAmount((ethAmount / BigInt.from(10).pow(exponent)).toString())} $tokenSymbol';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String fiatAmount() => _fiatAmount ?? '';
|
||||||
|
|
||||||
|
@override
|
||||||
|
void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String feeFormatted() => '${(ethFee / BigInt.from(10).pow(18)).toString()} ETH';
|
||||||
|
|
||||||
|
factory EthereumTransactionInfo.fromJson(Map<String, dynamic> data) {
|
||||||
|
return EthereumTransactionInfo(
|
||||||
|
id: data['id'] as String,
|
||||||
|
height: data['height'] as int,
|
||||||
|
ethAmount: BigInt.parse(data['amount']),
|
||||||
|
exponent: data['exponent'] as int,
|
||||||
|
ethFee: BigInt.parse(data['fee']),
|
||||||
|
direction: parseTransactionDirectionFromInt(data['direction'] as int),
|
||||||
|
date: DateTime.fromMillisecondsSinceEpoch(data['date'] as int),
|
||||||
|
isPending: data['isPending'] as bool,
|
||||||
|
confirmations: data['confirmations'] as int,
|
||||||
|
tokenSymbol: data['tokenSymbol'] as String,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
'id': id,
|
||||||
|
'height': height,
|
||||||
|
'amount': ethAmount.toString(),
|
||||||
|
'exponent': exponent,
|
||||||
|
'fee': ethFee.toString(),
|
||||||
|
'direction': direction.index,
|
||||||
|
'date': date.millisecondsSinceEpoch,
|
||||||
|
'isPending': isPending,
|
||||||
|
'confirmations': confirmations,
|
||||||
|
'tokenSymbol': tokenSymbol,
|
||||||
|
};
|
||||||
|
}
|
47
cw_ethereum/lib/ethereum_transaction_model.dart
Normal file
47
cw_ethereum/lib/ethereum_transaction_model.dart
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
class EthereumTransactionModel {
|
||||||
|
final DateTime date;
|
||||||
|
final String hash;
|
||||||
|
final String from;
|
||||||
|
final String to;
|
||||||
|
final BigInt amount;
|
||||||
|
final int gasUsed;
|
||||||
|
final BigInt gasPrice;
|
||||||
|
final String contractAddress;
|
||||||
|
final int confirmations;
|
||||||
|
final int blockNumber;
|
||||||
|
final String? tokenSymbol;
|
||||||
|
final int? tokenDecimal;
|
||||||
|
final bool isError;
|
||||||
|
|
||||||
|
EthereumTransactionModel({
|
||||||
|
required this.date,
|
||||||
|
required this.hash,
|
||||||
|
required this.from,
|
||||||
|
required this.to,
|
||||||
|
required this.amount,
|
||||||
|
required this.gasUsed,
|
||||||
|
required this.gasPrice,
|
||||||
|
required this.contractAddress,
|
||||||
|
required this.confirmations,
|
||||||
|
required this.blockNumber,
|
||||||
|
required this.tokenSymbol,
|
||||||
|
required this.tokenDecimal,
|
||||||
|
required this.isError,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory EthereumTransactionModel.fromJson(Map<String, dynamic> json) => EthereumTransactionModel(
|
||||||
|
date: DateTime.fromMillisecondsSinceEpoch(int.parse(json["timeStamp"]) * 1000),
|
||||||
|
hash: json["hash"],
|
||||||
|
from: json["from"],
|
||||||
|
to: json["to"],
|
||||||
|
amount: BigInt.parse(json["value"]),
|
||||||
|
gasUsed: int.parse(json["gasUsed"]),
|
||||||
|
gasPrice: BigInt.parse(json["gasPrice"]),
|
||||||
|
contractAddress: json["contractAddress"],
|
||||||
|
confirmations: int.parse(json["confirmations"]),
|
||||||
|
blockNumber: int.parse(json["blockNumber"]),
|
||||||
|
tokenSymbol: json["tokenSymbol"] ?? "ETH",
|
||||||
|
tokenDecimal: int.tryParse(json["tokenDecimal"] ?? ""),
|
||||||
|
isError: json["isError"] == "1",
|
||||||
|
);
|
||||||
|
}
|
52
cw_ethereum/lib/ethereum_transaction_priority.dart
Normal file
52
cw_ethereum/lib/ethereum_transaction_priority.dart
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
import 'package:cw_core/transaction_priority.dart';
|
||||||
|
|
||||||
|
class EthereumTransactionPriority extends TransactionPriority {
|
||||||
|
final int tip;
|
||||||
|
|
||||||
|
const EthereumTransactionPriority({required String title, required int raw, required this.tip})
|
||||||
|
: super(title: title, raw: raw);
|
||||||
|
|
||||||
|
static const List<EthereumTransactionPriority> all = [fast, medium, slow];
|
||||||
|
static const EthereumTransactionPriority slow =
|
||||||
|
EthereumTransactionPriority(title: 'slow', raw: 0, tip: 1);
|
||||||
|
static const EthereumTransactionPriority medium =
|
||||||
|
EthereumTransactionPriority(title: 'Medium', raw: 1, tip: 2);
|
||||||
|
static const EthereumTransactionPriority fast =
|
||||||
|
EthereumTransactionPriority(title: 'Fast', raw: 2, tip: 4);
|
||||||
|
|
||||||
|
static EthereumTransactionPriority deserialize({required int raw}) {
|
||||||
|
switch (raw) {
|
||||||
|
case 0:
|
||||||
|
return slow;
|
||||||
|
case 1:
|
||||||
|
return medium;
|
||||||
|
case 2:
|
||||||
|
return fast;
|
||||||
|
default:
|
||||||
|
throw Exception('Unexpected token: $raw for EthereumTransactionPriority deserialize');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String get units => 'gas';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
var label = '';
|
||||||
|
|
||||||
|
switch (this) {
|
||||||
|
case EthereumTransactionPriority.slow:
|
||||||
|
label = 'Slow';
|
||||||
|
break;
|
||||||
|
case EthereumTransactionPriority.medium:
|
||||||
|
label = 'Medium';
|
||||||
|
break;
|
||||||
|
case EthereumTransactionPriority.fast:
|
||||||
|
label = 'Fast';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
}
|
473
cw_ethereum/lib/ethereum_wallet.dart
Normal file
473
cw_ethereum/lib/ethereum_wallet.dart
Normal file
|
@ -0,0 +1,473 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
|
import 'package:cw_core/node.dart';
|
||||||
|
import 'package:cw_core/pathForWallet.dart';
|
||||||
|
import 'package:cw_core/pending_transaction.dart';
|
||||||
|
import 'package:cw_core/sync_status.dart';
|
||||||
|
import 'package:cw_core/transaction_direction.dart';
|
||||||
|
import 'package:cw_core/transaction_priority.dart';
|
||||||
|
import 'package:cw_core/wallet_addresses.dart';
|
||||||
|
import 'package:cw_core/wallet_base.dart';
|
||||||
|
import 'package:cw_core/wallet_info.dart';
|
||||||
|
import 'package:cw_ethereum/default_erc20_tokens.dart';
|
||||||
|
import 'package:cw_ethereum/erc20_balance.dart';
|
||||||
|
import 'package:cw_ethereum/ethereum_client.dart';
|
||||||
|
import 'package:cw_ethereum/ethereum_exceptions.dart';
|
||||||
|
import 'package:cw_ethereum/ethereum_formatter.dart';
|
||||||
|
import 'package:cw_ethereum/ethereum_transaction_credentials.dart';
|
||||||
|
import 'package:cw_ethereum/ethereum_transaction_history.dart';
|
||||||
|
import 'package:cw_ethereum/ethereum_transaction_info.dart';
|
||||||
|
import 'package:cw_ethereum/ethereum_transaction_model.dart';
|
||||||
|
import 'package:cw_ethereum/ethereum_transaction_priority.dart';
|
||||||
|
import 'package:cw_ethereum/ethereum_wallet_addresses.dart';
|
||||||
|
import 'package:cw_ethereum/file.dart';
|
||||||
|
import 'package:cw_core/erc20_token.dart';
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
|
import 'package:hex/hex.dart';
|
||||||
|
import 'package:mobx/mobx.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import 'package:web3dart/web3dart.dart';
|
||||||
|
import 'package:bip39/bip39.dart' as bip39;
|
||||||
|
import 'package:bip32/bip32.dart' as bip32;
|
||||||
|
|
||||||
|
part 'ethereum_wallet.g.dart';
|
||||||
|
|
||||||
|
class EthereumWallet = EthereumWalletBase with _$EthereumWallet;
|
||||||
|
|
||||||
|
abstract class EthereumWalletBase
|
||||||
|
extends WalletBase<ERC20Balance, EthereumTransactionHistory, EthereumTransactionInfo>
|
||||||
|
with Store {
|
||||||
|
EthereumWalletBase({
|
||||||
|
required WalletInfo walletInfo,
|
||||||
|
required String mnemonic,
|
||||||
|
required String password,
|
||||||
|
ERC20Balance? initialBalance,
|
||||||
|
}) : syncStatus = NotConnectedSyncStatus(),
|
||||||
|
_password = password,
|
||||||
|
_mnemonic = mnemonic,
|
||||||
|
_isTransactionUpdating = false,
|
||||||
|
_client = EthereumClient(),
|
||||||
|
walletAddresses = EthereumWalletAddresses(walletInfo),
|
||||||
|
balance = ObservableMap<CryptoCurrency, ERC20Balance>.of(
|
||||||
|
{CryptoCurrency.eth: initialBalance ?? ERC20Balance(BigInt.zero)}),
|
||||||
|
super(walletInfo) {
|
||||||
|
this.walletInfo = walletInfo;
|
||||||
|
transactionHistory = EthereumTransactionHistory(walletInfo: walletInfo, password: password);
|
||||||
|
|
||||||
|
if (!Hive.isAdapterRegistered(Erc20Token.typeId)) {
|
||||||
|
Hive.registerAdapter(Erc20TokenAdapter());
|
||||||
|
}
|
||||||
|
|
||||||
|
_sharedPrefs.complete(SharedPreferences.getInstance());
|
||||||
|
}
|
||||||
|
|
||||||
|
final String _mnemonic;
|
||||||
|
final String _password;
|
||||||
|
|
||||||
|
late final Box<Erc20Token> erc20TokensBox;
|
||||||
|
|
||||||
|
late final EthPrivateKey _privateKey;
|
||||||
|
|
||||||
|
late EthereumClient _client;
|
||||||
|
|
||||||
|
int? _gasPrice;
|
||||||
|
int? _estimatedGas;
|
||||||
|
bool _isTransactionUpdating;
|
||||||
|
|
||||||
|
// TODO: remove after integrating our own node and having eth_newPendingTransactionFilter
|
||||||
|
Timer? _transactionsUpdateTimer;
|
||||||
|
|
||||||
|
@override
|
||||||
|
WalletAddresses walletAddresses;
|
||||||
|
|
||||||
|
@override
|
||||||
|
@observable
|
||||||
|
SyncStatus syncStatus;
|
||||||
|
|
||||||
|
@override
|
||||||
|
@observable
|
||||||
|
late ObservableMap<CryptoCurrency, ERC20Balance> balance;
|
||||||
|
|
||||||
|
Completer<SharedPreferences> _sharedPrefs = Completer();
|
||||||
|
|
||||||
|
Future<void> init() async {
|
||||||
|
erc20TokensBox = await Hive.openBox<Erc20Token>(Erc20Token.boxName);
|
||||||
|
await walletAddresses.init();
|
||||||
|
await transactionHistory.init();
|
||||||
|
_privateKey = await getPrivateKey(_mnemonic, _password);
|
||||||
|
walletAddresses.address = _privateKey.address.toString();
|
||||||
|
await save();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int calculateEstimatedFee(TransactionPriority priority, int? amount) {
|
||||||
|
try {
|
||||||
|
if (priority is EthereumTransactionPriority) {
|
||||||
|
final priorityFee =
|
||||||
|
EtherAmount.fromUnitAndValue(EtherUnit.gwei, priority.tip).getInWei.toInt();
|
||||||
|
return (_gasPrice! + priorityFee) * (_estimatedGas ?? 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
} catch (e) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> changePassword(String password) {
|
||||||
|
throw UnimplementedError("changePassword");
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void close() {
|
||||||
|
_client.stop();
|
||||||
|
_transactionsUpdateTimer?.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
@override
|
||||||
|
Future<void> connectToNode({required Node node}) async {
|
||||||
|
try {
|
||||||
|
syncStatus = ConnectingSyncStatus();
|
||||||
|
|
||||||
|
final isConnected = _client.connect(node);
|
||||||
|
|
||||||
|
if (!isConnected) {
|
||||||
|
throw Exception("Ethereum Node connection failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
_client.setListeners(_privateKey.address, _onNewTransaction);
|
||||||
|
|
||||||
|
_setTransactionUpdateTimer();
|
||||||
|
|
||||||
|
syncStatus = ConnectedSyncStatus();
|
||||||
|
} catch (e) {
|
||||||
|
syncStatus = FailedSyncStatus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<PendingTransaction> createTransaction(Object credentials) async {
|
||||||
|
final _credentials = credentials as EthereumTransactionCredentials;
|
||||||
|
final outputs = _credentials.outputs;
|
||||||
|
final hasMultiDestination = outputs.length > 1;
|
||||||
|
final _erc20Balance = balance[_credentials.currency]!;
|
||||||
|
BigInt totalAmount = BigInt.zero;
|
||||||
|
int exponent =
|
||||||
|
_credentials.currency is Erc20Token ? (_credentials.currency as Erc20Token).decimal : 18;
|
||||||
|
num amountToEthereumMultiplier = pow(10, exponent);
|
||||||
|
|
||||||
|
if (hasMultiDestination) {
|
||||||
|
if (outputs.any((item) => item.sendAll || (item.formattedCryptoAmount ?? 0) <= 0)) {
|
||||||
|
throw EthereumTransactionCreationException(_credentials.currency);
|
||||||
|
}
|
||||||
|
|
||||||
|
final totalOriginalAmount = EthereumFormatter.parseEthereumAmountToDouble(
|
||||||
|
outputs.fold(0, (acc, value) => acc + (value.formattedCryptoAmount ?? 0)));
|
||||||
|
totalAmount = BigInt.from(totalOriginalAmount * amountToEthereumMultiplier);
|
||||||
|
|
||||||
|
if (_erc20Balance.balance < totalAmount) {
|
||||||
|
throw EthereumTransactionCreationException(_credentials.currency);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
final output = outputs.first;
|
||||||
|
final BigInt allAmount =
|
||||||
|
_erc20Balance.balance - BigInt.from(calculateEstimatedFee(_credentials.priority!, null));
|
||||||
|
final totalOriginalAmount =
|
||||||
|
EthereumFormatter.parseEthereumAmountToDouble(output.formattedCryptoAmount ?? 0);
|
||||||
|
totalAmount = output.sendAll
|
||||||
|
? allAmount
|
||||||
|
: BigInt.from(totalOriginalAmount * amountToEthereumMultiplier);
|
||||||
|
|
||||||
|
if (_erc20Balance.balance < totalAmount) {
|
||||||
|
throw EthereumTransactionCreationException(_credentials.currency);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final pendingEthereumTransaction = await _client.signTransaction(
|
||||||
|
privateKey: _privateKey,
|
||||||
|
toAddress: _credentials.outputs.first.address,
|
||||||
|
amount: totalAmount.toString(),
|
||||||
|
gas: _estimatedGas!,
|
||||||
|
priority: _credentials.priority!,
|
||||||
|
currency: _credentials.currency,
|
||||||
|
exponent: exponent,
|
||||||
|
contractAddress: _credentials.currency is Erc20Token
|
||||||
|
? (_credentials.currency as Erc20Token).contractAddress
|
||||||
|
: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
return pendingEthereumTransaction;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _updateTransactions() async {
|
||||||
|
try {
|
||||||
|
if (_isTransactionUpdating) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
bool isEtherscanEnabled = (await _sharedPrefs.future).getBool("use_etherscan") ?? true;
|
||||||
|
if (!isEtherscanEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_isTransactionUpdating = true;
|
||||||
|
final transactions = await fetchTransactions();
|
||||||
|
transactionHistory.addMany(transactions);
|
||||||
|
await transactionHistory.save();
|
||||||
|
_isTransactionUpdating = false;
|
||||||
|
} catch (_) {
|
||||||
|
_isTransactionUpdating = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Map<String, EthereumTransactionInfo>> fetchTransactions() async {
|
||||||
|
final address = _privateKey.address.hex;
|
||||||
|
final transactions = await _client.fetchTransactions(address);
|
||||||
|
|
||||||
|
final List<Future<List<EthereumTransactionModel>>> erc20TokensTransactions = [];
|
||||||
|
|
||||||
|
for (var token in balance.keys) {
|
||||||
|
if (token is Erc20Token) {
|
||||||
|
erc20TokensTransactions.add(_client.fetchTransactions(
|
||||||
|
address,
|
||||||
|
contractAddress: token.contractAddress,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final tokensTransaction = await Future.wait(erc20TokensTransactions);
|
||||||
|
transactions.addAll(tokensTransaction.expand((element) => element));
|
||||||
|
|
||||||
|
final Map<String, EthereumTransactionInfo> result = {};
|
||||||
|
|
||||||
|
for (var transactionModel in transactions) {
|
||||||
|
if (transactionModel.isError) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
result[transactionModel.hash] = EthereumTransactionInfo(
|
||||||
|
id: transactionModel.hash,
|
||||||
|
height: transactionModel.blockNumber,
|
||||||
|
ethAmount: transactionModel.amount,
|
||||||
|
direction: transactionModel.from == address
|
||||||
|
? TransactionDirection.outgoing
|
||||||
|
: TransactionDirection.incoming,
|
||||||
|
isPending: false,
|
||||||
|
date: transactionModel.date,
|
||||||
|
confirmations: transactionModel.confirmations,
|
||||||
|
ethFee: BigInt.from(transactionModel.gasUsed) * transactionModel.gasPrice,
|
||||||
|
exponent: transactionModel.tokenDecimal ?? 18,
|
||||||
|
tokenSymbol: transactionModel.tokenSymbol ?? "ETH",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Object get keys => throw UnimplementedError("keys");
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> rescan({required int height}) {
|
||||||
|
throw UnimplementedError("rescan");
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> save() async {
|
||||||
|
await walletAddresses.updateAddressesInBox();
|
||||||
|
final path = await makePath();
|
||||||
|
await write(path: path, password: _password, data: toJSON());
|
||||||
|
await transactionHistory.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get seed => _mnemonic;
|
||||||
|
|
||||||
|
@action
|
||||||
|
@override
|
||||||
|
Future<void> startSync() async {
|
||||||
|
try {
|
||||||
|
syncStatus = AttemptingSyncStatus();
|
||||||
|
await _updateBalance();
|
||||||
|
await _updateTransactions();
|
||||||
|
_gasPrice = await _client.getGasUnitPrice();
|
||||||
|
_estimatedGas = await _client.getEstimatedGas();
|
||||||
|
|
||||||
|
Timer.periodic(
|
||||||
|
const Duration(minutes: 1), (timer) async => _gasPrice = await _client.getGasUnitPrice());
|
||||||
|
Timer.periodic(const Duration(seconds: 10),
|
||||||
|
(timer) async => _estimatedGas = await _client.getEstimatedGas());
|
||||||
|
|
||||||
|
syncStatus = SyncedSyncStatus();
|
||||||
|
} catch (e) {
|
||||||
|
syncStatus = FailedSyncStatus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type);
|
||||||
|
|
||||||
|
String toJSON() => json.encode({
|
||||||
|
'mnemonic': _mnemonic,
|
||||||
|
'balance': balance[currency]!.toJSON(),
|
||||||
|
});
|
||||||
|
|
||||||
|
static Future<EthereumWallet> open({
|
||||||
|
required String name,
|
||||||
|
required String password,
|
||||||
|
required WalletInfo walletInfo,
|
||||||
|
}) async {
|
||||||
|
final path = await pathForWallet(name: name, type: walletInfo.type);
|
||||||
|
final jsonSource = await read(path: path, password: password);
|
||||||
|
final data = json.decode(jsonSource) as Map;
|
||||||
|
final mnemonic = data['mnemonic'] as String;
|
||||||
|
final balance = ERC20Balance.fromJSON(data['balance'] as String) ?? ERC20Balance(BigInt.zero);
|
||||||
|
|
||||||
|
return EthereumWallet(
|
||||||
|
walletInfo: walletInfo,
|
||||||
|
password: password,
|
||||||
|
mnemonic: mnemonic,
|
||||||
|
initialBalance: balance,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _updateBalance() async {
|
||||||
|
balance[currency] = await _fetchEthBalance();
|
||||||
|
|
||||||
|
await _fetchErc20Balances();
|
||||||
|
await save();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<ERC20Balance> _fetchEthBalance() async {
|
||||||
|
final balance = await _client.getBalance(_privateKey.address);
|
||||||
|
return ERC20Balance(balance.getInWei);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _fetchErc20Balances() async {
|
||||||
|
for (var token in erc20TokensBox.values) {
|
||||||
|
try {
|
||||||
|
if (token.enabled) {
|
||||||
|
balance[token] = await _client.fetchERC20Balances(
|
||||||
|
_privateKey.address,
|
||||||
|
token.contractAddress,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
balance.remove(token);
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<EthPrivateKey> getPrivateKey(String mnemonic, String password) async {
|
||||||
|
final seed = bip39.mnemonicToSeed(mnemonic);
|
||||||
|
|
||||||
|
final root = bip32.BIP32.fromSeed(seed);
|
||||||
|
|
||||||
|
const _hdPathEthereum = "m/44'/60'/0'/0";
|
||||||
|
const index = 0;
|
||||||
|
final addressAtIndex = root.derivePath("$_hdPathEthereum/$index");
|
||||||
|
|
||||||
|
return EthPrivateKey.fromHex(HEX.encode(addressAtIndex.privateKey as List<int>));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void>? updateBalance() async => await _updateBalance();
|
||||||
|
|
||||||
|
List<Erc20Token> get erc20Currencies => erc20TokensBox.values.toList();
|
||||||
|
|
||||||
|
Future<void> addErc20Token(Erc20Token token) async {
|
||||||
|
String? iconPath;
|
||||||
|
try {
|
||||||
|
iconPath = CryptoCurrency.all
|
||||||
|
.firstWhere((element) => element.title.toUpperCase() == token.symbol.toUpperCase())
|
||||||
|
.iconPath;
|
||||||
|
} catch (_) {}
|
||||||
|
|
||||||
|
final _token = Erc20Token(
|
||||||
|
name: token.name,
|
||||||
|
symbol: token.symbol,
|
||||||
|
contractAddress: token.contractAddress,
|
||||||
|
decimal: token.decimal,
|
||||||
|
enabled: token.enabled,
|
||||||
|
iconPath: iconPath,
|
||||||
|
);
|
||||||
|
|
||||||
|
await erc20TokensBox.put(_token.contractAddress, _token);
|
||||||
|
|
||||||
|
if (_token.enabled) {
|
||||||
|
balance[_token] = await _client.fetchERC20Balances(
|
||||||
|
_privateKey.address,
|
||||||
|
_token.contractAddress,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
balance.remove(_token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> deleteErc20Token(Erc20Token token) async {
|
||||||
|
await token.delete();
|
||||||
|
|
||||||
|
balance.remove(token);
|
||||||
|
_updateBalance();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Erc20Token?> getErc20Token(String contractAddress) async =>
|
||||||
|
await _client.getErc20Token(contractAddress);
|
||||||
|
|
||||||
|
void _onNewTransaction() {
|
||||||
|
_updateBalance();
|
||||||
|
_updateTransactions();
|
||||||
|
}
|
||||||
|
|
||||||
|
void addInitialTokens() {
|
||||||
|
final initialErc20Tokens = DefaultErc20Tokens().initialErc20Tokens;
|
||||||
|
|
||||||
|
initialErc20Tokens.forEach((token) => erc20TokensBox.put(token.contractAddress, token));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> renameWalletFiles(String newWalletName) async {
|
||||||
|
final currentWalletPath = await pathForWallet(name: walletInfo.name, type: type);
|
||||||
|
final currentWalletFile = File(currentWalletPath);
|
||||||
|
|
||||||
|
final currentDirPath = await pathForWalletDir(name: walletInfo.name, type: type);
|
||||||
|
final currentTransactionsFile = File('$currentDirPath/$transactionsHistoryFileName');
|
||||||
|
|
||||||
|
// Copies current wallet files into new wallet name's dir and files
|
||||||
|
if (currentWalletFile.existsSync()) {
|
||||||
|
final newWalletPath = await pathForWallet(name: newWalletName, type: type);
|
||||||
|
await currentWalletFile.copy(newWalletPath);
|
||||||
|
}
|
||||||
|
if (currentTransactionsFile.existsSync()) {
|
||||||
|
final newDirPath = await pathForWalletDir(name: newWalletName, type: type);
|
||||||
|
await currentTransactionsFile.copy('$newDirPath/$transactionsHistoryFileName');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete old name's dir and files
|
||||||
|
await Directory(currentDirPath).delete(recursive: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _setTransactionUpdateTimer() {
|
||||||
|
if (_transactionsUpdateTimer?.isActive ?? false) {
|
||||||
|
_transactionsUpdateTimer!.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
_transactionsUpdateTimer = Timer.periodic(Duration(seconds: 10), (_) {
|
||||||
|
_updateTransactions();
|
||||||
|
_updateBalance();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateEtherscanUsageState(bool isEnabled) {
|
||||||
|
if (isEnabled) {
|
||||||
|
_updateTransactions();
|
||||||
|
_setTransactionUpdateTimer();
|
||||||
|
} else {
|
||||||
|
_transactionsUpdateTimer?.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
33
cw_ethereum/lib/ethereum_wallet_addresses.dart
Normal file
33
cw_ethereum/lib/ethereum_wallet_addresses.dart
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import 'package:cw_core/wallet_addresses.dart';
|
||||||
|
import 'package:cw_core/wallet_info.dart';
|
||||||
|
import 'package:mobx/mobx.dart';
|
||||||
|
|
||||||
|
part 'ethereum_wallet_addresses.g.dart';
|
||||||
|
|
||||||
|
class EthereumWalletAddresses = EthereumWalletAddressesBase with _$EthereumWalletAddresses;
|
||||||
|
|
||||||
|
abstract class EthereumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
|
EthereumWalletAddressesBase(WalletInfo walletInfo)
|
||||||
|
: address = '',
|
||||||
|
super(walletInfo);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String address;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> init() async {
|
||||||
|
address = walletInfo.address;
|
||||||
|
await updateAddressesInBox();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> updateAddressesInBox() async {
|
||||||
|
try {
|
||||||
|
addressesMap.clear();
|
||||||
|
addressesMap[address] = '';
|
||||||
|
await saveAddressesInBox();
|
||||||
|
} catch (e) {
|
||||||
|
print(e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
23
cw_ethereum/lib/ethereum_wallet_creation_credentials.dart
Normal file
23
cw_ethereum/lib/ethereum_wallet_creation_credentials.dart
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import 'package:cw_core/wallet_credentials.dart';
|
||||||
|
import 'package:cw_core/wallet_info.dart';
|
||||||
|
|
||||||
|
class EthereumNewWalletCredentials extends WalletCredentials {
|
||||||
|
EthereumNewWalletCredentials({required String name, WalletInfo? walletInfo})
|
||||||
|
: super(name: name, walletInfo: walletInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
class EthereumRestoreWalletFromSeedCredentials extends WalletCredentials {
|
||||||
|
EthereumRestoreWalletFromSeedCredentials(
|
||||||
|
{required String name, required String password, required this.mnemonic, WalletInfo? walletInfo})
|
||||||
|
: super(name: name, password: password, walletInfo: walletInfo);
|
||||||
|
|
||||||
|
final String mnemonic;
|
||||||
|
}
|
||||||
|
|
||||||
|
class EthereumRestoreWalletFromWIFCredentials extends WalletCredentials {
|
||||||
|
EthereumRestoreWalletFromWIFCredentials(
|
||||||
|
{required String name, required String password, required this.wif, WalletInfo? walletInfo})
|
||||||
|
: super(name: name, password: password, walletInfo: walletInfo);
|
||||||
|
|
||||||
|
final String wif;
|
||||||
|
}
|
108
cw_ethereum/lib/ethereum_wallet_service.dart
Normal file
108
cw_ethereum/lib/ethereum_wallet_service.dart
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:cw_core/pathForWallet.dart';
|
||||||
|
import 'package:cw_core/wallet_base.dart';
|
||||||
|
import 'package:cw_core/wallet_info.dart';
|
||||||
|
import 'package:cw_core/wallet_service.dart';
|
||||||
|
import 'package:cw_core/wallet_type.dart';
|
||||||
|
import 'package:cw_ethereum/ethereum_mnemonics.dart';
|
||||||
|
import 'package:cw_ethereum/ethereum_wallet.dart';
|
||||||
|
import 'package:cw_ethereum/ethereum_wallet_creation_credentials.dart';
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
|
import 'package:bip39/bip39.dart' as bip39;
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
|
|
||||||
|
class EthereumWalletService extends WalletService<EthereumNewWalletCredentials,
|
||||||
|
EthereumRestoreWalletFromSeedCredentials, EthereumRestoreWalletFromWIFCredentials> {
|
||||||
|
EthereumWalletService(this.walletInfoSource);
|
||||||
|
|
||||||
|
final Box<WalletInfo> walletInfoSource;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<EthereumWallet> create(EthereumNewWalletCredentials credentials) async {
|
||||||
|
final mnemonic = bip39.generateMnemonic();
|
||||||
|
final wallet = EthereumWallet(
|
||||||
|
walletInfo: credentials.walletInfo!,
|
||||||
|
mnemonic: mnemonic,
|
||||||
|
password: credentials.password!,
|
||||||
|
);
|
||||||
|
|
||||||
|
await wallet.init();
|
||||||
|
wallet.addInitialTokens();
|
||||||
|
await wallet.save();
|
||||||
|
|
||||||
|
return wallet;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
WalletType getType() => WalletType.ethereum;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> isWalletExit(String name) async =>
|
||||||
|
File(await pathForWallet(name: name, type: getType())).existsSync();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<EthereumWallet> openWallet(String name, String password) async {
|
||||||
|
final walletInfo =
|
||||||
|
walletInfoSource.values.firstWhere((info) => info.id == WalletBase.idFor(name, getType()));
|
||||||
|
final wallet = await EthereumWalletBase.open(
|
||||||
|
name: name,
|
||||||
|
password: password,
|
||||||
|
walletInfo: walletInfo,
|
||||||
|
);
|
||||||
|
|
||||||
|
await wallet.init();
|
||||||
|
await wallet.save();
|
||||||
|
|
||||||
|
return wallet;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> remove(String wallet) async {
|
||||||
|
File(await pathForWalletDir(name: wallet, type: getType())).delete(recursive: true);
|
||||||
|
final walletInfo = walletInfoSource.values.firstWhereOrNull(
|
||||||
|
(info) => info.id == WalletBase.idFor(wallet, getType()))!;
|
||||||
|
await walletInfoSource.delete(walletInfo.key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<EthereumWallet> restoreFromKeys(credentials) {
|
||||||
|
throw UnimplementedError();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<EthereumWallet> restoreFromSeed(
|
||||||
|
EthereumRestoreWalletFromSeedCredentials credentials) async {
|
||||||
|
if (!bip39.validateMnemonic(credentials.mnemonic)) {
|
||||||
|
throw EthereumMnemonicIsIncorrectException();
|
||||||
|
}
|
||||||
|
|
||||||
|
final wallet = EthereumWallet(
|
||||||
|
password: credentials.password!,
|
||||||
|
mnemonic: credentials.mnemonic,
|
||||||
|
walletInfo: credentials.walletInfo!,
|
||||||
|
);
|
||||||
|
|
||||||
|
await wallet.init();
|
||||||
|
wallet.addInitialTokens();
|
||||||
|
await wallet.save();
|
||||||
|
|
||||||
|
return wallet;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> rename(String currentName, String password, String newName) async {
|
||||||
|
final currentWalletInfo = walletInfoSource.values
|
||||||
|
.firstWhere((info) => info.id == WalletBase.idFor(currentName, getType()));
|
||||||
|
final currentWallet = await EthereumWalletBase.open(
|
||||||
|
password: password, name: currentName, walletInfo: currentWalletInfo);
|
||||||
|
|
||||||
|
await currentWallet.renameWalletFiles(newName);
|
||||||
|
|
||||||
|
final newWalletInfo = currentWalletInfo;
|
||||||
|
newWalletInfo.id = WalletBase.idFor(newName, getType());
|
||||||
|
newWalletInfo.name = newName;
|
||||||
|
|
||||||
|
await walletInfoSource.put(currentWalletInfo.key, newWalletInfo);
|
||||||
|
}
|
||||||
|
}
|
39
cw_ethereum/lib/file.dart
Normal file
39
cw_ethereum/lib/file.dart
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:cw_core/key.dart';
|
||||||
|
import 'package:encrypt/encrypt.dart' as encrypt;
|
||||||
|
|
||||||
|
Future<void> write(
|
||||||
|
{required String path,
|
||||||
|
required String password,
|
||||||
|
required String data}) async {
|
||||||
|
final keys = extractKeys(password);
|
||||||
|
final key = encrypt.Key.fromBase64(keys.first);
|
||||||
|
final iv = encrypt.IV.fromBase64(keys.last);
|
||||||
|
final encrypted = await encode(key: key, iv: iv, data: data);
|
||||||
|
final f = File(path);
|
||||||
|
f.writeAsStringSync(encrypted);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> writeData(
|
||||||
|
{required String path,
|
||||||
|
required String password,
|
||||||
|
required String data}) async {
|
||||||
|
final keys = extractKeys(password);
|
||||||
|
final key = encrypt.Key.fromBase64(keys.first);
|
||||||
|
final iv = encrypt.IV.fromBase64(keys.last);
|
||||||
|
final encrypted = await encode(key: key, iv: iv, data: data);
|
||||||
|
final f = File(path);
|
||||||
|
f.writeAsStringSync(encrypted);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> read({required String path, required String password}) async {
|
||||||
|
final file = File(path);
|
||||||
|
|
||||||
|
if (!file.existsSync()) {
|
||||||
|
file.createSync();
|
||||||
|
}
|
||||||
|
|
||||||
|
final encrypted = file.readAsStringSync();
|
||||||
|
|
||||||
|
return decode(password: password, data: encrypted);
|
||||||
|
}
|
36
cw_ethereum/lib/pending_ethereum_transaction.dart
Normal file
36
cw_ethereum/lib/pending_ethereum_transaction.dart
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
import 'dart:math';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:cw_core/pending_transaction.dart';
|
||||||
|
import 'package:web3dart/crypto.dart';
|
||||||
|
|
||||||
|
class PendingEthereumTransaction with PendingTransaction {
|
||||||
|
final Function sendTransaction;
|
||||||
|
final Uint8List signedTransaction;
|
||||||
|
final BigInt fee;
|
||||||
|
final String amount;
|
||||||
|
final int exponent;
|
||||||
|
|
||||||
|
PendingEthereumTransaction({
|
||||||
|
required this.sendTransaction,
|
||||||
|
required this.signedTransaction,
|
||||||
|
required this.fee,
|
||||||
|
required this.amount,
|
||||||
|
required this.exponent,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get amountFormatted => (BigInt.parse(amount) / BigInt.from(pow(10, exponent))).toString();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> commit() async => await sendTransaction();
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get feeFormatted => (fee / BigInt.from(pow(10, 18))).toString();
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get hex => bytesToHex(signedTransaction, include0x: true);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get id => '';
|
||||||
|
}
|
68
cw_ethereum/pubspec.yaml
Normal file
68
cw_ethereum/pubspec.yaml
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
name: cw_ethereum
|
||||||
|
description: A new Flutter package project.
|
||||||
|
version: 0.0.1
|
||||||
|
publish_to: none
|
||||||
|
author: Cake Wallet
|
||||||
|
homepage: https://cakewallet.com
|
||||||
|
|
||||||
|
environment:
|
||||||
|
sdk: '>=2.18.2 <3.0.0'
|
||||||
|
flutter: ">=1.17.0"
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
flutter:
|
||||||
|
sdk: flutter
|
||||||
|
web3dart: 2.3.5
|
||||||
|
mobx: ^2.0.7+4
|
||||||
|
bip39: ^1.0.6
|
||||||
|
bip32: ^2.0.0
|
||||||
|
ed25519_hd_key: ^2.2.0
|
||||||
|
hex: ^0.2.0
|
||||||
|
http: ^0.13.4
|
||||||
|
shared_preferences: ^2.0.15
|
||||||
|
cw_core:
|
||||||
|
path: ../cw_core
|
||||||
|
|
||||||
|
dev_dependencies:
|
||||||
|
flutter_test:
|
||||||
|
sdk: flutter
|
||||||
|
build_runner: ^2.1.11
|
||||||
|
mobx_codegen: ^2.0.7
|
||||||
|
hive_generator: ^1.1.3
|
||||||
|
|
||||||
|
# For information on the generic Dart part of this file, see the
|
||||||
|
# following page: https://dart.dev/tools/pub/pubspec
|
||||||
|
|
||||||
|
# The following section is specific to Flutter packages.
|
||||||
|
flutter:
|
||||||
|
|
||||||
|
# To add assets to your package, add an assets section, like this:
|
||||||
|
# assets:
|
||||||
|
# - images/a_dot_burr.jpeg
|
||||||
|
# - images/a_dot_ham.jpeg
|
||||||
|
#
|
||||||
|
# For details regarding assets in packages, see
|
||||||
|
# https://flutter.dev/assets-and-images/#from-packages
|
||||||
|
#
|
||||||
|
# An image asset can refer to one or more resolution-specific "variants", see
|
||||||
|
# https://flutter.dev/assets-and-images/#resolution-aware
|
||||||
|
|
||||||
|
# To add custom fonts to your package, add a fonts section here,
|
||||||
|
# in this "flutter" section. Each entry in this list should have a
|
||||||
|
# "family" key with the font family name, and a "fonts" key with a
|
||||||
|
# list giving the asset and other descriptors for the font. For
|
||||||
|
# example:
|
||||||
|
# fonts:
|
||||||
|
# - family: Schyler
|
||||||
|
# fonts:
|
||||||
|
# - asset: fonts/Schyler-Regular.ttf
|
||||||
|
# - asset: fonts/Schyler-Italic.ttf
|
||||||
|
# style: italic
|
||||||
|
# - family: Trajan Pro
|
||||||
|
# fonts:
|
||||||
|
# - asset: fonts/TrajanPro.ttf
|
||||||
|
# - asset: fonts/TrajanPro_Bold.ttf
|
||||||
|
# weight: 700
|
||||||
|
#
|
||||||
|
# For details regarding fonts in packages, see
|
||||||
|
# https://flutter.dev/custom-fonts/#from-packages
|
12
cw_ethereum/test/cw_ethereum_test.dart
Normal file
12
cw_ethereum/test/cw_ethereum_test.dart
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
import 'package:cw_ethereum/cw_ethereum.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
test('adds one to input values', () {
|
||||||
|
final calculator = Calculator();
|
||||||
|
expect(calculator.addOne(2), 3);
|
||||||
|
expect(calculator.addOne(-7), -6);
|
||||||
|
expect(calculator.addOne(0), 1);
|
||||||
|
});
|
||||||
|
}
|
|
@ -254,6 +254,7 @@ abstract class HavenWalletBase extends WalletBase<MoneroBalance,
|
||||||
await haven_wallet.store();
|
await haven_wallet.store();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
Future<void> renameWalletFiles(String newWalletName) async {
|
Future<void> renameWalletFiles(String newWalletName) async {
|
||||||
final currentWalletPath = await pathForWallet(name: name, type: type);
|
final currentWalletPath = await pathForWallet(name: name, type: type);
|
||||||
final currentCacheFile = File(currentWalletPath);
|
final currentCacheFile = File(currentWalletPath);
|
||||||
|
|
|
@ -14,18 +14,18 @@ class MoneroTransactionInfo extends TransactionInfo {
|
||||||
MoneroTransactionInfo.fromMap(Map<String, Object?> map)
|
MoneroTransactionInfo.fromMap(Map<String, Object?> map)
|
||||||
: id = (map['hash'] ?? '') as String,
|
: id = (map['hash'] ?? '') as String,
|
||||||
height = (map['height'] ?? 0) as int,
|
height = (map['height'] ?? 0) as int,
|
||||||
direction =
|
direction = map['direction'] != null
|
||||||
parseTransactionDirectionFromNumber(map['direction'] as String) ??
|
? parseTransactionDirectionFromNumber(map['direction'] as String)
|
||||||
TransactionDirection.incoming,
|
: TransactionDirection.incoming,
|
||||||
date = DateTime.fromMillisecondsSinceEpoch(
|
date = DateTime.fromMillisecondsSinceEpoch(
|
||||||
(int.parse(map['timestamp'] as String) ?? 0) * 1000),
|
(int.tryParse(map['timestamp'] as String? ?? '') ?? 0) * 1000),
|
||||||
isPending = parseBoolFromString(map['isPending'] as String),
|
isPending = parseBoolFromString(map['isPending'] as String),
|
||||||
amount = map['amount'] as int,
|
amount = map['amount'] as int,
|
||||||
accountIndex = int.parse(map['accountIndex'] as String),
|
accountIndex = int.parse(map['accountIndex'] as String),
|
||||||
addressIndex = map['addressIndex'] as int,
|
addressIndex = map['addressIndex'] as int,
|
||||||
confirmations = map['confirmations'] as int,
|
confirmations = map['confirmations'] as int,
|
||||||
key = getTxKey((map['hash'] ?? '') as String),
|
key = getTxKey((map['hash'] ?? '') as String),
|
||||||
fee = map['fee'] as int ?? 0 {
|
fee = map['fee'] as int? ?? 0 {
|
||||||
additionalInfo = <String, dynamic>{
|
additionalInfo = <String, dynamic>{
|
||||||
'key': key,
|
'key': key,
|
||||||
'accountIndex': accountIndex,
|
'accountIndex': accountIndex,
|
||||||
|
@ -36,8 +36,7 @@ class MoneroTransactionInfo extends TransactionInfo {
|
||||||
MoneroTransactionInfo.fromRow(TransactionInfoRow row)
|
MoneroTransactionInfo.fromRow(TransactionInfoRow row)
|
||||||
: id = row.getHash(),
|
: id = row.getHash(),
|
||||||
height = row.blockHeight,
|
height = row.blockHeight,
|
||||||
direction = parseTransactionDirectionFromInt(row.direction) ??
|
direction = parseTransactionDirectionFromInt(row.direction),
|
||||||
TransactionDirection.incoming,
|
|
||||||
date = DateTime.fromMillisecondsSinceEpoch(row.getDatetime() * 1000),
|
date = DateTime.fromMillisecondsSinceEpoch(row.getDatetime() * 1000),
|
||||||
isPending = row.isPending != 0,
|
isPending = row.isPending != 0,
|
||||||
amount = row.getAmount(),
|
amount = row.getAmount(),
|
||||||
|
|
|
@ -269,6 +269,7 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
||||||
await monero_wallet.store();
|
await monero_wallet.store();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
Future<void> renameWalletFiles(String newWalletName) async {
|
Future<void> renameWalletFiles(String newWalletName) async {
|
||||||
final currentWalletDirPath = await pathForWalletDir(name: name, type: type);
|
final currentWalletDirPath = await pathForWalletDir(name: name, type: type);
|
||||||
|
|
||||||
|
|
|
@ -6,9 +6,9 @@ The following are the system requirements to build CakeWallet for your Android d
|
||||||
|
|
||||||
```
|
```
|
||||||
Ubuntu >= 16.04
|
Ubuntu >= 16.04
|
||||||
Android SDK 28
|
Android SDK 29 or higher (better to have the latest one 33)
|
||||||
Android NDK 17c
|
Android NDK 17c
|
||||||
Flutter 2 or above
|
Flutter 3.7.x
|
||||||
```
|
```
|
||||||
|
|
||||||
## Building CakeWallet on Android
|
## Building CakeWallet on Android
|
||||||
|
@ -55,7 +55,7 @@ You may download and install the latest version of Android Studio [here](https:/
|
||||||
|
|
||||||
### 3. Installing Flutter
|
### 3. Installing Flutter
|
||||||
|
|
||||||
Need to install flutter with version `3.x.x`. For this please check section [Install Flutter manually](https://docs.flutter.dev/get-started/install/linux#install-flutter-manually).
|
Need to install flutter with version `3.7.x`. For this please check section [Install Flutter manually](https://docs.flutter.dev/get-started/install/linux#install-flutter-manually).
|
||||||
|
|
||||||
### 4. Verify Installations
|
### 4. Verify Installations
|
||||||
|
|
||||||
|
@ -66,9 +66,9 @@ Verify that the Android toolchain, Flutter, and Android Studio have been correct
|
||||||
The output of this command will appear like this, indicating successful installations. If there are problems with your installation, they **must** be corrected before proceeding.
|
The output of this command will appear like this, indicating successful installations. If there are problems with your installation, they **must** be corrected before proceeding.
|
||||||
```
|
```
|
||||||
Doctor summary (to see all details, run flutter doctor -v):
|
Doctor summary (to see all details, run flutter doctor -v):
|
||||||
[✓] Flutter (Channel stable, 3.x.x, on Linux, locale en_US.UTF-8)
|
[✓] Flutter (Channel stable, 3.7.x, on Linux, locale en_US.UTF-8)
|
||||||
[✓] Android toolchain - develop for Android devices (Android SDK version 28)
|
[✓] Android toolchain - develop for Android devices (Android SDK version 29 or higher)
|
||||||
[✓] Android Studio (version 4.0)
|
[✓] Android Studio (version 4.0 or higher)
|
||||||
```
|
```
|
||||||
|
|
||||||
### 5. Generate a secure keystore for Android
|
### 5. Generate a secure keystore for Android
|
||||||
|
|
|
@ -80,7 +80,7 @@ class CWBitcoin extends Bitcoin {
|
||||||
isParsedAddress: out.isParsedAddress,
|
isParsedAddress: out.isParsedAddress,
|
||||||
formattedCryptoAmount: out.formattedCryptoAmount))
|
formattedCryptoAmount: out.formattedCryptoAmount))
|
||||||
.toList(),
|
.toList(),
|
||||||
priority: priority != null ? priority as BitcoinTransactionPriority : null,
|
priority: priority as BitcoinTransactionPriority,
|
||||||
feeRate: feeRate);
|
feeRate: feeRate);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -2,6 +2,7 @@ import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
||||||
import 'package:cake_wallet/generated/i18n.dart';
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
import 'package:cake_wallet/core/validator.dart';
|
import 'package:cake_wallet/core/validator.dart';
|
||||||
import 'package:cw_core/crypto_currency.dart';
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
|
import 'package:cw_core/erc20_token.dart';
|
||||||
|
|
||||||
class AddressValidator extends TextValidator {
|
class AddressValidator extends TextValidator {
|
||||||
AddressValidator({required CryptoCurrency type})
|
AddressValidator({required CryptoCurrency type})
|
||||||
|
@ -14,6 +15,9 @@ class AddressValidator extends TextValidator {
|
||||||
length: getLength(type));
|
length: getLength(type));
|
||||||
|
|
||||||
static String getPattern(CryptoCurrency type) {
|
static String getPattern(CryptoCurrency type) {
|
||||||
|
if (type is Erc20Token) {
|
||||||
|
return '0x[0-9a-zA-Z]';
|
||||||
|
}
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case CryptoCurrency.xmr:
|
case CryptoCurrency.xmr:
|
||||||
return '^4[0-9a-zA-Z]{94}\$|^8[0-9a-zA-Z]{94}\$|^[0-9a-zA-Z]{106}\$';
|
return '^4[0-9a-zA-Z]{94}\$|^8[0-9a-zA-Z]{94}\$|^[0-9a-zA-Z]{106}\$';
|
||||||
|
@ -56,6 +60,7 @@ class AddressValidator extends TextValidator {
|
||||||
case CryptoCurrency.zrx:
|
case CryptoCurrency.zrx:
|
||||||
case CryptoCurrency.dydx:
|
case CryptoCurrency.dydx:
|
||||||
case CryptoCurrency.steth:
|
case CryptoCurrency.steth:
|
||||||
|
case CryptoCurrency.shib:
|
||||||
return '0x[0-9a-zA-Z]';
|
return '0x[0-9a-zA-Z]';
|
||||||
case CryptoCurrency.xrp:
|
case CryptoCurrency.xrp:
|
||||||
return '^[0-9a-zA-Z]{34}\$|^X[0-9a-zA-Z]{46}\$';
|
return '^[0-9a-zA-Z]{34}\$|^X[0-9a-zA-Z]{46}\$';
|
||||||
|
@ -116,17 +121,14 @@ class AddressValidator extends TextValidator {
|
||||||
}
|
}
|
||||||
|
|
||||||
static List<int>? getLength(CryptoCurrency type) {
|
static List<int>? getLength(CryptoCurrency type) {
|
||||||
|
if (type is Erc20Token) {
|
||||||
|
return [42];
|
||||||
|
}
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case CryptoCurrency.xmr:
|
case CryptoCurrency.xmr:
|
||||||
return null;
|
return null;
|
||||||
case CryptoCurrency.ada:
|
case CryptoCurrency.ada:
|
||||||
return null;
|
return null;
|
||||||
case CryptoCurrency.avaxc:
|
|
||||||
return [42];
|
|
||||||
case CryptoCurrency.bch:
|
|
||||||
return [42];
|
|
||||||
case CryptoCurrency.bnb:
|
|
||||||
return [42];
|
|
||||||
case CryptoCurrency.btc:
|
case CryptoCurrency.btc:
|
||||||
return null;
|
return null;
|
||||||
case CryptoCurrency.dash:
|
case CryptoCurrency.dash:
|
||||||
|
@ -166,6 +168,10 @@ class AddressValidator extends TextValidator {
|
||||||
case CryptoCurrency.zrx:
|
case CryptoCurrency.zrx:
|
||||||
case CryptoCurrency.dydx:
|
case CryptoCurrency.dydx:
|
||||||
case CryptoCurrency.steth:
|
case CryptoCurrency.steth:
|
||||||
|
case CryptoCurrency.shib:
|
||||||
|
case CryptoCurrency.avaxc:
|
||||||
|
case CryptoCurrency.bch:
|
||||||
|
case CryptoCurrency.bnb:
|
||||||
return [42];
|
return [42];
|
||||||
case CryptoCurrency.ltc:
|
case CryptoCurrency.ltc:
|
||||||
return [34, 43, 63];
|
return [34, 43, 63];
|
||||||
|
@ -203,11 +209,8 @@ class AddressValidator extends TextValidator {
|
||||||
case CryptoCurrency.xusd:
|
case CryptoCurrency.xusd:
|
||||||
return [98, 99, 106];
|
return [98, 99, 106];
|
||||||
case CryptoCurrency.btt:
|
case CryptoCurrency.btt:
|
||||||
return [34];
|
|
||||||
case CryptoCurrency.bttc:
|
case CryptoCurrency.bttc:
|
||||||
return [34];
|
|
||||||
case CryptoCurrency.doge:
|
case CryptoCurrency.doge:
|
||||||
return [34];
|
|
||||||
case CryptoCurrency.firo:
|
case CryptoCurrency.firo:
|
||||||
return [34];
|
return [34];
|
||||||
case CryptoCurrency.hbar:
|
case CryptoCurrency.hbar:
|
||||||
|
@ -258,6 +261,8 @@ class AddressValidator extends TextValidator {
|
||||||
return '([^0-9a-zA-Z]|^)^L[a-zA-Z0-9]{26,33}([^0-9a-zA-Z]|\$)'
|
return '([^0-9a-zA-Z]|^)^L[a-zA-Z0-9]{26,33}([^0-9a-zA-Z]|\$)'
|
||||||
'|([^0-9a-zA-Z]|^)[LM][a-km-zA-HJ-NP-Z1-9]{26,33}([^0-9a-zA-Z]|\$)'
|
'|([^0-9a-zA-Z]|^)[LM][a-km-zA-HJ-NP-Z1-9]{26,33}([^0-9a-zA-Z]|\$)'
|
||||||
'|([^0-9a-zA-Z]|^)ltc[a-zA-Z0-9]{26,45}([^0-9a-zA-Z]|\$)';
|
'|([^0-9a-zA-Z]|^)ltc[a-zA-Z0-9]{26,45}([^0-9a-zA-Z]|\$)';
|
||||||
|
case CryptoCurrency.eth:
|
||||||
|
return '0x[0-9a-zA-Z]{42}';
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -240,6 +240,9 @@ class BackupService {
|
||||||
data[PreferencesKey.shouldRequireTOTP2FAForCreatingNewWallets] as bool?;
|
data[PreferencesKey.shouldRequireTOTP2FAForCreatingNewWallets] as bool?;
|
||||||
final shouldRequireTOTP2FAForAllSecurityAndBackupSettings =
|
final shouldRequireTOTP2FAForAllSecurityAndBackupSettings =
|
||||||
data[PreferencesKey.shouldRequireTOTP2FAForAllSecurityAndBackupSettings] as bool?;
|
data[PreferencesKey.shouldRequireTOTP2FAForAllSecurityAndBackupSettings] as bool?;
|
||||||
|
final sortBalanceTokensBy = data[PreferencesKey.sortBalanceBy] as int?;
|
||||||
|
final pinNativeTokenAtTop = data[PreferencesKey.pinNativeTokenAtTop] as bool?;
|
||||||
|
final useEtherscan = data[PreferencesKey.useEtherscan] as bool?;
|
||||||
|
|
||||||
await _sharedPreferences.setString(PreferencesKey.currentWalletName, currentWalletName);
|
await _sharedPreferences.setString(PreferencesKey.currentWalletName, currentWalletName);
|
||||||
|
|
||||||
|
@ -349,6 +352,15 @@ class BackupService {
|
||||||
PreferencesKey.shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
|
PreferencesKey.shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
|
||||||
shouldRequireTOTP2FAForAllSecurityAndBackupSettings);
|
shouldRequireTOTP2FAForAllSecurityAndBackupSettings);
|
||||||
|
|
||||||
|
if (sortBalanceTokensBy != null)
|
||||||
|
await _sharedPreferences.setInt(PreferencesKey.sortBalanceBy, sortBalanceTokensBy);
|
||||||
|
|
||||||
|
if (pinNativeTokenAtTop != null)
|
||||||
|
await _sharedPreferences.setBool(PreferencesKey.pinNativeTokenAtTop, pinNativeTokenAtTop);
|
||||||
|
|
||||||
|
if (useEtherscan != null)
|
||||||
|
await _sharedPreferences.setBool(PreferencesKey.useEtherscan, useEtherscan);
|
||||||
|
|
||||||
await preferencesFile.delete();
|
await preferencesFile.delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -492,6 +504,12 @@ class BackupService {
|
||||||
_sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForCreatingNewWallets),
|
_sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForCreatingNewWallets),
|
||||||
PreferencesKey.shouldRequireTOTP2FAForAllSecurityAndBackupSettings: _sharedPreferences
|
PreferencesKey.shouldRequireTOTP2FAForAllSecurityAndBackupSettings: _sharedPreferences
|
||||||
.getBool(PreferencesKey.shouldRequireTOTP2FAForAllSecurityAndBackupSettings),
|
.getBool(PreferencesKey.shouldRequireTOTP2FAForAllSecurityAndBackupSettings),
|
||||||
|
PreferencesKey.sortBalanceBy:
|
||||||
|
_sharedPreferences.getInt(PreferencesKey.sortBalanceBy),
|
||||||
|
PreferencesKey.pinNativeTokenAtTop:
|
||||||
|
_sharedPreferences.getBool(PreferencesKey.pinNativeTokenAtTop),
|
||||||
|
PreferencesKey.useEtherscan:
|
||||||
|
_sharedPreferences.getBool(PreferencesKey.useEtherscan),
|
||||||
};
|
};
|
||||||
|
|
||||||
return json.encode(preferences);
|
return json.encode(preferences);
|
||||||
|
|
|
@ -5,21 +5,20 @@ import 'package:flutter/foundation.dart';
|
||||||
import 'package:http/http.dart';
|
import 'package:http/http.dart';
|
||||||
import 'package:cake_wallet/.secrets.g.dart' as secrets;
|
import 'package:cake_wallet/.secrets.g.dart' as secrets;
|
||||||
|
|
||||||
|
|
||||||
const _fiatApiClearNetAuthority = 'fiat-api.cakewallet.com';
|
const _fiatApiClearNetAuthority = 'fiat-api.cakewallet.com';
|
||||||
const _fiatApiOnionAuthority = 'n4z7bdcmwk2oyddxvzaap3x2peqcplh3pzdy7tpkk5ejz5n4mhfvoxqd.onion';
|
const _fiatApiOnionAuthority = 'n4z7bdcmwk2oyddxvzaap3x2peqcplh3pzdy7tpkk5ejz5n4mhfvoxqd.onion';
|
||||||
const _fiatApiPath = '/v2/rates';
|
const _fiatApiPath = '/v2/rates';
|
||||||
|
|
||||||
Future<double> _fetchPrice(Map<String, dynamic> args) async {
|
Future<double> _fetchPrice(Map<String, dynamic> args) async {
|
||||||
final crypto = args['crypto'] as CryptoCurrency;
|
final crypto = args['crypto'] as String;
|
||||||
final fiat = args['fiat'] as FiatCurrency;
|
final fiat = args['fiat'] as String;
|
||||||
final torOnly = args['torOnly'] as bool;
|
final torOnly = args['torOnly'] as bool;
|
||||||
|
|
||||||
final Map<String, String> queryParams = {
|
final Map<String, String> queryParams = {
|
||||||
'interval_count': '1',
|
'interval_count': '1',
|
||||||
'base': crypto.toString(),
|
'base': crypto,
|
||||||
'quote': fiat.toString(),
|
'quote': fiat,
|
||||||
'key' : secrets.fiatApiKey,
|
'key': secrets.fiatApiKey,
|
||||||
};
|
};
|
||||||
|
|
||||||
double price = 0.0;
|
double price = 0.0;
|
||||||
|
@ -52,7 +51,11 @@ Future<double> _fetchPrice(Map<String, dynamic> args) async {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<double> _fetchPriceAsync(CryptoCurrency crypto, FiatCurrency fiat, bool torOnly) async =>
|
Future<double> _fetchPriceAsync(CryptoCurrency crypto, FiatCurrency fiat, bool torOnly) async =>
|
||||||
compute(_fetchPrice, {'fiat': fiat, 'crypto': crypto, 'torOnly': torOnly});
|
compute(_fetchPrice, {
|
||||||
|
'fiat': fiat.toString(),
|
||||||
|
'crypto': crypto.toString(),
|
||||||
|
'torOnly': torOnly,
|
||||||
|
});
|
||||||
|
|
||||||
class FiatConversionService {
|
class FiatConversionService {
|
||||||
static Future<double> fetchPrice({
|
static Future<double> fetchPrice({
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||||
|
import 'package:cake_wallet/ethereum/ethereum.dart';
|
||||||
import 'package:cake_wallet/haven/haven.dart';
|
import 'package:cake_wallet/haven/haven.dart';
|
||||||
import 'package:cake_wallet/core/validator.dart';
|
import 'package:cake_wallet/core/validator.dart';
|
||||||
import 'package:cake_wallet/entities/mnemonic_item.dart';
|
import 'package:cake_wallet/entities/mnemonic_item.dart';
|
||||||
|
@ -25,6 +26,8 @@ class SeedValidator extends Validator<MnemonicItem> {
|
||||||
return monero!.getMoneroWordList(language);
|
return monero!.getMoneroWordList(language);
|
||||||
case WalletType.haven:
|
case WalletType.haven:
|
||||||
return haven!.getMoneroWordList(language);
|
return haven!.getMoneroWordList(language);
|
||||||
|
case WalletType.ethereum:
|
||||||
|
return ethereum!.getEthereumWordList(language);
|
||||||
default:
|
default:
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
33
lib/di.dart
33
lib/di.dart
|
@ -7,6 +7,7 @@ import 'package:cake_wallet/core/yat_service.dart';
|
||||||
import 'package:cake_wallet/entities/exchange_api_mode.dart';
|
import 'package:cake_wallet/entities/exchange_api_mode.dart';
|
||||||
import 'package:cake_wallet/entities/parse_address_from_domain.dart';
|
import 'package:cake_wallet/entities/parse_address_from_domain.dart';
|
||||||
import 'package:cake_wallet/entities/receive_page_option.dart';
|
import 'package:cake_wallet/entities/receive_page_option.dart';
|
||||||
|
import 'package:cake_wallet/ethereum/ethereum.dart';
|
||||||
import 'package:cake_wallet/ionia/ionia_anypay.dart';
|
import 'package:cake_wallet/ionia/ionia_anypay.dart';
|
||||||
import 'package:cake_wallet/ionia/ionia_gift_card.dart';
|
import 'package:cake_wallet/ionia/ionia_gift_card.dart';
|
||||||
import 'package:cake_wallet/ionia/ionia_tip.dart';
|
import 'package:cake_wallet/ionia/ionia_tip.dart';
|
||||||
|
@ -16,6 +17,8 @@ import 'package:cake_wallet/src/screens/buy/webview_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/dashboard/desktop_dashboard_page.dart';
|
import 'package:cake_wallet/src/screens/dashboard/desktop_dashboard_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_sidebar_wrapper.dart';
|
import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_sidebar_wrapper.dart';
|
||||||
import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart';
|
import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/dashboard/edit_token_page.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/dashboard/home_settings_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/dashboard/widgets/transactions_page.dart';
|
import 'package:cake_wallet/src/screens/dashboard/widgets/transactions_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/receive/anonpay_invoice_page.dart';
|
import 'package:cake_wallet/src/screens/receive/anonpay_invoice_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/receive/anonpay_receive_page.dart';
|
import 'package:cake_wallet/src/screens/receive/anonpay_receive_page.dart';
|
||||||
|
@ -40,6 +43,7 @@ import 'package:cake_wallet/utils/responsive_layout_util.dart';
|
||||||
import 'package:cake_wallet/view_model/dashboard/desktop_sidebar_view_model.dart';
|
import 'package:cake_wallet/view_model/dashboard/desktop_sidebar_view_model.dart';
|
||||||
import 'package:cake_wallet/view_model/anon_invoice_page_view_model.dart';
|
import 'package:cake_wallet/view_model/anon_invoice_page_view_model.dart';
|
||||||
import 'package:cake_wallet/view_model/anonpay_details_view_model.dart';
|
import 'package:cake_wallet/view_model/anonpay_details_view_model.dart';
|
||||||
|
import 'package:cake_wallet/view_model/dashboard/home_settings_view_model.dart';
|
||||||
import 'package:cake_wallet/view_model/dashboard/market_place_view_model.dart';
|
import 'package:cake_wallet/view_model/dashboard/market_place_view_model.dart';
|
||||||
import 'package:cake_wallet/view_model/dashboard/receive_option_view_model.dart';
|
import 'package:cake_wallet/view_model/dashboard/receive_option_view_model.dart';
|
||||||
import 'package:cake_wallet/view_model/ionia/ionia_auth_view_model.dart';
|
import 'package:cake_wallet/view_model/ionia/ionia_auth_view_model.dart';
|
||||||
|
@ -70,6 +74,7 @@ import 'package:cake_wallet/view_model/advanced_privacy_settings_view_model.dart
|
||||||
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart';
|
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart';
|
||||||
import 'package:cake_wallet/view_model/wallet_list/wallet_edit_view_model.dart';
|
import 'package:cake_wallet/view_model/wallet_list/wallet_edit_view_model.dart';
|
||||||
import 'package:cake_wallet/view_model/wallet_list/wallet_list_item.dart';
|
import 'package:cake_wallet/view_model/wallet_list/wallet_list_item.dart';
|
||||||
|
import 'package:cw_core/erc20_token.dart';
|
||||||
import 'package:cw_core/unspent_coins_info.dart';
|
import 'package:cw_core/unspent_coins_info.dart';
|
||||||
import 'package:cake_wallet/core/backup_service.dart';
|
import 'package:cake_wallet/core/backup_service.dart';
|
||||||
import 'package:cw_core/wallet_service.dart';
|
import 'package:cw_core/wallet_service.dart';
|
||||||
|
@ -239,9 +244,9 @@ Future setup({
|
||||||
getIt.registerSingletonAsync<SharedPreferences>(() => SharedPreferences.getInstance());
|
getIt.registerSingletonAsync<SharedPreferences>(() => SharedPreferences.getInstance());
|
||||||
}
|
}
|
||||||
|
|
||||||
final isBitcoinBuyEnabled = (secrets.wyreSecretKey.isNotEmpty ?? false) &&
|
final isBitcoinBuyEnabled = (secrets.wyreSecretKey.isNotEmpty) &&
|
||||||
(secrets.wyreApiKey.isNotEmpty ?? false) &&
|
(secrets.wyreApiKey.isNotEmpty) &&
|
||||||
(secrets.wyreAccountId.isNotEmpty ?? false);
|
(secrets.wyreAccountId.isNotEmpty);
|
||||||
|
|
||||||
final settingsStore = await SettingsStoreBase.load(
|
final settingsStore = await SettingsStoreBase.load(
|
||||||
nodeSource: _nodeSource,
|
nodeSource: _nodeSource,
|
||||||
|
@ -638,7 +643,7 @@ Future setup({
|
||||||
});
|
});
|
||||||
|
|
||||||
getIt.registerFactory(() {
|
getIt.registerFactory(() {
|
||||||
return PrivacySettingsViewModel(getIt.get<SettingsStore>());
|
return PrivacySettingsViewModel(getIt.get<SettingsStore>(), getIt.get<AppStore>().wallet!);
|
||||||
});
|
});
|
||||||
|
|
||||||
getIt.registerFactory(() {
|
getIt.registerFactory(() {
|
||||||
|
@ -745,6 +750,8 @@ Future setup({
|
||||||
return bitcoin!.createBitcoinWalletService(_walletInfoSource, _unspentCoinsInfoSource!);
|
return bitcoin!.createBitcoinWalletService(_walletInfoSource, _unspentCoinsInfoSource!);
|
||||||
case WalletType.litecoin:
|
case WalletType.litecoin:
|
||||||
return bitcoin!.createLitecoinWalletService(_walletInfoSource, _unspentCoinsInfoSource!);
|
return bitcoin!.createLitecoinWalletService(_walletInfoSource, _unspentCoinsInfoSource!);
|
||||||
|
case WalletType.ethereum:
|
||||||
|
return ethereum!.createEthereumWalletService(_walletInfoSource);
|
||||||
default:
|
default:
|
||||||
throw Exception('Unexpected token: ${param1.toString()} for generating of WalletService');
|
throw Exception('Unexpected token: ${param1.toString()} for generating of WalletService');
|
||||||
}
|
}
|
||||||
|
@ -787,8 +794,8 @@ Future setup({
|
||||||
transactionDetailsViewModel:
|
transactionDetailsViewModel:
|
||||||
getIt.get<TransactionDetailsViewModel>(param1: transactionInfo)));
|
getIt.get<TransactionDetailsViewModel>(param1: transactionInfo)));
|
||||||
|
|
||||||
getIt.registerFactoryParam<NewWalletTypePage, void Function(BuildContext, WalletType), void>(
|
getIt.registerFactoryParam<NewWalletTypePage, void Function(BuildContext, WalletType), bool?>(
|
||||||
(param1, _) => NewWalletTypePage(onTypeSelected: param1));
|
(param1, isCreate) => NewWalletTypePage(onTypeSelected: param1, isCreate: isCreate ?? true));
|
||||||
|
|
||||||
getIt.registerFactoryParam<PreSeedPage, WalletType, void>(
|
getIt.registerFactoryParam<PreSeedPage, WalletType, void>(
|
||||||
(WalletType type, _) => PreSeedPage(type));
|
(WalletType type, _) => PreSeedPage(type));
|
||||||
|
@ -1034,5 +1041,19 @@ Future setup({
|
||||||
getIt.registerFactoryParam<AdvancedPrivacySettingsViewModel, WalletType, void>(
|
getIt.registerFactoryParam<AdvancedPrivacySettingsViewModel, WalletType, void>(
|
||||||
(type, _) => AdvancedPrivacySettingsViewModel(type, getIt.get<SettingsStore>()));
|
(type, _) => AdvancedPrivacySettingsViewModel(type, getIt.get<SettingsStore>()));
|
||||||
|
|
||||||
|
getIt.registerFactoryParam<HomeSettingsPage, BalanceViewModel, void>((balanceViewModel, _) =>
|
||||||
|
HomeSettingsPage(getIt.get<HomeSettingsViewModel>(param1: balanceViewModel)));
|
||||||
|
|
||||||
|
getIt.registerFactoryParam<HomeSettingsViewModel, BalanceViewModel, void>(
|
||||||
|
(balanceViewModel, _) => HomeSettingsViewModel(getIt.get<SettingsStore>(), balanceViewModel));
|
||||||
|
|
||||||
|
getIt.registerFactoryParam<EditTokenPage, HomeSettingsViewModel, Map<String, dynamic>>(
|
||||||
|
(homeSettingsViewModel, arguments) => EditTokenPage(
|
||||||
|
homeSettingsViewModel: homeSettingsViewModel,
|
||||||
|
erc20token: arguments['token'] as Erc20Token?,
|
||||||
|
initialContractAddress: arguments['contractAddress'] as String?,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
_isSetupFinished = true;
|
_isSetupFinished = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@ const newCakeWalletMoneroUri = 'xmr-node.cakewallet.com:18081';
|
||||||
const cakeWalletBitcoinElectrumUri = 'electrum.cakewallet.com:50002';
|
const cakeWalletBitcoinElectrumUri = 'electrum.cakewallet.com:50002';
|
||||||
const cakeWalletLitecoinElectrumUri = 'ltc-electrum.cakewallet.com:50002';
|
const cakeWalletLitecoinElectrumUri = 'ltc-electrum.cakewallet.com:50002';
|
||||||
const havenDefaultNodeUri = 'nodes.havenprotocol.org:443';
|
const havenDefaultNodeUri = 'nodes.havenprotocol.org:443';
|
||||||
|
const ethereumDefaultNodeUri = 'ethereum.publicnode.com';
|
||||||
|
|
||||||
Future defaultSettingsMigration(
|
Future defaultSettingsMigration(
|
||||||
{required int version,
|
{required int version,
|
||||||
|
@ -157,6 +158,12 @@ Future defaultSettingsMigration(
|
||||||
case 20:
|
case 20:
|
||||||
await migrateExchangeStatus(sharedPreferences);
|
await migrateExchangeStatus(sharedPreferences);
|
||||||
break;
|
break;
|
||||||
|
case 21:
|
||||||
|
await addEthereumNodeList(nodes: nodes);
|
||||||
|
await changeEthereumCurrentNodeToDefault(
|
||||||
|
sharedPreferences: sharedPreferences, nodes: nodes);
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -242,6 +249,12 @@ Node? getHavenDefaultNode({required Box<Node> nodes}) {
|
||||||
?? nodes.values.firstWhereOrNull((node) => node.type == WalletType.haven);
|
?? nodes.values.firstWhereOrNull((node) => node.type == WalletType.haven);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Node? getEthereumDefaultNode({required Box<Node> nodes}) {
|
||||||
|
return nodes.values.firstWhereOrNull(
|
||||||
|
(Node node) => node.uriRaw == ethereumDefaultNodeUri)
|
||||||
|
?? nodes.values.firstWhereOrNull((node) => node.type == WalletType.ethereum);
|
||||||
|
}
|
||||||
|
|
||||||
Node getMoneroDefaultNode({required Box<Node> nodes}) {
|
Node getMoneroDefaultNode({required Box<Node> nodes}) {
|
||||||
final timeZone = DateTime.now().timeZoneOffset.inHours;
|
final timeZone = DateTime.now().timeZoneOffset.inHours;
|
||||||
var nodeUri = '';
|
var nodeUri = '';
|
||||||
|
@ -438,6 +451,8 @@ Future<void> checkCurrentNodes(
|
||||||
.getInt(PreferencesKey.currentLitecoinElectrumSererIdKey);
|
.getInt(PreferencesKey.currentLitecoinElectrumSererIdKey);
|
||||||
final currentHavenNodeId = sharedPreferences
|
final currentHavenNodeId = sharedPreferences
|
||||||
.getInt(PreferencesKey.currentHavenNodeIdKey);
|
.getInt(PreferencesKey.currentHavenNodeIdKey);
|
||||||
|
final currentEthereumNodeId = sharedPreferences
|
||||||
|
.getInt(PreferencesKey.currentEthereumNodeIdKey);
|
||||||
final currentMoneroNode = nodeSource.values.firstWhereOrNull(
|
final currentMoneroNode = nodeSource.values.firstWhereOrNull(
|
||||||
(node) => node.key == currentMoneroNodeId);
|
(node) => node.key == currentMoneroNodeId);
|
||||||
final currentBitcoinElectrumServer = nodeSource.values.firstWhereOrNull(
|
final currentBitcoinElectrumServer = nodeSource.values.firstWhereOrNull(
|
||||||
|
@ -446,6 +461,8 @@ Future<void> checkCurrentNodes(
|
||||||
(node) => node.key == currentLitecoinElectrumSeverId);
|
(node) => node.key == currentLitecoinElectrumSeverId);
|
||||||
final currentHavenNodeServer = nodeSource.values.firstWhereOrNull(
|
final currentHavenNodeServer = nodeSource.values.firstWhereOrNull(
|
||||||
(node) => node.key == currentHavenNodeId);
|
(node) => node.key == currentHavenNodeId);
|
||||||
|
final currentEthereumNodeServer = nodeSource.values.firstWhereOrNull(
|
||||||
|
(node) => node.key == currentEthereumNodeId);
|
||||||
|
|
||||||
if (currentMoneroNode == null) {
|
if (currentMoneroNode == null) {
|
||||||
final newCakeWalletNode =
|
final newCakeWalletNode =
|
||||||
|
@ -479,6 +496,13 @@ Future<void> checkCurrentNodes(
|
||||||
await sharedPreferences.setInt(
|
await sharedPreferences.setInt(
|
||||||
PreferencesKey.currentHavenNodeIdKey, node.key as int);
|
PreferencesKey.currentHavenNodeIdKey, node.key as int);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (currentEthereumNodeServer == null) {
|
||||||
|
final node = Node(uri: ethereumDefaultNodeUri, type: WalletType.ethereum);
|
||||||
|
await nodeSource.add(node);
|
||||||
|
await sharedPreferences.setInt(
|
||||||
|
PreferencesKey.currentEthereumNodeIdKey, node.key as int);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> resetBitcoinElectrumServer(
|
Future<void> resetBitcoinElectrumServer(
|
||||||
|
@ -522,8 +546,26 @@ Future<void> migrateExchangeStatus(SharedPreferences sharedPreferences) async {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await sharedPreferences.setInt(PreferencesKey.exchangeStatusKey, isExchangeDisabled
|
await sharedPreferences.setInt(PreferencesKey.exchangeStatusKey, isExchangeDisabled
|
||||||
? ExchangeApiMode.disabled.raw : ExchangeApiMode.enabled.raw);
|
? ExchangeApiMode.disabled.raw : ExchangeApiMode.enabled.raw);
|
||||||
|
|
||||||
await sharedPreferences.remove(PreferencesKey.disableExchangeKey);
|
await sharedPreferences.remove(PreferencesKey.disableExchangeKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> addEthereumNodeList({required Box<Node> nodes}) async {
|
||||||
|
final nodeList = await loadDefaultEthereumNodes();
|
||||||
|
for (var node in nodeList) {
|
||||||
|
if (nodes.values.firstWhereOrNull((element) => element.uriRaw == node.uriRaw) == null) {
|
||||||
|
await nodes.add(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> changeEthereumCurrentNodeToDefault(
|
||||||
|
{required SharedPreferences sharedPreferences,
|
||||||
|
required Box<Node> nodes}) async {
|
||||||
|
final node = getEthereumDefaultNode(nodes: nodes);
|
||||||
|
final nodeId = node?.key as int? ?? 0;
|
||||||
|
|
||||||
|
await sharedPreferences.setInt(PreferencesKey.currentEthereumNodeIdKey, nodeId);
|
||||||
|
}
|
||||||
|
|
|
@ -46,6 +46,7 @@ class MainActions {
|
||||||
switch (walletType) {
|
switch (walletType) {
|
||||||
case WalletType.bitcoin:
|
case WalletType.bitcoin:
|
||||||
case WalletType.litecoin:
|
case WalletType.litecoin:
|
||||||
|
case WalletType.ethereum:
|
||||||
if (viewModel.isEnabledBuyAction) {
|
if (viewModel.isEnabledBuyAction) {
|
||||||
final uri = getIt.get<OnRamperBuyProvider>().requestUrl();
|
final uri = getIt.get<OnRamperBuyProvider>().requestUrl();
|
||||||
if (DeviceInfo.instance.isMobile) {
|
if (DeviceInfo.instance.isMobile) {
|
||||||
|
@ -116,6 +117,7 @@ class MainActions {
|
||||||
switch (walletType) {
|
switch (walletType) {
|
||||||
case WalletType.bitcoin:
|
case WalletType.bitcoin:
|
||||||
case WalletType.litecoin:
|
case WalletType.litecoin:
|
||||||
|
case WalletType.ethereum:
|
||||||
if (viewModel.isEnabledSellAction) {
|
if (viewModel.isEnabledSellAction) {
|
||||||
final moonPaySellProvider = MoonPaySellProvider();
|
final moonPaySellProvider = MoonPaySellProvider();
|
||||||
final uri = await moonPaySellProvider.requestUrl(
|
final uri = await moonPaySellProvider.requestUrl(
|
||||||
|
|
|
@ -70,6 +70,22 @@ Future<List<Node>> loadDefaultHavenNodes() async {
|
||||||
return nodes;
|
return nodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<List<Node>> loadDefaultEthereumNodes() async {
|
||||||
|
final nodesRaw = await rootBundle.loadString('assets/ethereum_server_list.yml');
|
||||||
|
final loadedNodes = loadYaml(nodesRaw) as YamlList;
|
||||||
|
final nodes = <Node>[];
|
||||||
|
|
||||||
|
for (final raw in loadedNodes) {
|
||||||
|
if (raw is Map) {
|
||||||
|
final node = Node.fromMap(Map<String, Object>.from(raw));
|
||||||
|
node.type = WalletType.ethereum;
|
||||||
|
nodes.add(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nodes;
|
||||||
|
}
|
||||||
|
|
||||||
Future resetToDefault(Box<Node> nodeSource) async {
|
Future resetToDefault(Box<Node> nodeSource) async {
|
||||||
final moneroNodes = await loadDefaultNodes();
|
final moneroNodes = await loadDefaultNodes();
|
||||||
final bitcoinElectrumServerList = await loadBitcoinElectrumServerList();
|
final bitcoinElectrumServerList = await loadBitcoinElectrumServerList();
|
||||||
|
|
|
@ -5,6 +5,7 @@ class PreferencesKey {
|
||||||
static const currentBitcoinElectrumSererIdKey = 'current_node_id_btc';
|
static const currentBitcoinElectrumSererIdKey = 'current_node_id_btc';
|
||||||
static const currentLitecoinElectrumSererIdKey = 'current_node_id_ltc';
|
static const currentLitecoinElectrumSererIdKey = 'current_node_id_ltc';
|
||||||
static const currentHavenNodeIdKey = 'current_node_id_xhv';
|
static const currentHavenNodeIdKey = 'current_node_id_xhv';
|
||||||
|
static const currentEthereumNodeIdKey = 'current_node_id_eth';
|
||||||
static const currentFiatCurrencyKey = 'current_fiat_currency';
|
static const currentFiatCurrencyKey = 'current_fiat_currency';
|
||||||
static const currentTransactionPriorityKeyLegacy = 'current_fee_priority';
|
static const currentTransactionPriorityKeyLegacy = 'current_fee_priority';
|
||||||
static const currentBalanceDisplayModeKey = 'current_balance_display_mode';
|
static const currentBalanceDisplayModeKey = 'current_balance_display_mode';
|
||||||
|
@ -31,6 +32,7 @@ class PreferencesKey {
|
||||||
static const bitcoinTransactionPriority = 'current_fee_priority_bitcoin';
|
static const bitcoinTransactionPriority = 'current_fee_priority_bitcoin';
|
||||||
static const havenTransactionPriority = 'current_fee_priority_haven';
|
static const havenTransactionPriority = 'current_fee_priority_haven';
|
||||||
static const litecoinTransactionPriority = 'current_fee_priority_litecoin';
|
static const litecoinTransactionPriority = 'current_fee_priority_litecoin';
|
||||||
|
static const ethereumTransactionPriority = 'current_fee_priority_ethereum';
|
||||||
static const shouldShowReceiveWarning = 'should_show_receive_warning';
|
static const shouldShowReceiveWarning = 'should_show_receive_warning';
|
||||||
static const shouldShowYatPopup = 'should_show_yat_popup';
|
static const shouldShowYatPopup = 'should_show_yat_popup';
|
||||||
static const moneroWalletPasswordUpdateV1Base = 'monero_wallet_update_v1';
|
static const moneroWalletPasswordUpdateV1Base = 'monero_wallet_update_v1';
|
||||||
|
@ -38,6 +40,9 @@ class PreferencesKey {
|
||||||
static const lastAuthTimeMilliseconds = 'last_auth_time_milliseconds';
|
static const lastAuthTimeMilliseconds = 'last_auth_time_milliseconds';
|
||||||
static const lastPopupDate = 'last_popup_date';
|
static const lastPopupDate = 'last_popup_date';
|
||||||
static const lastAppReviewDate = 'last_app_review_date';
|
static const lastAppReviewDate = 'last_app_review_date';
|
||||||
|
static const sortBalanceBy = 'sort_balance_by';
|
||||||
|
static const pinNativeTokenAtTop = 'pin_native_token_at_top';
|
||||||
|
static const useEtherscan = 'use_etherscan';
|
||||||
|
|
||||||
static String moneroWalletUpdateV1Key(String name) =>
|
static String moneroWalletUpdateV1Key(String name) =>
|
||||||
'${PreferencesKey.moneroWalletPasswordUpdateV1Base}_${name}';
|
'${PreferencesKey.moneroWalletPasswordUpdateV1Base}_${name}';
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||||
|
import 'package:cake_wallet/ethereum/ethereum.dart';
|
||||||
import 'package:cake_wallet/haven/haven.dart';
|
import 'package:cake_wallet/haven/haven.dart';
|
||||||
import 'package:cake_wallet/monero/monero.dart';
|
import 'package:cake_wallet/monero/monero.dart';
|
||||||
import 'package:cw_core/transaction_priority.dart';
|
import 'package:cw_core/transaction_priority.dart';
|
||||||
|
@ -14,6 +15,8 @@ List<TransactionPriority> priorityForWalletType(WalletType type) {
|
||||||
return bitcoin!.getLitecoinTransactionPriorities();
|
return bitcoin!.getLitecoinTransactionPriorities();
|
||||||
case WalletType.haven:
|
case WalletType.haven:
|
||||||
return haven!.getTransactionPriorities();
|
return haven!.getTransactionPriorities();
|
||||||
|
case WalletType.ethereum:
|
||||||
|
return ethereum!.getTransactionPriorities();
|
||||||
default:
|
default:
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
19
lib/entities/sort_balance_types.dart
Normal file
19
lib/entities/sort_balance_types.dart
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
|
|
||||||
|
enum SortBalanceBy {
|
||||||
|
FiatBalance,
|
||||||
|
GrossBalance,
|
||||||
|
Alphabetical;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
switch (this) {
|
||||||
|
case SortBalanceBy.FiatBalance:
|
||||||
|
return S.current.fiat_balance;
|
||||||
|
case SortBalanceBy.GrossBalance:
|
||||||
|
return S.current.gross_balance;
|
||||||
|
case SortBalanceBy.Alphabetical:
|
||||||
|
return S.current.alphabetical;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -55,5 +55,5 @@ class Template extends HiveObject {
|
||||||
|
|
||||||
String get amount => amountRaw ?? '';
|
String get amount => amountRaw ?? '';
|
||||||
|
|
||||||
List<Template>? get additionalRecipients => additionalRecipientsRaw ?? null;
|
List<Template>? get additionalRecipients => additionalRecipientsRaw;
|
||||||
}
|
}
|
||||||
|
|
126
lib/ethereum/cw_ethereum.dart
Normal file
126
lib/ethereum/cw_ethereum.dart
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
part of 'ethereum.dart';
|
||||||
|
|
||||||
|
class CWEthereum extends Ethereum {
|
||||||
|
@override
|
||||||
|
List<String> getEthereumWordList(String language) => EthereumMnemonics.englishWordlist;
|
||||||
|
|
||||||
|
WalletService createEthereumWalletService(Box<WalletInfo> walletInfoSource) =>
|
||||||
|
EthereumWalletService(walletInfoSource);
|
||||||
|
|
||||||
|
@override
|
||||||
|
WalletCredentials createEthereumNewWalletCredentials({
|
||||||
|
required String name,
|
||||||
|
WalletInfo? walletInfo,
|
||||||
|
}) =>
|
||||||
|
EthereumNewWalletCredentials(name: name, walletInfo: walletInfo);
|
||||||
|
|
||||||
|
@override
|
||||||
|
WalletCredentials createEthereumRestoreWalletFromSeedCredentials({
|
||||||
|
required String name,
|
||||||
|
required String mnemonic,
|
||||||
|
required String password,
|
||||||
|
}) =>
|
||||||
|
EthereumRestoreWalletFromSeedCredentials(name: name, password: password, mnemonic: mnemonic);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String getAddress(WalletBase wallet) => (wallet as EthereumWallet).walletAddresses.address;
|
||||||
|
|
||||||
|
@override
|
||||||
|
TransactionPriority getDefaultTransactionPriority() => EthereumTransactionPriority.medium;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<TransactionPriority> getTransactionPriorities() => EthereumTransactionPriority.all;
|
||||||
|
|
||||||
|
@override
|
||||||
|
TransactionPriority deserializeEthereumTransactionPriority(int raw) =>
|
||||||
|
EthereumTransactionPriority.deserialize(raw: raw);
|
||||||
|
|
||||||
|
Object createEthereumTransactionCredentials(
|
||||||
|
List<Output> outputs, {
|
||||||
|
required TransactionPriority priority,
|
||||||
|
required CryptoCurrency currency,
|
||||||
|
int? feeRate,
|
||||||
|
}) =>
|
||||||
|
EthereumTransactionCredentials(
|
||||||
|
outputs
|
||||||
|
.map((out) => OutputInfo(
|
||||||
|
fiatAmount: out.fiatAmount,
|
||||||
|
cryptoAmount: out.cryptoAmount,
|
||||||
|
address: out.address,
|
||||||
|
note: out.note,
|
||||||
|
sendAll: out.sendAll,
|
||||||
|
extractedAddress: out.extractedAddress,
|
||||||
|
isParsedAddress: out.isParsedAddress,
|
||||||
|
formattedCryptoAmount: out.formattedCryptoAmount))
|
||||||
|
.toList(),
|
||||||
|
priority: priority as EthereumTransactionPriority,
|
||||||
|
currency: currency,
|
||||||
|
feeRate: feeRate,
|
||||||
|
);
|
||||||
|
|
||||||
|
Object createEthereumTransactionCredentialsRaw(
|
||||||
|
List<OutputInfo> outputs, {
|
||||||
|
TransactionPriority? priority,
|
||||||
|
required CryptoCurrency currency,
|
||||||
|
required int feeRate,
|
||||||
|
}) =>
|
||||||
|
EthereumTransactionCredentials(
|
||||||
|
outputs,
|
||||||
|
priority: priority as EthereumTransactionPriority?,
|
||||||
|
currency: currency,
|
||||||
|
feeRate: feeRate,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
int formatterEthereumParseAmount(String amount) => EthereumFormatter.parseEthereumAmount(amount);
|
||||||
|
|
||||||
|
@override
|
||||||
|
double formatterEthereumAmountToDouble(
|
||||||
|
{TransactionInfo? transaction, BigInt? amount, int exponent = 18}) {
|
||||||
|
assert(transaction != null || amount != null);
|
||||||
|
|
||||||
|
if (transaction != null) {
|
||||||
|
transaction as EthereumTransactionInfo;
|
||||||
|
return transaction.ethAmount / BigInt.from(10).pow(transaction.exponent);
|
||||||
|
} else {
|
||||||
|
return (amount!) / BigInt.from(10).pow(exponent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Erc20Token> getERC20Currencies(WalletBase wallet) {
|
||||||
|
final ethereumWallet = wallet as EthereumWallet;
|
||||||
|
return ethereumWallet.erc20Currencies;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> addErc20Token(WalletBase wallet, Erc20Token token) async =>
|
||||||
|
await (wallet as EthereumWallet).addErc20Token(token);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> deleteErc20Token(WalletBase wallet, Erc20Token token) async =>
|
||||||
|
await (wallet as EthereumWallet).deleteErc20Token(token);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Erc20Token?> getErc20Token(WalletBase wallet, String contractAddress) async {
|
||||||
|
final ethereumWallet = wallet as EthereumWallet;
|
||||||
|
return await ethereumWallet.getErc20Token(contractAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction) {
|
||||||
|
transaction as EthereumTransactionInfo;
|
||||||
|
if (transaction.tokenSymbol == CryptoCurrency.eth.title) {
|
||||||
|
return CryptoCurrency.eth;
|
||||||
|
}
|
||||||
|
|
||||||
|
wallet as EthereumWallet;
|
||||||
|
return wallet.erc20Currencies
|
||||||
|
.firstWhere((element) => transaction.tokenSymbol == element.symbol);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void updateEtherscanUsageState(WalletBase wallet, bool isEnabled) {
|
||||||
|
(wallet as EthereumWallet).updateEtherscanUsageState(isEnabled);
|
||||||
|
}
|
||||||
|
}
|
|
@ -141,7 +141,7 @@ Future<void> main() async {
|
||||||
transactionDescriptions: transactionDescriptions,
|
transactionDescriptions: transactionDescriptions,
|
||||||
secureStorage: secureStorage,
|
secureStorage: secureStorage,
|
||||||
anonpayInvoiceInfo: anonpayInvoiceInfo,
|
anonpayInvoiceInfo: anonpayInvoiceInfo,
|
||||||
initialMigrationVersion: 19);
|
initialMigrationVersion: 21);
|
||||||
runApp(App());
|
runApp(App());
|
||||||
}, (error, stackTrace) async {
|
}, (error, stackTrace) async {
|
||||||
ExceptionHandler.onError(FlutterErrorDetails(exception: error, stack: stackTrace));
|
ExceptionHandler.onError(FlutterErrorDetails(exception: error, stack: stackTrace));
|
||||||
|
|
|
@ -2,6 +2,7 @@ import 'dart:async';
|
||||||
import 'package:cake_wallet/core/fiat_conversion_service.dart';
|
import 'package:cake_wallet/core/fiat_conversion_service.dart';
|
||||||
import 'package:cake_wallet/entities/fiat_api_mode.dart';
|
import 'package:cake_wallet/entities/fiat_api_mode.dart';
|
||||||
import 'package:cake_wallet/entities/update_haven_rate.dart';
|
import 'package:cake_wallet/entities/update_haven_rate.dart';
|
||||||
|
import 'package:cake_wallet/ethereum/ethereum.dart';
|
||||||
import 'package:cake_wallet/store/app_store.dart';
|
import 'package:cake_wallet/store/app_store.dart';
|
||||||
import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
|
import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
|
||||||
import 'package:cake_wallet/store/settings_store.dart';
|
import 'package:cake_wallet/store/settings_store.dart';
|
||||||
|
@ -31,6 +32,20 @@ Future<void> startFiatRateUpdate(
|
||||||
fiat: settingsStore.fiatCurrency,
|
fiat: settingsStore.fiatCurrency,
|
||||||
torOnly: settingsStore.fiatApiMode == FiatApiMode.torOnly);
|
torOnly: settingsStore.fiatApiMode == FiatApiMode.torOnly);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (appStore.wallet!.type == WalletType.ethereum) {
|
||||||
|
final currencies =
|
||||||
|
ethereum!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled);
|
||||||
|
|
||||||
|
for (final currency in currencies) {
|
||||||
|
() async {
|
||||||
|
fiatConversionStore.prices[currency] = await FiatConversionService.fetchPrice(
|
||||||
|
crypto: currency,
|
||||||
|
fiat: settingsStore.fiatCurrency,
|
||||||
|
torOnly: settingsStore.fiatApiMode == FiatApiMode.torOnly);
|
||||||
|
}.call();
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print(e);
|
print(e);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:cake_wallet/entities/fiat_api_mode.dart';
|
import 'package:cake_wallet/entities/fiat_api_mode.dart';
|
||||||
import 'package:cake_wallet/entities/fiat_currency.dart';
|
|
||||||
import 'package:cake_wallet/entities/update_haven_rate.dart';
|
import 'package:cake_wallet/entities/update_haven_rate.dart';
|
||||||
|
import 'package:cake_wallet/ethereum/ethereum.dart';
|
||||||
import 'package:cw_core/transaction_history.dart';
|
import 'package:cw_core/transaction_history.dart';
|
||||||
import 'package:cw_core/balance.dart';
|
import 'package:cw_core/balance.dart';
|
||||||
import 'package:cw_core/transaction_info.dart';
|
import 'package:cw_core/transaction_info.dart';
|
||||||
|
@ -97,6 +97,20 @@ void startCurrentWalletChangeReaction(AppStore appStore,
|
||||||
crypto: wallet.currency,
|
crypto: wallet.currency,
|
||||||
fiat: settingsStore.fiatCurrency,
|
fiat: settingsStore.fiatCurrency,
|
||||||
torOnly: settingsStore.fiatApiMode == FiatApiMode.torOnly);
|
torOnly: settingsStore.fiatApiMode == FiatApiMode.torOnly);
|
||||||
|
|
||||||
|
if (wallet.type == WalletType.ethereum) {
|
||||||
|
final currencies =
|
||||||
|
ethereum!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled);
|
||||||
|
|
||||||
|
for (final currency in currencies) {
|
||||||
|
() async {
|
||||||
|
fiatConversionStore.prices[currency] = await FiatConversionService.fetchPrice(
|
||||||
|
crypto: currency,
|
||||||
|
fiat: settingsStore.fiatCurrency,
|
||||||
|
torOnly: settingsStore.fiatApiMode == FiatApiMode.torOnly);
|
||||||
|
}.call();
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print(e.toString());
|
print(e.toString());
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,8 @@ import 'package:cake_wallet/src/screens/backup/edit_backup_password_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/buy/buy_webview_page.dart';
|
import 'package:cake_wallet/src/screens/buy/buy_webview_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/buy/webview_page.dart';
|
import 'package:cake_wallet/src/screens/buy/webview_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/buy/pre_order_page.dart';
|
import 'package:cake_wallet/src/screens/buy/pre_order_page.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/dashboard/edit_token_page.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/dashboard/home_settings_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/restore/sweeping_wallet_page.dart';
|
import 'package:cake_wallet/src/screens/restore/sweeping_wallet_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/receive/anonpay_invoice_page.dart';
|
import 'package:cake_wallet/src/screens/receive/anonpay_invoice_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/receive/anonpay_receive_page.dart';
|
import 'package:cake_wallet/src/screens/receive/anonpay_receive_page.dart';
|
||||||
|
@ -313,7 +315,7 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
||||||
return CupertinoPageRoute<void>(
|
return CupertinoPageRoute<void>(
|
||||||
fullscreenDialog: true,
|
fullscreenDialog: true,
|
||||||
builder: (_) => getIt.get<SecurityBackupPage>());
|
builder: (_) => getIt.get<SecurityBackupPage>());
|
||||||
|
|
||||||
case Routes.privacyPage:
|
case Routes.privacyPage:
|
||||||
return CupertinoPageRoute<void>(
|
return CupertinoPageRoute<void>(
|
||||||
fullscreenDialog: true,
|
fullscreenDialog: true,
|
||||||
|
@ -328,7 +330,7 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
||||||
return CupertinoPageRoute<void>(
|
return CupertinoPageRoute<void>(
|
||||||
fullscreenDialog: true,
|
fullscreenDialog: true,
|
||||||
builder: (_) => getIt.get<OtherSettingsPage>());
|
builder: (_) => getIt.get<OtherSettingsPage>());
|
||||||
|
|
||||||
case Routes.newNode:
|
case Routes.newNode:
|
||||||
final args = settings.arguments as Map<String, dynamic>?;
|
final args = settings.arguments as Map<String, dynamic>?;
|
||||||
return CupertinoPageRoute<void>(
|
return CupertinoPageRoute<void>(
|
||||||
|
@ -336,7 +338,7 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
||||||
param1: args?['editingNode'] as Node?,
|
param1: args?['editingNode'] as Node?,
|
||||||
param2: args?['isSelected'] as bool?));
|
param2: args?['isSelected'] as bool?));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
case Routes.accountCreation:
|
case Routes.accountCreation:
|
||||||
return CupertinoPageRoute<String>(
|
return CupertinoPageRoute<String>(
|
||||||
|
@ -466,7 +468,7 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
||||||
fullscreenDialog: true,
|
fullscreenDialog: true,
|
||||||
builder: (_) => getIt.get<IoniaWelcomePage>(),
|
builder: (_) => getIt.get<IoniaWelcomePage>(),
|
||||||
);
|
);
|
||||||
|
|
||||||
case Routes.ioniaLoginPage:
|
case Routes.ioniaLoginPage:
|
||||||
return CupertinoPageRoute<void>( builder: (_) => getIt.get<IoniaLoginPage>());
|
return CupertinoPageRoute<void>( builder: (_) => getIt.get<IoniaLoginPage>());
|
||||||
|
|
||||||
|
@ -480,7 +482,7 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
||||||
case Routes.ioniaBuyGiftCardPage:
|
case Routes.ioniaBuyGiftCardPage:
|
||||||
final args = settings.arguments as List;
|
final args = settings.arguments as List;
|
||||||
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaBuyGiftCardPage>(param1: args));
|
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaBuyGiftCardPage>(param1: args));
|
||||||
|
|
||||||
case Routes.ioniaBuyGiftCardDetailPage:
|
case Routes.ioniaBuyGiftCardDetailPage:
|
||||||
final args = settings.arguments as List;
|
final args = settings.arguments as List;
|
||||||
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaBuyGiftCardDetailPage>(param1: args));
|
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaBuyGiftCardDetailPage>(param1: args));
|
||||||
|
@ -497,7 +499,7 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
||||||
|
|
||||||
case Routes.ioniaAccountPage:
|
case Routes.ioniaAccountPage:
|
||||||
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaAccountPage>());
|
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaAccountPage>());
|
||||||
|
|
||||||
case Routes.ioniaAccountCardsPage:
|
case Routes.ioniaAccountCardsPage:
|
||||||
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaAccountCardsPage>());
|
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaAccountCardsPage>());
|
||||||
|
|
||||||
|
@ -508,11 +510,11 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
||||||
case Routes.ioniaGiftCardDetailPage:
|
case Routes.ioniaGiftCardDetailPage:
|
||||||
final args = settings.arguments as List;
|
final args = settings.arguments as List;
|
||||||
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaGiftCardDetailPage>(param1: args.first));
|
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaGiftCardDetailPage>(param1: args.first));
|
||||||
|
|
||||||
case Routes.ioniaCustomRedeemPage:
|
case Routes.ioniaCustomRedeemPage:
|
||||||
final args = settings.arguments as List;
|
final args = settings.arguments as List;
|
||||||
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaCustomRedeemPage>(param1: args));
|
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaCustomRedeemPage>(param1: args));
|
||||||
|
|
||||||
case Routes.ioniaMoreOptionsPage:
|
case Routes.ioniaMoreOptionsPage:
|
||||||
final args = settings.arguments as List;
|
final args = settings.arguments as List;
|
||||||
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaMoreOptionsPage>(param1: args));
|
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaMoreOptionsPage>(param1: args));
|
||||||
|
@ -584,6 +586,25 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
||||||
case Routes.modify2FAPage:
|
case Routes.modify2FAPage:
|
||||||
return MaterialPageRoute<void>(builder: (_) => getIt.get<Modify2FAPage>());
|
return MaterialPageRoute<void>(builder: (_) => getIt.get<Modify2FAPage>());
|
||||||
|
|
||||||
|
case Routes.homeSettings:
|
||||||
|
return CupertinoPageRoute<void>(
|
||||||
|
builder: (_) => getIt.get<HomeSettingsPage>(param1: settings.arguments),
|
||||||
|
);
|
||||||
|
|
||||||
|
case Routes.editToken:
|
||||||
|
final args = settings.arguments as Map<String, dynamic>;
|
||||||
|
|
||||||
|
return CupertinoPageRoute<void>(
|
||||||
|
settings: RouteSettings(name: Routes.editToken),
|
||||||
|
builder: (_) => getIt.get<EditTokenPage>(
|
||||||
|
param1: args['homeSettingsViewModel'],
|
||||||
|
param2: {
|
||||||
|
'token': args['token'],
|
||||||
|
'contractAddress': args['contractAddress'],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return MaterialPageRoute<void>(
|
return MaterialPageRoute<void>(
|
||||||
builder: (_) => Scaffold(
|
builder: (_) => Scaffold(
|
||||||
|
|
|
@ -88,4 +88,6 @@ class Routes {
|
||||||
static const setup_2faQRPage = '/setup_2fa_qr_page';
|
static const setup_2faQRPage = '/setup_2fa_qr_page';
|
||||||
static const totpAuthCodePage = '/totp_auth_code_page';
|
static const totpAuthCodePage = '/totp_auth_code_page';
|
||||||
static const modify2FAPage = '/modify_2fa_page';
|
static const modify2FAPage = '/modify_2fa_page';
|
||||||
|
static const homeSettings = '/home_settings';
|
||||||
|
static const editToken = '/edit_token';
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
|
||||||
final bitcoinIcon = Image.asset('assets/images/bitcoin.png', height: 24, width: 24);
|
final bitcoinIcon = Image.asset('assets/images/bitcoin.png', height: 24, width: 24);
|
||||||
final litecoinIcon = Image.asset('assets/images/litecoin_icon.png', height: 24, width: 24);
|
final litecoinIcon = Image.asset('assets/images/litecoin_icon.png', height: 24, width: 24);
|
||||||
final havenIcon = Image.asset('assets/images/haven_logo.png', height: 24, width: 24);
|
final havenIcon = Image.asset('assets/images/haven_logo.png', height: 24, width: 24);
|
||||||
|
final ethereumIcon = Image.asset('assets/images/eth_icon.png', height: 24, width: 24);
|
||||||
final nonWalletTypeIcon = Image.asset('assets/images/close.png', height: 24, width: 24);
|
final nonWalletTypeIcon = Image.asset('assets/images/close.png', height: 24, width: 24);
|
||||||
|
|
||||||
Image _newWalletImage(BuildContext context) => Image.asset(
|
Image _newWalletImage(BuildContext context) => Image.asset(
|
||||||
|
@ -136,6 +137,8 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
|
||||||
return litecoinIcon;
|
return litecoinIcon;
|
||||||
case WalletType.haven:
|
case WalletType.haven:
|
||||||
return havenIcon;
|
return havenIcon;
|
||||||
|
case WalletType.ethereum:
|
||||||
|
return ethereumIcon;
|
||||||
default:
|
default:
|
||||||
return nonWalletTypeIcon;
|
return nonWalletTypeIcon;
|
||||||
}
|
}
|
||||||
|
|
309
lib/src/screens/dashboard/edit_token_page.dart
Normal file
309
lib/src/screens/dashboard/edit_token_page.dart
Normal file
|
@ -0,0 +1,309 @@
|
||||||
|
import 'package:cake_wallet/core/address_validator.dart';
|
||||||
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||||
|
import 'package:cake_wallet/src/widgets/address_text_field.dart';
|
||||||
|
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
|
||||||
|
import 'package:cake_wallet/src/widgets/checkbox_widget.dart';
|
||||||
|
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||||
|
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
|
||||||
|
import 'package:cake_wallet/view_model/dashboard/home_settings_view_model.dart';
|
||||||
|
import 'package:cw_core/erc20_token.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
|
class EditTokenPage extends BasePage {
|
||||||
|
EditTokenPage({
|
||||||
|
Key? key,
|
||||||
|
required this.homeSettingsViewModel,
|
||||||
|
this.erc20token,
|
||||||
|
this.initialContractAddress,
|
||||||
|
}) : assert(erc20token == null || initialContractAddress == null);
|
||||||
|
|
||||||
|
final HomeSettingsViewModel homeSettingsViewModel;
|
||||||
|
final Erc20Token? erc20token;
|
||||||
|
final String? initialContractAddress;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? get title => S.current.edit_token;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget body(BuildContext context) {
|
||||||
|
return EditTokenPageBody(
|
||||||
|
homeSettingsViewModel: homeSettingsViewModel,
|
||||||
|
erc20token: erc20token,
|
||||||
|
initialContractAddress: initialContractAddress,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class EditTokenPageBody extends StatefulWidget {
|
||||||
|
const EditTokenPageBody({
|
||||||
|
Key? key,
|
||||||
|
required this.homeSettingsViewModel,
|
||||||
|
this.erc20token,
|
||||||
|
this.initialContractAddress,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final HomeSettingsViewModel homeSettingsViewModel;
|
||||||
|
final Erc20Token? erc20token;
|
||||||
|
final String? initialContractAddress;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<EditTokenPageBody> createState() => _EditTokenPageBodyState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _EditTokenPageBodyState extends State<EditTokenPageBody> {
|
||||||
|
final TextEditingController _contractAddressController = TextEditingController();
|
||||||
|
final TextEditingController _tokenNameController = TextEditingController();
|
||||||
|
final TextEditingController _tokenSymbolController = TextEditingController();
|
||||||
|
final TextEditingController _tokenDecimalController = TextEditingController();
|
||||||
|
|
||||||
|
final FocusNode _contractAddressFocusNode = FocusNode();
|
||||||
|
final FocusNode _tokenNameFocusNode = FocusNode();
|
||||||
|
final FocusNode _tokenSymbolFocusNode = FocusNode();
|
||||||
|
final FocusNode _tokenDecimalFocusNode = FocusNode();
|
||||||
|
|
||||||
|
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
|
||||||
|
|
||||||
|
bool _showDisclaimer = false;
|
||||||
|
bool _disclaimerChecked = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
if (widget.erc20token != null) {
|
||||||
|
_contractAddressController.text = widget.erc20token!.contractAddress;
|
||||||
|
_tokenNameController.text = widget.erc20token!.name;
|
||||||
|
_tokenSymbolController.text = widget.erc20token!.symbol;
|
||||||
|
_tokenDecimalController.text = widget.erc20token!.decimal.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (widget.initialContractAddress != null) {
|
||||||
|
_contractAddressController.text = widget.initialContractAddress!;
|
||||||
|
_getTokenInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
_contractAddressFocusNode.addListener(() {
|
||||||
|
if (!_contractAddressFocusNode.hasFocus) {
|
||||||
|
_getTokenInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
final contractAddress = _contractAddressController.text;
|
||||||
|
if (contractAddress.isNotEmpty && contractAddress != widget.erc20token?.contractAddress) {
|
||||||
|
setState(() {
|
||||||
|
_showDisclaimer = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () => FocusScope.of(context).unfocus(),
|
||||||
|
child: ScrollableWithBottomSection(
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
|
content: Padding(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 25),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 16, horizontal: 28),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).accentTextTheme.bodySmall!.color!,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Image.asset('assets/images/restore_keys.png'),
|
||||||
|
const SizedBox(width: 24),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
S.of(context).warning,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: Theme.of(context).primaryTextTheme.titleLarge!.color!,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.only(top: 5),
|
||||||
|
child: Text(
|
||||||
|
S.of(context).add_token_warning,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.normal,
|
||||||
|
color: Theme.of(context).primaryTextTheme.labelSmall!.color!,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 50),
|
||||||
|
_tokenForm(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
|
||||||
|
bottomSection: Column(
|
||||||
|
children: [
|
||||||
|
if (_showDisclaimer) ...[
|
||||||
|
CheckboxWidget(
|
||||||
|
value: _disclaimerChecked,
|
||||||
|
caption: S.of(context).add_token_disclaimer_check,
|
||||||
|
onChanged: (value) {
|
||||||
|
_disclaimerChecked = value;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
SizedBox(height: 20),
|
||||||
|
],
|
||||||
|
Row(
|
||||||
|
children: <Widget>[
|
||||||
|
Expanded(
|
||||||
|
child: PrimaryButton(
|
||||||
|
onPressed: () async {
|
||||||
|
if (widget.erc20token != null) {
|
||||||
|
await widget.homeSettingsViewModel.deleteErc20Token(widget.erc20token!);
|
||||||
|
}
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
text: widget.erc20token != null ? S.of(context).delete : S.of(context).cancel,
|
||||||
|
color: Colors.red,
|
||||||
|
textColor: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 20),
|
||||||
|
Expanded(
|
||||||
|
child: PrimaryButton(
|
||||||
|
onPressed: () async {
|
||||||
|
if (_formKey.currentState!.validate() &&
|
||||||
|
(!_showDisclaimer || _disclaimerChecked)) {
|
||||||
|
await widget.homeSettingsViewModel.addErc20Token(Erc20Token(
|
||||||
|
name: _tokenNameController.text,
|
||||||
|
symbol: _tokenSymbolController.text,
|
||||||
|
contractAddress: _contractAddressController.text,
|
||||||
|
decimal: int.parse(_tokenDecimalController.text),
|
||||||
|
));
|
||||||
|
Navigator.pop(context);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
text: S.of(context).save,
|
||||||
|
color: Theme.of(context).accentTextTheme.bodyLarge!.color!,
|
||||||
|
textColor: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _getTokenInfo() async {
|
||||||
|
if (_contractAddressController.text.isNotEmpty) {
|
||||||
|
final token =
|
||||||
|
await widget.homeSettingsViewModel.getErc20Token(_contractAddressController.text);
|
||||||
|
|
||||||
|
if (token != null) {
|
||||||
|
if (_tokenNameController.text.isEmpty) _tokenNameController.text = token.name;
|
||||||
|
if (_tokenSymbolController.text.isEmpty) _tokenSymbolController.text = token.symbol;
|
||||||
|
if (_tokenDecimalController.text.isEmpty)
|
||||||
|
_tokenDecimalController.text = token.decimal.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _pasteText() async {
|
||||||
|
final value = await Clipboard.getData('text/plain');
|
||||||
|
|
||||||
|
if (value?.text?.isNotEmpty ?? false) {
|
||||||
|
_contractAddressController.text = value!.text!;
|
||||||
|
|
||||||
|
_getTokenInfo();
|
||||||
|
setState(() {
|
||||||
|
_showDisclaimer = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _tokenForm() {
|
||||||
|
return Form(
|
||||||
|
key: _formKey,
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
AddressTextField(
|
||||||
|
controller: _contractAddressController,
|
||||||
|
focusNode: _contractAddressFocusNode,
|
||||||
|
placeholder: S.of(context).token_contract_address,
|
||||||
|
options: [AddressTextFieldOption.paste],
|
||||||
|
buttonColor: Theme.of(context).hintColor,
|
||||||
|
validator: AddressValidator(type: widget.homeSettingsViewModel.nativeToken),
|
||||||
|
onPushPasteButton: (_) {
|
||||||
|
_pasteText();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
BaseTextFormField(
|
||||||
|
controller: _tokenNameController,
|
||||||
|
focusNode: _tokenNameFocusNode,
|
||||||
|
onSubmit: (_) => FocusScope.of(context).requestFocus(_tokenSymbolFocusNode),
|
||||||
|
textInputAction: TextInputAction.next,
|
||||||
|
hintText: S.of(context).token_name,
|
||||||
|
validator: (text) {
|
||||||
|
if (text?.isNotEmpty ?? false) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return S.of(context).field_required;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
BaseTextFormField(
|
||||||
|
controller: _tokenSymbolController,
|
||||||
|
focusNode: _tokenSymbolFocusNode,
|
||||||
|
onSubmit: (_) => FocusScope.of(context).requestFocus(_tokenDecimalFocusNode),
|
||||||
|
textInputAction: TextInputAction.next,
|
||||||
|
hintText: S.of(context).token_symbol,
|
||||||
|
validator: (text) {
|
||||||
|
if (text?.isNotEmpty ?? false) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return S.of(context).field_required;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
BaseTextFormField(
|
||||||
|
controller: _tokenDecimalController,
|
||||||
|
focusNode: _tokenDecimalFocusNode,
|
||||||
|
textInputAction: TextInputAction.done,
|
||||||
|
hintText: S.of(context).token_decimal,
|
||||||
|
validator: (text) {
|
||||||
|
if (text?.isEmpty ?? true) {
|
||||||
|
return S.of(context).field_required;
|
||||||
|
}
|
||||||
|
if (int.tryParse(text!) == null) {
|
||||||
|
return S.of(context).invalid_input;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
SizedBox(height: 24),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
164
lib/src/screens/dashboard/home_settings_page.dart
Normal file
164
lib/src/screens/dashboard/home_settings_page.dart
Normal file
|
@ -0,0 +1,164 @@
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:cake_wallet/core/address_validator.dart';
|
||||||
|
import 'package:cake_wallet/entities/sort_balance_types.dart';
|
||||||
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
|
import 'package:cake_wallet/routes.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/settings/widgets/settings_picker_cell.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell.dart';
|
||||||
|
import 'package:cake_wallet/view_model/dashboard/home_settings_view_model.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||||
|
|
||||||
|
class HomeSettingsPage extends BasePage {
|
||||||
|
HomeSettingsPage(this._homeSettingsViewModel);
|
||||||
|
|
||||||
|
final HomeSettingsViewModel _homeSettingsViewModel;
|
||||||
|
|
||||||
|
final TextEditingController _searchController = TextEditingController();
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? get title => S.current.home_screen_settings;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget body(BuildContext context) {
|
||||||
|
return SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Observer(
|
||||||
|
builder: (_) => SettingsPickerCell<SortBalanceBy>(
|
||||||
|
title: S.current.sort_by,
|
||||||
|
items: SortBalanceBy.values,
|
||||||
|
selectedItem: _homeSettingsViewModel.sortBalanceBy,
|
||||||
|
onItemSelected: _homeSettingsViewModel.setSortBalanceBy,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Divider(color: Theme.of(context).primaryTextTheme.bodySmall!.decorationColor!),
|
||||||
|
Observer(
|
||||||
|
builder: (_) => SettingsSwitcherCell(
|
||||||
|
title: S.of(context).pin_at_top(_homeSettingsViewModel.nativeToken.title),
|
||||||
|
value: _homeSettingsViewModel.pinNativeToken,
|
||||||
|
onValueChange: (_, bool value) {
|
||||||
|
_homeSettingsViewModel.setPinNativeToken(value);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Divider(color: Theme.of(context).primaryTextTheme.bodySmall!.decorationColor!),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsetsDirectional.only(start: 16),
|
||||||
|
child: TextFormField(
|
||||||
|
controller: _searchController,
|
||||||
|
style: TextStyle(color: Theme.of(context).primaryTextTheme.titleLarge!.color!),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: S.of(context).search_add_token,
|
||||||
|
prefixIcon: Image.asset("assets/images/search_icon.png"),
|
||||||
|
filled: true,
|
||||||
|
fillColor: Theme.of(context).accentTextTheme.displaySmall!.color!,
|
||||||
|
alignLabelWithHint: false,
|
||||||
|
contentPadding: const EdgeInsets.symmetric(vertical: 4, horizontal: 16),
|
||||||
|
enabledBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(30),
|
||||||
|
borderSide: const BorderSide(color: Colors.transparent),
|
||||||
|
),
|
||||||
|
focusedBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(30),
|
||||||
|
borderSide: const BorderSide(color: Colors.transparent),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onChanged: (String text) => _homeSettingsViewModel.changeSearchText(text),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
RawMaterialButton(
|
||||||
|
onPressed: () async {
|
||||||
|
Navigator.pushNamed(context, Routes.editToken, arguments: {
|
||||||
|
'homeSettingsViewModel': _homeSettingsViewModel,
|
||||||
|
if (AddressValidator(type: _homeSettingsViewModel.nativeToken)
|
||||||
|
.isValid(_searchController.text))
|
||||||
|
'contractAddress': _searchController.text,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
elevation: 0,
|
||||||
|
fillColor: Theme.of(context).accentTextTheme.bodySmall!.color!,
|
||||||
|
child: Icon(
|
||||||
|
Icons.add,
|
||||||
|
color: Theme.of(context).primaryTextTheme.titleLarge!.color!,
|
||||||
|
size: 22.0,
|
||||||
|
),
|
||||||
|
padding: EdgeInsets.all(12),
|
||||||
|
shape: CircleBorder(),
|
||||||
|
splashColor: Theme.of(context).accentTextTheme.bodySmall!.color!,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 16, left: 16, right: 16),
|
||||||
|
child: Observer(
|
||||||
|
builder: (_) => ListView.builder(
|
||||||
|
itemCount: _homeSettingsViewModel.tokens.length,
|
||||||
|
shrinkWrap: true,
|
||||||
|
physics: NeverScrollableScrollPhysics(),
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return Container(
|
||||||
|
margin: EdgeInsets.only(top: 16),
|
||||||
|
child: Observer(
|
||||||
|
builder: (_) {
|
||||||
|
final token = _homeSettingsViewModel.tokens.elementAt(index);
|
||||||
|
|
||||||
|
return SettingsSwitcherCell(
|
||||||
|
title: "${token.name} "
|
||||||
|
"(${token.symbol})",
|
||||||
|
value: token.enabled,
|
||||||
|
onValueChange: (_, bool value) {
|
||||||
|
_homeSettingsViewModel.changeTokenAvailability(token, value);
|
||||||
|
},
|
||||||
|
onTap: (_) {
|
||||||
|
Navigator.pushNamed(context, Routes.editToken, arguments: {
|
||||||
|
'homeSettingsViewModel': _homeSettingsViewModel,
|
||||||
|
'token': token,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
leading: token.iconPath != null
|
||||||
|
? Container(
|
||||||
|
child: Image.asset(
|
||||||
|
token.iconPath!,
|
||||||
|
height: 30.0,
|
||||||
|
width: 30.0,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: Container(
|
||||||
|
height: 30.0,
|
||||||
|
width: 30.0,
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
token.symbol.substring(0, min(token.symbol.length, 2)),
|
||||||
|
style: TextStyle(fontSize: 11),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
color: Colors.grey.shade400,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).accentTextTheme.bodySmall!.color!,
|
||||||
|
borderRadius: BorderRadius.circular(30),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -27,15 +27,15 @@ class AddressPage extends BasePage {
|
||||||
required this.addressListViewModel,
|
required this.addressListViewModel,
|
||||||
required this.dashboardViewModel,
|
required this.dashboardViewModel,
|
||||||
required this.receiveOptionViewModel,
|
required this.receiveOptionViewModel,
|
||||||
}) : _cryptoAmountFocus = FocusNode(),
|
}) : _cryptoAmountFocus = FocusNode(),
|
||||||
_formKey = GlobalKey<FormState>(),
|
_formKey = GlobalKey<FormState>(),
|
||||||
_amountController = TextEditingController(){
|
_amountController = TextEditingController() {
|
||||||
_amountController.addListener(() {
|
_amountController.addListener(() {
|
||||||
if (_formKey.currentState!.validate()) {
|
if (_formKey.currentState!.validate()) {
|
||||||
addressListViewModel.changeAmount(
|
addressListViewModel.changeAmount(
|
||||||
_amountController.text,
|
_amountController.text,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,15 +63,11 @@ class AddressPage extends BasePage {
|
||||||
Widget? leading(BuildContext context) {
|
Widget? leading(BuildContext context) {
|
||||||
final _backButton = Icon(
|
final _backButton = Icon(
|
||||||
Icons.arrow_back_ios,
|
Icons.arrow_back_ios,
|
||||||
color: Theme.of(context)
|
color: Theme.of(context).accentTextTheme.displayMedium!.backgroundColor!,
|
||||||
.accentTextTheme!
|
|
||||||
.displayMedium!
|
|
||||||
.backgroundColor!,
|
|
||||||
size: 16,
|
size: 16,
|
||||||
);
|
);
|
||||||
final _closeButton = currentTheme.type == ThemeType.dark
|
final _closeButton =
|
||||||
? closeButtonImageDarkTheme
|
currentTheme.type == ThemeType.dark ? closeButtonImageDarkTheme : closeButtonImage;
|
||||||
: closeButtonImage;
|
|
||||||
|
|
||||||
bool isMobileView = ResponsiveLayoutUtil.instance.isMobile;
|
bool isMobileView = ResponsiveLayoutUtil.instance.isMobile;
|
||||||
|
|
||||||
|
@ -82,13 +78,10 @@ class AddressPage extends BasePage {
|
||||||
child: ButtonTheme(
|
child: ButtonTheme(
|
||||||
minWidth: double.minPositive,
|
minWidth: double.minPositive,
|
||||||
child: Semantics(
|
child: Semantics(
|
||||||
label: !isMobileView
|
label: !isMobileView ? S.of(context).close : S.of(context).seed_alert_back,
|
||||||
? S.of(context).close
|
|
||||||
: S.of(context).seed_alert_back,
|
|
||||||
child: TextButton(
|
child: TextButton(
|
||||||
style: ButtonStyle(
|
style: ButtonStyle(
|
||||||
overlayColor: MaterialStateColor.resolveWith(
|
overlayColor: MaterialStateColor.resolveWith((states) => Colors.transparent),
|
||||||
(states) => Colors.transparent),
|
|
||||||
),
|
),
|
||||||
onPressed: () => onClose(context),
|
onPressed: () => onClose(context),
|
||||||
child: !isMobileView ? _closeButton : _backButton,
|
child: !isMobileView ? _closeButton : _backButton,
|
||||||
|
@ -100,8 +93,7 @@ class AddressPage extends BasePage {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget middle(BuildContext context) =>
|
Widget middle(BuildContext context) => PresentReceiveOptionPicker(
|
||||||
PresentReceiveOptionPicker(
|
|
||||||
receiveOptionViewModel: receiveOptionViewModel,
|
receiveOptionViewModel: receiveOptionViewModel,
|
||||||
hasWhiteBackground: currentTheme.type == ThemeType.light,
|
hasWhiteBackground: currentTheme.type == ThemeType.light,
|
||||||
);
|
);
|
||||||
|
@ -136,10 +128,7 @@ class AddressPage extends BasePage {
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
Icons.share,
|
Icons.share,
|
||||||
size: 20,
|
size: 20,
|
||||||
color: Theme.of(context)
|
color: Theme.of(context).accentTextTheme.displayMedium!.backgroundColor!,
|
||||||
.accentTextTheme!
|
|
||||||
.displayMedium!
|
|
||||||
.backgroundColor!,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -180,10 +169,7 @@ class AddressPage extends BasePage {
|
||||||
tapOutsideToDismiss: true,
|
tapOutsideToDismiss: true,
|
||||||
config: KeyboardActionsConfig(
|
config: KeyboardActionsConfig(
|
||||||
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
|
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
|
||||||
keyboardBarColor: Theme.of(context)
|
keyboardBarColor: Theme.of(context).accentTextTheme.bodyLarge!.backgroundColor!,
|
||||||
.accentTextTheme!
|
|
||||||
.bodyLarge!
|
|
||||||
.backgroundColor!,
|
|
||||||
nextFocus: false,
|
nextFocus: false,
|
||||||
actions: [
|
actions: [
|
||||||
KeyboardActionsItem(
|
KeyboardActionsItem(
|
||||||
|
@ -205,62 +191,54 @@ class AddressPage extends BasePage {
|
||||||
isLight: dashboardViewModel.settingsStore.currentTheme.type ==
|
isLight: dashboardViewModel.settingsStore.currentTheme.type ==
|
||||||
ThemeType.light))),
|
ThemeType.light))),
|
||||||
Observer(builder: (_) {
|
Observer(builder: (_) {
|
||||||
return addressListViewModel.hasAddressList
|
if (addressListViewModel.hasAddressList) {
|
||||||
? GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () => Navigator.of(context).pushNamed(Routes.receive),
|
onTap: () => Navigator.of(context).pushNamed(Routes.receive),
|
||||||
child: Container(
|
child: Container(
|
||||||
height: 50,
|
height: 50,
|
||||||
padding: EdgeInsets.only(left: 24, right: 12),
|
padding: EdgeInsets.only(left: 24, right: 12),
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.all(Radius.circular(25)),
|
borderRadius: BorderRadius.all(Radius.circular(25)),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: Theme.of(context)
|
color: Theme.of(context).textTheme.titleMedium!.color!, width: 1),
|
||||||
.textTheme!
|
color: Theme.of(context).textTheme.titleLarge!.backgroundColor!),
|
||||||
.titleMedium!
|
child: Row(
|
||||||
.color!,
|
mainAxisSize: MainAxisSize.max,
|
||||||
width: 1),
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
color: Theme.of(context)
|
children: <Widget>[
|
||||||
.textTheme!
|
Observer(
|
||||||
.titleLarge!
|
builder: (_) => Text(
|
||||||
.backgroundColor!),
|
addressListViewModel.hasAccounts
|
||||||
child: Row(
|
? S.of(context).accounts_subaddresses
|
||||||
mainAxisSize: MainAxisSize.max,
|
: S.of(context).addresses,
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
style: TextStyle(
|
||||||
children: <Widget>[
|
fontSize: 14,
|
||||||
Observer(
|
fontWeight: FontWeight.w500,
|
||||||
builder: (_) => Text(
|
color: Theme.of(context)
|
||||||
addressListViewModel.hasAccounts
|
.accentTextTheme.displayMedium!
|
||||||
? S.of(context).accounts_subaddresses
|
.backgroundColor!),
|
||||||
: S.of(context).addresses,
|
)),
|
||||||
style: TextStyle(
|
Icon(
|
||||||
fontSize: 14,
|
Icons.arrow_forward_ios,
|
||||||
fontWeight: FontWeight.w500,
|
size: 14,
|
||||||
color: Theme.of(context)
|
color:
|
||||||
.accentTextTheme!
|
Theme.of(context).accentTextTheme.displayMedium!.backgroundColor!,
|
||||||
.displayMedium!
|
)
|
||||||
.backgroundColor!),
|
],
|
||||||
)),
|
),
|
||||||
Icon(
|
),
|
||||||
Icons.arrow_forward_ios,
|
);
|
||||||
size: 14,
|
} else if (addressListViewModel.showElectrumAddressDisclaimer) {
|
||||||
color: Theme.of(context)
|
return Text(S.of(context).electrum_address_disclaimer,
|
||||||
.accentTextTheme!
|
textAlign: TextAlign.center,
|
||||||
.displayMedium!
|
style: TextStyle(
|
||||||
.backgroundColor!,
|
fontSize: 15,
|
||||||
)
|
color:
|
||||||
],
|
Theme.of(context).accentTextTheme.displaySmall!.backgroundColor!));
|
||||||
),
|
} else {
|
||||||
),
|
return const SizedBox();
|
||||||
)
|
}
|
||||||
: Text(S.of(context).electrum_address_disclaimer,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 15,
|
|
||||||
color: Theme.of(context)
|
|
||||||
.accentTextTheme!
|
|
||||||
.displaySmall!
|
|
||||||
.backgroundColor!));
|
|
||||||
})
|
})
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
|
import 'package:cake_wallet/routes.dart';
|
||||||
import 'package:cake_wallet/src/screens/exchange_trade/information_page.dart';
|
import 'package:cake_wallet/src/screens/exchange_trade/information_page.dart';
|
||||||
import 'package:cake_wallet/store/settings_store.dart';
|
import 'package:cake_wallet/store/settings_store.dart';
|
||||||
import 'package:cake_wallet/themes/theme_base.dart';
|
import 'package:cake_wallet/themes/theme_base.dart';
|
||||||
import 'package:cake_wallet/utils/feature_flag.dart';
|
import 'package:cake_wallet/utils/feature_flag.dart';
|
||||||
import 'package:cake_wallet/utils/responsive_layout_util.dart';
|
|
||||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||||
|
@ -20,51 +20,78 @@ class BalancePage extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onLongPress: () => dashboardViewModel.balanceViewModel.isReversing =
|
onLongPress: () => dashboardViewModel.balanceViewModel.isReversing =
|
||||||
!dashboardViewModel.balanceViewModel.isReversing,
|
!dashboardViewModel.balanceViewModel.isReversing,
|
||||||
onLongPressUp: () => dashboardViewModel.balanceViewModel.isReversing =
|
onLongPressUp: () => dashboardViewModel.balanceViewModel.isReversing =
|
||||||
!dashboardViewModel.balanceViewModel.isReversing,
|
!dashboardViewModel.balanceViewModel.isReversing,
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
child: Column(
|
||||||
SizedBox(height: 56),
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
Container(
|
children: [
|
||||||
|
SizedBox(height: 56),
|
||||||
|
Container(
|
||||||
margin: const EdgeInsets.only(left: 24, bottom: 16),
|
margin: const EdgeInsets.only(left: 24, bottom: 16),
|
||||||
child: Observer(builder: (_) {
|
child: Observer(
|
||||||
return Text(dashboardViewModel.balanceViewModel.asset,
|
builder: (_) {
|
||||||
style: TextStyle(
|
return Row(
|
||||||
fontSize: 24,
|
children: [
|
||||||
fontFamily: 'Lato',
|
Text(
|
||||||
fontWeight: FontWeight.w600,
|
dashboardViewModel.balanceViewModel.asset,
|
||||||
color: Theme.of(context)
|
style: TextStyle(
|
||||||
.accentTextTheme!
|
fontSize: 24,
|
||||||
.displayMedium!
|
fontFamily: 'Lato',
|
||||||
.backgroundColor!,
|
fontWeight: FontWeight.w600,
|
||||||
height: 1),
|
color: Theme.of(context).accentTextTheme.displayMedium!.backgroundColor!,
|
||||||
maxLines: 1,
|
height: 1,
|
||||||
textAlign: TextAlign.center);
|
),
|
||||||
})),
|
maxLines: 1,
|
||||||
Observer(builder: (_) {
|
textAlign: TextAlign.center,
|
||||||
if (dashboardViewModel.balanceViewModel.isShowCard && FeatureFlag.isCakePayEnabled) {
|
),
|
||||||
return IntroducingCard(
|
if (dashboardViewModel.balanceViewModel.isHomeScreenSettingsEnabled)
|
||||||
title: S.of(context).introducing_cake_pay,
|
InkWell(
|
||||||
subTitle: S.of(context).cake_pay_learn_more,
|
onTap: () => Navigator.pushNamed(context, Routes.homeSettings,
|
||||||
borderColor: settingsStore.currentTheme.type == ThemeType.bright
|
arguments: dashboardViewModel.balanceViewModel),
|
||||||
? Color.fromRGBO(255, 255, 255, 0.2)
|
child: Padding(
|
||||||
: Colors.transparent,
|
padding: const EdgeInsets.all(8.0),
|
||||||
closeCard: dashboardViewModel.balanceViewModel.disableIntroCakePayCard);
|
child: Image.asset(
|
||||||
}
|
'assets/images/home_screen_settings_icon.png',
|
||||||
return Container();
|
color:
|
||||||
}),
|
Theme.of(context).accentTextTheme.displayMedium!.backgroundColor!,
|
||||||
Observer(builder: (_) {
|
),
|
||||||
return ListView.separated(
|
),
|
||||||
physics: NeverScrollableScrollPhysics(),
|
),
|
||||||
shrinkWrap: true,
|
],
|
||||||
separatorBuilder: (_, __) => Container(padding: EdgeInsets.only(bottom: 8)),
|
);
|
||||||
itemCount: dashboardViewModel.balanceViewModel.formattedBalances.length,
|
},
|
||||||
itemBuilder: (__, index) {
|
),
|
||||||
final balance =
|
),
|
||||||
dashboardViewModel.balanceViewModel.formattedBalances.elementAt(index);
|
Observer(
|
||||||
return buildBalanceRow(context,
|
builder: (_) {
|
||||||
|
if (dashboardViewModel.balanceViewModel.isShowCard &&
|
||||||
|
FeatureFlag.isCakePayEnabled) {
|
||||||
|
return IntroducingCard(
|
||||||
|
title: S.of(context).introducing_cake_pay,
|
||||||
|
subTitle: S.of(context).cake_pay_learn_more,
|
||||||
|
borderColor: settingsStore.currentTheme.type == ThemeType.bright
|
||||||
|
? Color.fromRGBO(255, 255, 255, 0.2)
|
||||||
|
: Colors.transparent,
|
||||||
|
closeCard: dashboardViewModel.balanceViewModel.disableIntroCakePayCard);
|
||||||
|
}
|
||||||
|
return Container();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Observer(
|
||||||
|
builder: (_) {
|
||||||
|
return ListView.separated(
|
||||||
|
physics: NeverScrollableScrollPhysics(),
|
||||||
|
shrinkWrap: true,
|
||||||
|
separatorBuilder: (_, __) => Container(padding: EdgeInsets.only(bottom: 8)),
|
||||||
|
itemCount: dashboardViewModel.balanceViewModel.formattedBalances.length,
|
||||||
|
itemBuilder: (__, index) {
|
||||||
|
final balance =
|
||||||
|
dashboardViewModel.balanceViewModel.formattedBalances.elementAt(index);
|
||||||
|
return buildBalanceRow(
|
||||||
|
context,
|
||||||
availableBalanceLabel:
|
availableBalanceLabel:
|
||||||
'${dashboardViewModel.balanceViewModel.availableBalanceLabel}',
|
'${dashboardViewModel.balanceViewModel.availableBalanceLabel}',
|
||||||
availableBalance: balance.availableBalance,
|
availableBalance: balance.availableBalance,
|
||||||
|
@ -75,45 +102,57 @@ class BalancePage extends StatelessWidget {
|
||||||
additionalFiatBalance: balance.fiatAdditionalBalance,
|
additionalFiatBalance: balance.fiatAdditionalBalance,
|
||||||
frozenBalance: balance.frozenBalance,
|
frozenBalance: balance.frozenBalance,
|
||||||
frozenFiatBalance: balance.fiatFrozenBalance,
|
frozenFiatBalance: balance.fiatFrozenBalance,
|
||||||
currency: balance.formattedAssetTitle);
|
currency: balance.formattedAssetTitle,
|
||||||
});
|
hasAdditionalBalance:
|
||||||
})
|
dashboardViewModel.balanceViewModel.hasAdditionalBalance,
|
||||||
])));
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildBalanceRow(BuildContext context,
|
Widget buildBalanceRow(
|
||||||
{required String availableBalanceLabel,
|
BuildContext context, {
|
||||||
required String availableBalance,
|
required String availableBalanceLabel,
|
||||||
required String availableFiatBalance,
|
required String availableBalance,
|
||||||
required String additionalBalanceLabel,
|
required String availableFiatBalance,
|
||||||
required String additionalBalance,
|
required String additionalBalanceLabel,
|
||||||
required String additionalFiatBalance,
|
required String additionalBalance,
|
||||||
required String frozenBalance,
|
required String additionalFiatBalance,
|
||||||
required String frozenFiatBalance,
|
required String frozenBalance,
|
||||||
required String currency}) {
|
required String frozenFiatBalance,
|
||||||
|
required String currency,
|
||||||
|
required bool hasAdditionalBalance,
|
||||||
|
}) {
|
||||||
return Container(
|
return Container(
|
||||||
margin: const EdgeInsets.only(left: 16, right: 16),
|
margin: const EdgeInsets.only(left: 16, right: 16),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(30.0),
|
borderRadius: BorderRadius.circular(30.0),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: settingsStore.currentTheme.type == ThemeType.bright
|
color: settingsStore.currentTheme.type == ThemeType.bright
|
||||||
? Color.fromRGBO(255, 255, 255, 0.2)
|
? Color.fromRGBO(255, 255, 255, 0.2)
|
||||||
: Colors.transparent,
|
: Colors.transparent,
|
||||||
width: 1,
|
width: 1,
|
||||||
),
|
),
|
||||||
color: Theme.of(context).textTheme!.titleLarge!.backgroundColor!),
|
color: Theme.of(context).textTheme.titleLarge!.backgroundColor!,
|
||||||
|
),
|
||||||
child: Container(
|
child: Container(
|
||||||
margin: const EdgeInsets.only(top: 16, left: 24, right: 24, bottom: 24),
|
margin: const EdgeInsets.only(top: 16, left: 24, right: 24, bottom: 24),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
behavior: HitTestBehavior.opaque,
|
behavior: HitTestBehavior.opaque,
|
||||||
onTap: () => _showBalanceDescription(context),
|
onTap: hasAdditionalBalance ? () => _showBalanceDescription(context) : null,
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
@ -129,19 +168,19 @@ class BalancePage extends StatelessWidget {
|
||||||
.displaySmall!
|
.displaySmall!
|
||||||
.backgroundColor!,
|
.backgroundColor!,
|
||||||
height: 1)),
|
height: 1)),
|
||||||
Padding(
|
if (hasAdditionalBalance)
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
Padding(
|
||||||
child: Icon(Icons.help_outline,
|
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||||
size: 16,
|
child: Icon(Icons.help_outline,
|
||||||
color: Theme.of(context)
|
size: 16,
|
||||||
.accentTextTheme!
|
color: Theme.of(context)
|
||||||
.displaySmall!
|
.accentTextTheme!
|
||||||
.backgroundColor!),
|
.displaySmall!
|
||||||
)
|
.backgroundColor!),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),SizedBox(
|
|
||||||
height: 6,
|
|
||||||
),
|
),
|
||||||
|
SizedBox(height: 6),
|
||||||
AutoSizeText(availableBalance,
|
AutoSizeText(availableBalance,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
|
@ -154,9 +193,7 @@ class BalancePage extends StatelessWidget {
|
||||||
height: 1),
|
height: 1),
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
textAlign: TextAlign.start),
|
textAlign: TextAlign.start),
|
||||||
SizedBox(
|
SizedBox(height: 6),
|
||||||
height: 6,
|
|
||||||
),
|
|
||||||
Text('${availableFiatBalance}',
|
Text('${availableFiatBalance}',
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
|
@ -168,7 +205,6 @@ class BalancePage extends StatelessWidget {
|
||||||
.displayMedium!
|
.displayMedium!
|
||||||
.backgroundColor!,
|
.backgroundColor!,
|
||||||
height: 1)),
|
height: 1)),
|
||||||
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -177,97 +213,99 @@ class BalancePage extends StatelessWidget {
|
||||||
fontSize: 28,
|
fontSize: 28,
|
||||||
fontFamily: 'Lato',
|
fontFamily: 'Lato',
|
||||||
fontWeight: FontWeight.w800,
|
fontWeight: FontWeight.w800,
|
||||||
color: Theme.of(context)
|
color: Theme.of(context).accentTextTheme!.displayMedium!.backgroundColor!,
|
||||||
.accentTextTheme!
|
|
||||||
.displayMedium!
|
|
||||||
.backgroundColor!,
|
|
||||||
height: 1)),
|
height: 1)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
SizedBox(height: 26),
|
|
||||||
if (frozenBalance.isNotEmpty)
|
if (frozenBalance.isNotEmpty)
|
||||||
Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
Column(
|
||||||
Text(S.current.frozen_balance,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
SizedBox(height: 26),
|
||||||
|
Text(
|
||||||
|
S.current.frozen_balance,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12,
|
|
||||||
fontFamily: 'Lato',
|
|
||||||
fontWeight: FontWeight.w400,
|
|
||||||
color: Theme.of(context)
|
|
||||||
.accentTextTheme!
|
|
||||||
.displaySmall!
|
|
||||||
.backgroundColor!,
|
|
||||||
height: 1)),
|
|
||||||
SizedBox(height: 8),
|
|
||||||
AutoSizeText(frozenBalance,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 20,
|
|
||||||
fontFamily: 'Lato',
|
|
||||||
fontWeight: FontWeight.w400,
|
|
||||||
color: Theme.of(context)
|
|
||||||
.accentTextTheme!
|
|
||||||
.displayMedium!
|
|
||||||
.backgroundColor!,
|
|
||||||
height: 1),
|
|
||||||
maxLines: 1,
|
|
||||||
textAlign: TextAlign.center),
|
|
||||||
SizedBox(height: 4),
|
|
||||||
Text(
|
|
||||||
frozenFiatBalance,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontFamily: 'Lato',
|
fontFamily: 'Lato',
|
||||||
fontWeight: FontWeight.w400,
|
fontWeight: FontWeight.w400,
|
||||||
color: Theme.of(context)
|
color: Theme.of(context).accentTextTheme.displaySmall!.backgroundColor!,
|
||||||
.accentTextTheme!
|
height: 1,
|
||||||
.displayMedium!
|
),
|
||||||
.backgroundColor!,
|
),
|
||||||
height: 1),
|
SizedBox(height: 8),
|
||||||
),
|
AutoSizeText(
|
||||||
SizedBox(height: 24)
|
frozenBalance,
|
||||||
]),
|
style: TextStyle(
|
||||||
Text('${additionalBalanceLabel}',
|
fontSize: 20,
|
||||||
textAlign: TextAlign.center,
|
fontFamily: 'Lato',
|
||||||
style: TextStyle(
|
fontWeight: FontWeight.w400,
|
||||||
fontSize: 12,
|
color: Theme.of(context).accentTextTheme.displayMedium!.backgroundColor!,
|
||||||
fontFamily: 'Lato',
|
height: 1,
|
||||||
fontWeight: FontWeight.w400,
|
),
|
||||||
color: Theme.of(context)
|
maxLines: 1,
|
||||||
.accentTextTheme!
|
textAlign: TextAlign.center,
|
||||||
.displaySmall!
|
),
|
||||||
.backgroundColor!,
|
SizedBox(height: 4),
|
||||||
height: 1)),
|
Text(
|
||||||
SizedBox(height: 8),
|
frozenFiatBalance,
|
||||||
AutoSizeText(additionalBalance,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 20,
|
fontSize: 12,
|
||||||
fontFamily: 'Lato',
|
fontFamily: 'Lato',
|
||||||
fontWeight: FontWeight.w400,
|
fontWeight: FontWeight.w400,
|
||||||
color: Theme.of(context)
|
color: Theme.of(context).accentTextTheme.displayMedium!.backgroundColor!,
|
||||||
.accentTextTheme!
|
height: 1,
|
||||||
.displayMedium!
|
),
|
||||||
.backgroundColor!,
|
),
|
||||||
height: 1),
|
],
|
||||||
maxLines: 1,
|
),
|
||||||
textAlign: TextAlign.center),
|
if (hasAdditionalBalance)
|
||||||
SizedBox(
|
Column(
|
||||||
height: 4,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
),
|
children: [
|
||||||
Text(
|
SizedBox(height: 24),
|
||||||
'${additionalFiatBalance}',
|
Text(
|
||||||
textAlign: TextAlign.center,
|
'${additionalBalanceLabel}',
|
||||||
style: TextStyle(
|
textAlign: TextAlign.center,
|
||||||
fontSize: 12,
|
style: TextStyle(
|
||||||
fontFamily: 'Lato',
|
fontSize: 12,
|
||||||
fontWeight: FontWeight.w400,
|
fontFamily: 'Lato',
|
||||||
color: Theme.of(context)
|
fontWeight: FontWeight.w400,
|
||||||
.accentTextTheme!
|
color: Theme.of(context).accentTextTheme.displaySmall!.backgroundColor!,
|
||||||
.displayMedium!
|
height: 1,
|
||||||
.backgroundColor!,
|
),
|
||||||
height: 1),
|
),
|
||||||
)
|
SizedBox(height: 8),
|
||||||
])),
|
AutoSizeText(
|
||||||
|
additionalBalance,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
fontFamily: 'Lato',
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
color: Theme.of(context).accentTextTheme.displayMedium!.backgroundColor!,
|
||||||
|
height: 1,
|
||||||
|
),
|
||||||
|
maxLines: 1,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
'${additionalFiatBalance}',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontFamily: 'Lato',
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
color: Theme.of(context).accentTextTheme.displayMedium!.backgroundColor!,
|
||||||
|
height: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,17 +19,18 @@ class MenuWidget extends StatefulWidget {
|
||||||
|
|
||||||
class MenuWidgetState extends State<MenuWidget> {
|
class MenuWidgetState extends State<MenuWidget> {
|
||||||
MenuWidgetState()
|
MenuWidgetState()
|
||||||
: this.menuWidth = 0,
|
: this.menuWidth = 0,
|
||||||
this.screenWidth = 0,
|
this.screenWidth = 0,
|
||||||
this.screenHeight = 0,
|
this.screenHeight = 0,
|
||||||
this.headerHeight = 120,
|
this.headerHeight = 120,
|
||||||
this.tileHeight = 60,
|
this.tileHeight = 60,
|
||||||
this.fromTopEdge = 50,
|
this.fromTopEdge = 50,
|
||||||
this.fromBottomEdge = 25,
|
this.fromBottomEdge = 25,
|
||||||
this.moneroIcon = Image.asset('assets/images/monero_menu.png'),
|
this.moneroIcon = Image.asset('assets/images/monero_menu.png'),
|
||||||
this.bitcoinIcon = Image.asset('assets/images/bitcoin_menu.png'),
|
this.bitcoinIcon = Image.asset('assets/images/bitcoin_menu.png'),
|
||||||
this.litecoinIcon = Image.asset('assets/images/litecoin_menu.png'),
|
this.litecoinIcon = Image.asset('assets/images/litecoin_menu.png'),
|
||||||
this.havenIcon = Image.asset('assets/images/haven_menu.png');
|
this.havenIcon = Image.asset('assets/images/haven_menu.png'),
|
||||||
|
this.ethereumIcon = Image.asset('assets/images/eth_icon.png');
|
||||||
|
|
||||||
final largeScreen = 731;
|
final largeScreen = 731;
|
||||||
|
|
||||||
|
@ -46,6 +47,7 @@ class MenuWidgetState extends State<MenuWidget> {
|
||||||
Image bitcoinIcon;
|
Image bitcoinIcon;
|
||||||
Image litecoinIcon;
|
Image litecoinIcon;
|
||||||
Image havenIcon;
|
Image havenIcon;
|
||||||
|
Image ethereumIcon;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
@ -85,16 +87,14 @@ class MenuWidgetState extends State<MenuWidget> {
|
||||||
|
|
||||||
moneroIcon = Image.asset('assets/images/monero_menu.png',
|
moneroIcon = Image.asset('assets/images/monero_menu.png',
|
||||||
color: Theme.of(context)
|
color: Theme.of(context)
|
||||||
.accentTextTheme!
|
.accentTextTheme
|
||||||
.labelSmall!
|
.labelSmall!
|
||||||
.decorationColor!);
|
.decorationColor!);
|
||||||
bitcoinIcon = Image.asset('assets/images/bitcoin_menu.png',
|
bitcoinIcon = Image.asset('assets/images/bitcoin_menu.png',
|
||||||
color: Theme.of(context)
|
color: Theme.of(context)
|
||||||
.accentTextTheme!
|
.accentTextTheme
|
||||||
.labelSmall!
|
.labelSmall!
|
||||||
.decorationColor!);
|
.decorationColor!);
|
||||||
litecoinIcon = Image.asset('assets/images/litecoin_menu.png');
|
|
||||||
havenIcon = Image.asset('assets/images/haven_menu.png');
|
|
||||||
|
|
||||||
return Row(
|
return Row(
|
||||||
mainAxisSize: MainAxisSize.max,
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
@ -178,7 +178,7 @@ class MenuWidgetState extends State<MenuWidget> {
|
||||||
index--;
|
index--;
|
||||||
|
|
||||||
final item = SettingActions.all[index];
|
final item = SettingActions.all[index];
|
||||||
|
|
||||||
final isLastTile = index == itemCount - 1;
|
final isLastTile = index == itemCount - 1;
|
||||||
|
|
||||||
return SettingActionButton(
|
return SettingActionButton(
|
||||||
|
@ -215,6 +215,8 @@ class MenuWidgetState extends State<MenuWidget> {
|
||||||
return litecoinIcon;
|
return litecoinIcon;
|
||||||
case WalletType.haven:
|
case WalletType.haven:
|
||||||
return havenIcon;
|
return havenIcon;
|
||||||
|
case WalletType.ethereum:
|
||||||
|
return ethereumIcon;
|
||||||
default:
|
default:
|
||||||
throw Exception('No icon for ${type.toString()}');
|
throw Exception('No icon for ${type.toString()}');
|
||||||
}
|
}
|
||||||
|
|
|
@ -511,17 +511,16 @@ class ExchangeCardState extends State<ExchangeCard> {
|
||||||
|
|
||||||
void _presentPicker(BuildContext context) {
|
void _presentPicker(BuildContext context) {
|
||||||
showPopUp<void>(
|
showPopUp<void>(
|
||||||
builder: (_) => CurrencyPicker(
|
context: context,
|
||||||
selectedAtIndex: widget.currencies.indexOf(_selectedCurrency),
|
builder: (_) => CurrencyPicker(
|
||||||
items: widget.currencies,
|
selectedAtIndex: widget.currencies.indexOf(_selectedCurrency),
|
||||||
hintText: S.of(context).search_currency,
|
items: widget.currencies,
|
||||||
isMoneroWallet: _isMoneroWallet,
|
hintText: S.of(context).search_currency,
|
||||||
isConvertFrom: widget.hasRefundAddress,
|
isMoneroWallet: _isMoneroWallet,
|
||||||
onItemSelected: (Currency item) =>
|
isConvertFrom: widget.hasRefundAddress,
|
||||||
widget.onCurrencySelected != null
|
onItemSelected: (Currency item) => widget.onCurrencySelected(item as CryptoCurrency),
|
||||||
? widget.onCurrencySelected(item as CryptoCurrency)
|
),
|
||||||
: null),
|
);
|
||||||
context: context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _showAmountPopup(BuildContext context, PaymentRequest paymentRequest) {
|
void _showAmountPopup(BuildContext context, PaymentRequest paymentRequest) {
|
||||||
|
|
|
@ -11,14 +11,17 @@ import 'package:cw_core/wallet_type.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class NewWalletTypePage extends BasePage {
|
class NewWalletTypePage extends BasePage {
|
||||||
NewWalletTypePage({required this.onTypeSelected});
|
NewWalletTypePage({required this.onTypeSelected, required this.isCreate});
|
||||||
|
|
||||||
final void Function(BuildContext, WalletType) onTypeSelected;
|
final void Function(BuildContext, WalletType) onTypeSelected;
|
||||||
|
final bool isCreate;
|
||||||
|
|
||||||
final walletTypeImage = Image.asset('assets/images/wallet_type.png');
|
final walletTypeImage = Image.asset('assets/images/wallet_type.png');
|
||||||
final walletTypeLightImage = Image.asset('assets/images/wallet_type_light.png');
|
final walletTypeLightImage = Image.asset('assets/images/wallet_type_light.png');
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get title => S.current.wallet_list_restore_wallet;
|
String get title =>
|
||||||
|
isCreate ? S.current.wallet_list_create_new_wallet : S.current.wallet_list_restore_wallet;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget body(BuildContext context) => WalletTypeForm(
|
Widget body(BuildContext context) => WalletTypeForm(
|
||||||
|
|
|
@ -1,85 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:cake_wallet/src/screens/restore/widgets/restore_button.dart';
|
|
||||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
|
||||||
import 'package:cw_core/wallet_type.dart';
|
|
||||||
import 'package:cake_wallet/generated/i18n.dart';
|
|
||||||
|
|
||||||
class RestoreWalletOptionsPage extends BasePage {
|
|
||||||
RestoreWalletOptionsPage(
|
|
||||||
{required this.type,
|
|
||||||
required this.onRestoreFromSeed,
|
|
||||||
required this.onRestoreFromKeys});
|
|
||||||
|
|
||||||
final WalletType type;
|
|
||||||
final Function(BuildContext context) onRestoreFromSeed;
|
|
||||||
final Function(BuildContext context) onRestoreFromKeys;
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get title => S.current.restore_restore_wallet;
|
|
||||||
|
|
||||||
final imageSeed = Image.asset('assets/images/restore_seed.png');
|
|
||||||
final imageKeys = Image.asset('assets/images/restore_keys.png');
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget body(BuildContext context) {
|
|
||||||
return Container(
|
|
||||||
width: double.infinity,
|
|
||||||
height: double.infinity,
|
|
||||||
padding: EdgeInsets.all(24),
|
|
||||||
child: SingleChildScrollView(
|
|
||||||
child: Column(
|
|
||||||
children: <Widget>[
|
|
||||||
RestoreButton(
|
|
||||||
onPressed: () => onRestoreFromSeed(context),
|
|
||||||
image: imageSeed,
|
|
||||||
title: S.of(context).restore_title_from_seed,
|
|
||||||
description: _fromSeedDescription(context)),
|
|
||||||
Padding(
|
|
||||||
padding: EdgeInsets.only(top: 24),
|
|
||||||
child: RestoreButton(
|
|
||||||
onPressed: () => onRestoreFromKeys(context),
|
|
||||||
image: imageKeys,
|
|
||||||
title: _fromKeyTitle(context),
|
|
||||||
description: _fromKeyDescription(context)),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
String _fromSeedDescription(BuildContext context) {
|
|
||||||
switch (type) {
|
|
||||||
case WalletType.monero:
|
|
||||||
return S.of(context).restore_description_from_seed;
|
|
||||||
case WalletType.bitcoin:
|
|
||||||
// TODO: Add transaction for bitcoin description.
|
|
||||||
return S.of(context).restore_bitcoin_description_from_seed;
|
|
||||||
default:
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String _fromKeyDescription(BuildContext context) {
|
|
||||||
switch (type) {
|
|
||||||
case WalletType.monero:
|
|
||||||
return S.of(context).restore_description_from_keys;
|
|
||||||
case WalletType.bitcoin:
|
|
||||||
// TODO: Add transaction for bitcoin description.
|
|
||||||
return S.of(context).restore_bitcoin_description_from_keys;
|
|
||||||
default:
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String _fromKeyTitle(BuildContext context) {
|
|
||||||
switch (type) {
|
|
||||||
case WalletType.monero:
|
|
||||||
return S.of(context).restore_title_from_keys;
|
|
||||||
case WalletType.bitcoin:
|
|
||||||
// TODO: Add transaction for bitcoin description.
|
|
||||||
return S.of(context).restore_bitcoin_title_from_keys;
|
|
||||||
default:
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -11,9 +11,7 @@ class PreSeedPage extends BasePage {
|
||||||
PreSeedPage(this.type)
|
PreSeedPage(this.type)
|
||||||
: imageLight = Image.asset('assets/images/pre_seed_light.png'),
|
: imageLight = Image.asset('assets/images/pre_seed_light.png'),
|
||||||
imageDark = Image.asset('assets/images/pre_seed_dark.png'),
|
imageDark = Image.asset('assets/images/pre_seed_dark.png'),
|
||||||
wordsCount = type == WalletType.monero
|
wordsCount = _wordsCount(type);
|
||||||
? 25
|
|
||||||
: 24; // FIXME: Stupid fast implementation
|
|
||||||
|
|
||||||
final Image imageDark;
|
final Image imageDark;
|
||||||
final Image imageLight;
|
final Image imageLight;
|
||||||
|
@ -68,4 +66,15 @@ class PreSeedPage extends BasePage {
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int _wordsCount(WalletType type) {
|
||||||
|
switch (type) {
|
||||||
|
case WalletType.monero:
|
||||||
|
return 25;
|
||||||
|
case WalletType.ethereum:
|
||||||
|
return 12;
|
||||||
|
default:
|
||||||
|
return 24;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -220,115 +220,108 @@ class SendPage extends BasePage {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (sendViewModel.hasMultiRecipient)
|
Container(
|
||||||
Container(
|
height: 40,
|
||||||
height: 40,
|
width: double.infinity,
|
||||||
width: double.infinity,
|
padding: EdgeInsets.only(left: 24),
|
||||||
padding: EdgeInsets.only(left: 24),
|
child: SingleChildScrollView(
|
||||||
child: SingleChildScrollView(
|
scrollDirection: Axis.horizontal,
|
||||||
scrollDirection: Axis.horizontal,
|
child: Observer(
|
||||||
child: Observer(
|
builder: (_) {
|
||||||
builder: (_) {
|
final templates = sendViewModel.templates;
|
||||||
final templates = sendViewModel.templates;
|
final itemCount = templates.length;
|
||||||
final itemCount = templates.length;
|
|
||||||
|
|
||||||
return Row(
|
return Row(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
AddTemplateButton(
|
AddTemplateButton(
|
||||||
onTap: () => Navigator.of(context)
|
onTap: () => Navigator.of(context)
|
||||||
.pushNamed(Routes.sendTemplate),
|
.pushNamed(Routes.sendTemplate),
|
||||||
currentTemplatesLength: templates.length,
|
currentTemplatesLength: templates.length,
|
||||||
),
|
),
|
||||||
ListView.builder(
|
ListView.builder(
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
physics: NeverScrollableScrollPhysics(),
|
physics: NeverScrollableScrollPhysics(),
|
||||||
itemCount: itemCount,
|
itemCount: itemCount,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final template = templates[index];
|
final template = templates[index];
|
||||||
return TemplateTile(
|
return TemplateTile(
|
||||||
key: UniqueKey(),
|
key: UniqueKey(),
|
||||||
to: template.name,
|
to: template.name,
|
||||||
hasMultipleRecipients:
|
hasMultipleRecipients:
|
||||||
template.additionalRecipients !=
|
template.additionalRecipients != null &&
|
||||||
null &&
|
template.additionalRecipients!.length > 1,
|
||||||
template.additionalRecipients!
|
amount: template.isCurrencySelected
|
||||||
.length > 1,
|
? template.amount
|
||||||
amount: template.isCurrencySelected
|
: template.amountFiat,
|
||||||
? template.amount
|
from: template.isCurrencySelected
|
||||||
: template.amountFiat,
|
? template.cryptoCurrency
|
||||||
from: template.isCurrencySelected
|
: template.fiatCurrency,
|
||||||
? template.cryptoCurrency
|
onTap: () async {
|
||||||
: template.fiatCurrency,
|
if (template.additionalRecipients?.isNotEmpty ?? false) {
|
||||||
onTap: () async {
|
sendViewModel.clearOutputs();
|
||||||
if (template.additionalRecipients !=
|
|
||||||
null) {
|
|
||||||
sendViewModel.clearOutputs();
|
|
||||||
|
|
||||||
template.additionalRecipients!
|
for (int i = 0;i < template.additionalRecipients!.length;i++) {
|
||||||
.forEach((currentElement) async {
|
Output output;
|
||||||
int i = template
|
try {
|
||||||
.additionalRecipients!
|
output = sendViewModel.outputs[i];
|
||||||
.indexOf(currentElement);
|
} catch (e) {
|
||||||
|
sendViewModel.addOutput();
|
||||||
|
output = sendViewModel.outputs[i];
|
||||||
|
}
|
||||||
|
|
||||||
Output output;
|
await _setInputsFromTemplate(
|
||||||
try {
|
context,
|
||||||
output = sendViewModel.outputs[i];
|
output: output,
|
||||||
} catch (e) {
|
template: template.additionalRecipients![i],
|
||||||
sendViewModel.addOutput();
|
);
|
||||||
output = sendViewModel.outputs[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
await _setInputsFromTemplate(
|
|
||||||
context,
|
|
||||||
output: output,
|
|
||||||
template: currentElement);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
final output = _defineCurrentOutput();
|
|
||||||
await _setInputsFromTemplate(
|
|
||||||
context,
|
|
||||||
output: output,
|
|
||||||
template: template);
|
|
||||||
}
|
}
|
||||||
},
|
} else {
|
||||||
onRemove: () {
|
final output = _defineCurrentOutput();
|
||||||
showPopUp<void>(
|
await _setInputsFromTemplate(
|
||||||
context: context,
|
context,
|
||||||
builder: (dialogContext) {
|
output: output,
|
||||||
return AlertWithTwoActions(
|
template: template,
|
||||||
alertTitle:
|
|
||||||
S.of(context).template,
|
|
||||||
alertContent: S
|
|
||||||
.of(context)
|
|
||||||
.confirm_delete_template,
|
|
||||||
rightButtonText:
|
|
||||||
S.of(context).delete,
|
|
||||||
leftButtonText:
|
|
||||||
S.of(context).cancel,
|
|
||||||
actionRightButton: () {
|
|
||||||
Navigator.of(dialogContext)
|
|
||||||
.pop();
|
|
||||||
sendViewModel
|
|
||||||
.sendTemplateViewModel
|
|
||||||
.removeTemplate(
|
|
||||||
template: template);
|
|
||||||
},
|
|
||||||
actionLeftButton: () =>
|
|
||||||
Navigator.of(dialogContext)
|
|
||||||
.pop());
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
);
|
},
|
||||||
},
|
onRemove: () {
|
||||||
),
|
showPopUp<void>(
|
||||||
],
|
context: context,
|
||||||
);
|
builder: (dialogContext) {
|
||||||
},
|
return AlertWithTwoActions(
|
||||||
),
|
alertTitle:
|
||||||
|
S.of(context).template,
|
||||||
|
alertContent: S
|
||||||
|
.of(context)
|
||||||
|
.confirm_delete_template,
|
||||||
|
rightButtonText:
|
||||||
|
S.of(context).delete,
|
||||||
|
leftButtonText:
|
||||||
|
S.of(context).cancel,
|
||||||
|
actionRightButton: () {
|
||||||
|
Navigator.of(dialogContext)
|
||||||
|
.pop();
|
||||||
|
sendViewModel
|
||||||
|
.sendTemplateViewModel
|
||||||
|
.removeTemplate(
|
||||||
|
template: template);
|
||||||
|
},
|
||||||
|
actionLeftButton: () =>
|
||||||
|
Navigator.of(dialogContext)
|
||||||
|
.pop());
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -350,7 +343,7 @@ class SendPage extends BasePage {
|
||||||
.displaySmall!
|
.displaySmall!
|
||||||
.decorationColor!,
|
.decorationColor!,
|
||||||
))),
|
))),
|
||||||
if (sendViewModel.hasMultiRecipient)
|
if (sendViewModel.sendTemplateViewModel.hasMultiRecipient)
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.only(bottom: 12),
|
padding: EdgeInsets.only(bottom: 12),
|
||||||
child: PrimaryButton(
|
child: PrimaryButton(
|
||||||
|
@ -518,6 +511,7 @@ class SendPage extends BasePage {
|
||||||
output.address = template.address;
|
output.address = template.address;
|
||||||
|
|
||||||
if (template.isCurrencySelected) {
|
if (template.isCurrencySelected) {
|
||||||
|
sendViewModel.setSelectedCryptoCurrency(template.cryptoCurrency);
|
||||||
output.setCryptoAmount(template.amount);
|
output.setCryptoAmount(template.amount);
|
||||||
} else {
|
} else {
|
||||||
sendViewModel.setFiatCurrency(fiatFromTemplate);
|
sendViewModel.setFiatCurrency(fiatFromTemplate);
|
||||||
|
|
|
@ -67,8 +67,7 @@ class SendTemplatePage extends BasePage {
|
||||||
controller: controller,
|
controller: controller,
|
||||||
itemCount: sendTemplateViewModel.recipients.length,
|
itemCount: sendTemplateViewModel.recipients.length,
|
||||||
itemBuilder: (_, index) {
|
itemBuilder: (_, index) {
|
||||||
final template =
|
final template = sendTemplateViewModel.recipients[index];
|
||||||
sendTemplateViewModel.recipients[index];
|
|
||||||
return SendTemplateCard(
|
return SendTemplateCard(
|
||||||
template: template,
|
template: template,
|
||||||
index: index,
|
index: index,
|
||||||
|
@ -76,8 +75,7 @@ class SendTemplatePage extends BasePage {
|
||||||
});
|
});
|
||||||
})),
|
})),
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.only(
|
padding: EdgeInsets.only(top: 10, left: 24, right: 24, bottom: 10),
|
||||||
top: 10, left: 24, right: 24, bottom: 10),
|
|
||||||
child: Container(
|
child: Container(
|
||||||
height: 10,
|
height: 10,
|
||||||
child: Observer(
|
child: Observer(
|
||||||
|
@ -107,55 +105,42 @@ class SendTemplatePage extends BasePage {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
])),
|
])),
|
||||||
bottomSectionPadding:
|
bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
|
||||||
EdgeInsets.only(left: 24, right: 24, bottom: 24),
|
|
||||||
bottomSection: Column(children: [
|
bottomSection: Column(children: [
|
||||||
// if (sendViewModel.hasMultiRecipient)
|
if (sendTemplateViewModel.hasMultiRecipient)
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.only(bottom: 12),
|
padding: EdgeInsets.only(bottom: 12),
|
||||||
child: PrimaryButton(
|
child: PrimaryButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
sendTemplateViewModel.addRecipient();
|
sendTemplateViewModel.addRecipient();
|
||||||
Future.delayed(const Duration(milliseconds: 250), () {
|
Future.delayed(const Duration(milliseconds: 250), () {
|
||||||
controller.jumpToPage(
|
controller.jumpToPage(sendTemplateViewModel.recipients.length - 1);
|
||||||
sendTemplateViewModel.recipients.length - 1);
|
});
|
||||||
});
|
},
|
||||||
},
|
text: S.of(context).add_receiver,
|
||||||
text: S.of(context).add_receiver,
|
color: Colors.transparent,
|
||||||
color: Colors.transparent,
|
textColor: Theme.of(context).accentTextTheme.displaySmall!.decorationColor!,
|
||||||
textColor: Theme.of(context)
|
isDottedBorder: true,
|
||||||
.accentTextTheme
|
borderColor:
|
||||||
.displaySmall!
|
Theme.of(context).primaryTextTheme.displaySmall!.decorationColor!)),
|
||||||
.decorationColor!,
|
|
||||||
isDottedBorder: true,
|
|
||||||
borderColor: Theme.of(context)
|
|
||||||
.primaryTextTheme
|
|
||||||
.displaySmall!
|
|
||||||
.decorationColor!)),
|
|
||||||
PrimaryButton(
|
PrimaryButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (_formKey.currentState != null &&
|
if (_formKey.currentState != null && _formKey.currentState!.validate()) {
|
||||||
_formKey.currentState!.validate()) {
|
|
||||||
final mainTemplate = sendTemplateViewModel.recipients[0];
|
final mainTemplate = sendTemplateViewModel.recipients[0];
|
||||||
print(sendTemplateViewModel.recipients.map((element) =>
|
final additionalRecipients = sendTemplateViewModel.recipients
|
||||||
element.toTemplate(
|
.map((element) => element.toTemplate(
|
||||||
cryptoCurrency:
|
cryptoCurrency: element.selectedCurrency.title,
|
||||||
sendTemplateViewModel.cryptoCurrency.title,
|
fiatCurrency: sendTemplateViewModel.fiatCurrency))
|
||||||
fiatCurrency:
|
.toList();
|
||||||
sendTemplateViewModel.fiatCurrency)));
|
|
||||||
sendTemplateViewModel.addTemplate(
|
sendTemplateViewModel.addTemplate(
|
||||||
isCurrencySelected: mainTemplate.isCurrencySelected,
|
isCurrencySelected: mainTemplate.isCurrencySelected,
|
||||||
name: mainTemplate.name,
|
name: mainTemplate.name,
|
||||||
address: mainTemplate.address,
|
address: mainTemplate.address,
|
||||||
|
cryptoCurrency: mainTemplate.selectedCurrency.title,
|
||||||
amount: mainTemplate.output.cryptoAmount,
|
amount: mainTemplate.output.cryptoAmount,
|
||||||
amountFiat: mainTemplate.output.fiatAmount,
|
amountFiat: mainTemplate.output.fiatAmount,
|
||||||
additionalRecipients: sendTemplateViewModel.recipients
|
additionalRecipients: additionalRecipients);
|
||||||
.map((element) => element.toTemplate(
|
|
||||||
cryptoCurrency: sendTemplateViewModel
|
|
||||||
.cryptoCurrency.title,
|
|
||||||
fiatCurrency:
|
|
||||||
sendTemplateViewModel.fiatCurrency))
|
|
||||||
.toList());
|
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -4,29 +4,53 @@ class PrefixCurrencyIcon extends StatelessWidget {
|
||||||
PrefixCurrencyIcon({
|
PrefixCurrencyIcon({
|
||||||
required this.isSelected,
|
required this.isSelected,
|
||||||
required this.title,
|
required this.title,
|
||||||
|
this.onTap,
|
||||||
});
|
});
|
||||||
|
|
||||||
final bool isSelected;
|
final bool isSelected;
|
||||||
final String title;
|
final String title;
|
||||||
|
final Function()? onTap;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Padding(
|
return GestureDetector(
|
||||||
|
onTap: onTap,
|
||||||
|
child: Padding(
|
||||||
padding: EdgeInsets.fromLTRB(0, 6.0, 8.0, 0),
|
padding: EdgeInsets.fromLTRB(0, 6.0, 8.0, 0),
|
||||||
child: Column(children: [
|
child: Column(
|
||||||
Container(
|
children: [
|
||||||
padding: EdgeInsets.symmetric(vertical: 4, horizontal: 8),
|
Container(
|
||||||
decoration: BoxDecoration(
|
padding: EdgeInsets.symmetric(vertical: 4, horizontal: 8),
|
||||||
borderRadius: BorderRadius.circular(26),
|
decoration: BoxDecoration(
|
||||||
color: isSelected ? Colors.green : Colors.transparent,
|
borderRadius: BorderRadius.circular(26),
|
||||||
|
color: isSelected ? Colors.green : Colors.transparent,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: <Widget>[
|
||||||
|
if (onTap != null)
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.only(right: 5),
|
||||||
|
child: Image.asset(
|
||||||
|
'assets/images/arrow_bottom_purple_icon.png',
|
||||||
|
color: Colors.white,
|
||||||
|
height: 8,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
title + ':',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
child: Text(title + ':',
|
],
|
||||||
style: TextStyle(
|
),
|
||||||
fontSize: 16,
|
),
|
||||||
fontWeight: FontWeight.w600,
|
);
|
||||||
color: Colors.white,
|
|
||||||
)),
|
|
||||||
)
|
|
||||||
]));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
import 'package:cake_wallet/entities/priority_for_wallet_type.dart';
|
import 'package:cake_wallet/entities/priority_for_wallet_type.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart';
|
||||||
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
||||||
import 'package:cake_wallet/utils/payment_request.dart';
|
import 'package:cake_wallet/utils/payment_request.dart';
|
||||||
import 'package:cake_wallet/utils/responsive_layout_util.dart';
|
import 'package:cake_wallet/utils/responsive_layout_util.dart';
|
||||||
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
|
import 'package:cw_core/currency.dart';
|
||||||
import 'package:cw_core/transaction_priority.dart';
|
import 'package:cw_core/transaction_priority.dart';
|
||||||
import 'package:cake_wallet/routes.dart';
|
import 'package:cake_wallet/routes.dart';
|
||||||
import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
|
import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
|
||||||
|
@ -32,18 +35,14 @@ class SendCard extends StatefulWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
SendCardState createState() => SendCardState(
|
SendCardState createState() => SendCardState(
|
||||||
output: output,
|
output: output,
|
||||||
sendViewModel: sendViewModel,
|
sendViewModel: sendViewModel,
|
||||||
initialPaymentRequest: initialPaymentRequest,
|
initialPaymentRequest: initialPaymentRequest,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class SendCardState extends State<SendCard>
|
class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<SendCard> {
|
||||||
with AutomaticKeepAliveClientMixin<SendCard> {
|
SendCardState({required this.output, required this.sendViewModel, this.initialPaymentRequest})
|
||||||
SendCardState({
|
|
||||||
required this.output,
|
|
||||||
required this.sendViewModel,
|
|
||||||
this.initialPaymentRequest})
|
|
||||||
: addressController = TextEditingController(),
|
: addressController = TextEditingController(),
|
||||||
cryptoAmountController = TextEditingController(),
|
cryptoAmountController = TextEditingController(),
|
||||||
fiatAmountController = TextEditingController(),
|
fiatAmountController = TextEditingController(),
|
||||||
|
@ -100,40 +99,41 @@ class SendCardState extends State<SendCard>
|
||||||
return Stack(
|
return Stack(
|
||||||
children: [
|
children: [
|
||||||
KeyboardActions(
|
KeyboardActions(
|
||||||
config: KeyboardActionsConfig(
|
config: KeyboardActionsConfig(
|
||||||
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
|
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
|
||||||
keyboardBarColor: Theme.of(context)
|
keyboardBarColor: Theme.of(context).accentTextTheme.bodyLarge!.backgroundColor!,
|
||||||
.accentTextTheme
|
nextFocus: false,
|
||||||
.bodyLarge!
|
actions: [
|
||||||
.backgroundColor!,
|
KeyboardActionsItem(
|
||||||
nextFocus: false,
|
focusNode: cryptoAmountFocus,
|
||||||
actions: [
|
toolbarButtons: [(_) => KeyboardDoneButton()],
|
||||||
KeyboardActionsItem(
|
),
|
||||||
focusNode: cryptoAmountFocus,
|
KeyboardActionsItem(
|
||||||
toolbarButtons: [(_) => KeyboardDoneButton()],
|
focusNode: fiatAmountFocus,
|
||||||
),
|
toolbarButtons: [(_) => KeyboardDoneButton()],
|
||||||
KeyboardActionsItem(
|
)
|
||||||
focusNode: fiatAmountFocus,
|
],
|
||||||
toolbarButtons: [(_) => KeyboardDoneButton()],
|
),
|
||||||
)
|
child: Container(
|
||||||
]),
|
height: 0,
|
||||||
child: Container(
|
color: Colors.transparent,
|
||||||
height: 0,
|
),
|
||||||
color: Colors.transparent,
|
),
|
||||||
)),
|
|
||||||
Container(
|
Container(
|
||||||
decoration: ResponsiveLayoutUtil.instance.isMobile ? BoxDecoration(
|
decoration: ResponsiveLayoutUtil.instance.isMobile
|
||||||
borderRadius: BorderRadius.only(
|
? BoxDecoration(
|
||||||
bottomLeft: Radius.circular(24),
|
borderRadius: BorderRadius.only(
|
||||||
bottomRight: Radius.circular(24)),
|
bottomLeft: Radius.circular(24), bottomRight: Radius.circular(24)),
|
||||||
gradient: LinearGradient(colors: [
|
gradient: LinearGradient(
|
||||||
Theme.of(context).primaryTextTheme.titleMedium!.color!,
|
colors: [
|
||||||
Theme.of(context)
|
Theme.of(context).primaryTextTheme.titleMedium!.color!,
|
||||||
.primaryTextTheme
|
Theme.of(context).primaryTextTheme.titleMedium!.decorationColor!,
|
||||||
.titleMedium!
|
],
|
||||||
.decorationColor!,
|
begin: Alignment.topLeft,
|
||||||
], begin: Alignment.topLeft, end: Alignment.bottomRight),
|
end: Alignment.bottomRight,
|
||||||
) : null,
|
),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.fromLTRB(
|
padding: EdgeInsets.fromLTRB(
|
||||||
24,
|
24,
|
||||||
|
@ -142,7 +142,8 @@ class SendCardState extends State<SendCard>
|
||||||
ResponsiveLayoutUtil.instance.isMobile ? 32 : 0,
|
ResponsiveLayoutUtil.instance.isMobile ? 32 : 0,
|
||||||
),
|
),
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
child: Observer(builder: (_) => Column(
|
child: Observer(
|
||||||
|
builder: (_) => Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Observer(builder: (_) {
|
Observer(builder: (_) {
|
||||||
|
@ -164,25 +165,15 @@ class SendCardState extends State<SendCard>
|
||||||
AddressTextFieldOption.qrCode,
|
AddressTextFieldOption.qrCode,
|
||||||
AddressTextFieldOption.addressBook
|
AddressTextFieldOption.addressBook
|
||||||
],
|
],
|
||||||
buttonColor: Theme.of(context)
|
buttonColor: Theme.of(context).primaryTextTheme.headlineMedium!.color!,
|
||||||
.primaryTextTheme
|
borderColor: Theme.of(context).primaryTextTheme.headlineSmall!.color!,
|
||||||
.headlineMedium!
|
|
||||||
.color!,
|
|
||||||
borderColor: Theme.of(context)
|
|
||||||
.primaryTextTheme
|
|
||||||
.headlineSmall!
|
|
||||||
.color!,
|
|
||||||
textStyle: TextStyle(
|
textStyle: TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
color: Colors.white),
|
|
||||||
hintStyle: TextStyle(
|
hintStyle: TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
color: Theme.of(context)
|
color:
|
||||||
.primaryTextTheme
|
Theme.of(context).primaryTextTheme.headlineSmall!.decorationColor!),
|
||||||
.headlineSmall!
|
|
||||||
.decorationColor!),
|
|
||||||
onPushPasteButton: (context) async {
|
onPushPasteButton: (context) async {
|
||||||
output.resetParsedAddress();
|
output.resetParsedAddress();
|
||||||
await output.fetchParsedAddress(context);
|
await output.fetchParsedAddress(context);
|
||||||
|
@ -197,170 +188,192 @@ class SendCardState extends State<SendCard>
|
||||||
selectedCurrency: sendViewModel.currency,
|
selectedCurrency: sendViewModel.currency,
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
if (output.isParsedAddress) Padding(
|
if (output.isParsedAddress)
|
||||||
padding: const EdgeInsets.only(top: 20),
|
Padding(
|
||||||
child: BaseTextFormField(
|
padding: const EdgeInsets.only(top: 20),
|
||||||
controller: extractedAddressController,
|
child: BaseTextFormField(
|
||||||
readOnly: true,
|
controller: extractedAddressController,
|
||||||
borderColor: Theme.of(context)
|
readOnly: true,
|
||||||
.primaryTextTheme
|
borderColor: Theme.of(context).primaryTextTheme.headlineSmall!.color!,
|
||||||
.headlineSmall!
|
textStyle: TextStyle(
|
||||||
.color!,
|
fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
|
||||||
textStyle: TextStyle(
|
validator: sendViewModel.addressValidator)),
|
||||||
fontSize: 14,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
color: Colors.white),
|
|
||||||
validator: sendViewModel.addressValidator
|
|
||||||
)
|
|
||||||
),
|
|
||||||
Observer(
|
Observer(
|
||||||
builder: (_) => Padding(
|
builder: (_) => Padding(
|
||||||
padding: const EdgeInsets.only(top: 20),
|
padding: const EdgeInsets.only(top: 20),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(bottom: 8.0),
|
padding: const EdgeInsets.only(bottom: 8.0),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Text(
|
sendViewModel.hasMultipleTokens
|
||||||
sendViewModel.selectedCryptoCurrency.title,
|
? Container(
|
||||||
style: TextStyle(
|
padding: EdgeInsets.only(right: 8),
|
||||||
fontSize: 16,
|
height: 32,
|
||||||
fontWeight: FontWeight.w600,
|
child: InkWell(
|
||||||
color: Colors.white,
|
onTap: () => _presentPicker(context),
|
||||||
)),
|
child: Row(
|
||||||
sendViewModel.selectedCryptoCurrency.tag != null ? Padding(
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
padding: const EdgeInsets.fromLTRB(3.0,0,3.0,0),
|
mainAxisSize: MainAxisSize.min,
|
||||||
child: Container(
|
children: <Widget>[
|
||||||
height: 32,
|
Padding(
|
||||||
decoration: BoxDecoration(
|
padding: EdgeInsets.only(right: 5),
|
||||||
color: Theme.of(context)
|
child: Image.asset(
|
||||||
.primaryTextTheme
|
'assets/images/arrow_bottom_purple_icon.png',
|
||||||
.headlineMedium!
|
color: Colors.white,
|
||||||
.color!,
|
height: 8,
|
||||||
borderRadius:
|
),
|
||||||
BorderRadius.all(Radius.circular(6))),
|
),
|
||||||
child: Center(
|
Text(
|
||||||
child: Padding(
|
sendViewModel.selectedCryptoCurrency.title,
|
||||||
padding: const EdgeInsets.all(6.0),
|
style: TextStyle(
|
||||||
child: Text( sendViewModel.selectedCryptoCurrency.tag!,
|
fontWeight: FontWeight.w600,
|
||||||
style: TextStyle(
|
fontSize: 16,
|
||||||
fontSize: 12,
|
color: Colors.white),
|
||||||
fontWeight: FontWeight.bold,
|
),
|
||||||
color: Theme.of(context)
|
],
|
||||||
.primaryTextTheme
|
),
|
||||||
.headlineMedium!
|
|
||||||
.decorationColor!)),
|
|
||||||
),
|
),
|
||||||
),
|
)
|
||||||
),
|
: Text(
|
||||||
) : Container(),
|
sendViewModel.selectedCryptoCurrency.title,
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(right: 10.0),
|
|
||||||
child: Text(':',
|
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
color: Colors.white)),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Stack(
|
|
||||||
children: [
|
|
||||||
BaseTextFormField(
|
|
||||||
focusNode: cryptoAmountFocus,
|
|
||||||
controller: cryptoAmountController,
|
|
||||||
keyboardType:
|
|
||||||
TextInputType.numberWithOptions(
|
|
||||||
signed: false, decimal: true),
|
|
||||||
inputFormatters: [
|
|
||||||
FilteringTextInputFormatter.deny(RegExp('[\\-|\\ ]'))
|
|
||||||
],
|
|
||||||
suffixIcon: SizedBox(
|
|
||||||
width: prefixIconWidth,
|
|
||||||
),
|
|
||||||
hintText: '0.0000',
|
|
||||||
borderColor: Colors.transparent,
|
|
||||||
textStyle: TextStyle(
|
|
||||||
fontSize: 14,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
color: Colors.white),
|
color: Colors.white),
|
||||||
placeholderTextStyle: TextStyle(
|
),
|
||||||
|
sendViewModel.selectedCryptoCurrency.tag != null
|
||||||
|
? Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(3.0, 0, 3.0, 0),
|
||||||
|
child: Container(
|
||||||
|
height: 32,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.primaryTextTheme
|
||||||
|
.headlineMedium!
|
||||||
|
.color!,
|
||||||
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(6),
|
||||||
|
)),
|
||||||
|
child: Center(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(6.0),
|
||||||
|
child: Text(
|
||||||
|
sendViewModel.selectedCryptoCurrency.tag!,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.primaryTextTheme
|
||||||
|
.headlineMedium!
|
||||||
|
.decorationColor!),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: Container(),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 10.0),
|
||||||
|
child: Text(
|
||||||
|
':',
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
fontSize: 16,
|
||||||
|
color: Colors.white),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
BaseTextFormField(
|
||||||
|
focusNode: cryptoAmountFocus,
|
||||||
|
controller: cryptoAmountController,
|
||||||
|
keyboardType: TextInputType.numberWithOptions(
|
||||||
|
signed: false, decimal: true),
|
||||||
|
inputFormatters: [
|
||||||
|
FilteringTextInputFormatter.deny(RegExp('[\\-|\\ ]'))
|
||||||
|
],
|
||||||
|
suffixIcon: SizedBox(
|
||||||
|
width: prefixIconWidth,
|
||||||
|
),
|
||||||
|
hintText: '0.0000',
|
||||||
|
borderColor: Colors.transparent,
|
||||||
|
textStyle: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: Colors.white),
|
||||||
|
placeholderTextStyle: TextStyle(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.primaryTextTheme
|
||||||
|
.headlineSmall!
|
||||||
|
.decorationColor!,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
fontSize: 14),
|
||||||
|
validator: output.sendAll
|
||||||
|
? sendViewModel.allAmountValidator
|
||||||
|
: sendViewModel.amountValidator,
|
||||||
|
),
|
||||||
|
if (!sendViewModel.isBatchSending)
|
||||||
|
Positioned(
|
||||||
|
top: 2,
|
||||||
|
right: 0,
|
||||||
|
child: Container(
|
||||||
|
width: prefixIconWidth,
|
||||||
|
height: prefixIconHeight,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () async => output.setSendAll(),
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
color: Theme.of(context)
|
color: Theme.of(context)
|
||||||
.primaryTextTheme
|
.primaryTextTheme
|
||||||
.headlineSmall!
|
.headlineMedium!
|
||||||
.decorationColor!,
|
.color!,
|
||||||
fontWeight: FontWeight.w500,
|
borderRadius: BorderRadius.all(
|
||||||
fontSize: 14),
|
Radius.circular(6),
|
||||||
validator: output.sendAll
|
),
|
||||||
? sendViewModel.allAmountValidator
|
),
|
||||||
: sendViewModel
|
child: Center(
|
||||||
.amountValidator),
|
child: Text(
|
||||||
if (!sendViewModel.isBatchSending) Positioned(
|
S.of(context).all,
|
||||||
top: 2,
|
textAlign: TextAlign.center,
|
||||||
right: 0,
|
style: TextStyle(
|
||||||
child: Container(
|
fontSize: 12,
|
||||||
width: prefixIconWidth,
|
fontWeight: FontWeight.bold,
|
||||||
height: prefixIconHeight,
|
color: Theme.of(context)
|
||||||
child: InkWell(
|
.primaryTextTheme
|
||||||
onTap: () async =>
|
.headlineMedium!
|
||||||
output.setSendAll(),
|
.decorationColor!,
|
||||||
child: Container(
|
),
|
||||||
decoration: BoxDecoration(
|
),
|
||||||
color: Theme.of(context)
|
),
|
||||||
.primaryTextTheme
|
),
|
||||||
.headlineMedium!
|
),
|
||||||
.color!,
|
),
|
||||||
borderRadius:
|
),
|
||||||
BorderRadius.all(
|
],
|
||||||
Radius.circular(6))),
|
|
||||||
child: Center(
|
|
||||||
child: Text(
|
|
||||||
S.of(context).all,
|
|
||||||
textAlign:
|
|
||||||
TextAlign.center,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
fontWeight:
|
|
||||||
FontWeight.bold,
|
|
||||||
color:
|
|
||||||
Theme.of(context)
|
|
||||||
.primaryTextTheme
|
|
||||||
.headlineMedium!
|
|
||||||
.decorationColor!))),
|
|
||||||
))))]),
|
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
)
|
],
|
||||||
)),
|
)),
|
||||||
Divider(height: 1,color: Theme.of(context)
|
),
|
||||||
.primaryTextTheme
|
Divider(
|
||||||
.headlineSmall!
|
height: 1,
|
||||||
.decorationColor!),
|
color: Theme.of(context).primaryTextTheme.headlineSmall!.decorationColor!),
|
||||||
Observer(
|
Observer(
|
||||||
builder: (_) => Padding(
|
builder: (_) => Padding(
|
||||||
padding: EdgeInsets.only(top: 10),
|
padding: EdgeInsets.only(top: 10),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.max,
|
mainAxisSize: MainAxisSize.max,
|
||||||
mainAxisAlignment:
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
MainAxisAlignment.spaceBetween,
|
children: <Widget>[
|
||||||
children: <Widget>[
|
Expanded(
|
||||||
Expanded(
|
child: Text(
|
||||||
child: Text(
|
S.of(context).available_balance + ':',
|
||||||
S.of(context).available_balance +
|
|
||||||
':',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
color: Theme.of(context)
|
|
||||||
.primaryTextTheme
|
|
||||||
.headlineSmall!
|
|
||||||
.decorationColor!),
|
|
||||||
)),
|
|
||||||
Text(
|
|
||||||
sendViewModel.balance,
|
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
|
@ -368,10 +381,22 @@ class SendCardState extends State<SendCard>
|
||||||
.primaryTextTheme
|
.primaryTextTheme
|
||||||
.headlineSmall!
|
.headlineSmall!
|
||||||
.decorationColor!),
|
.decorationColor!),
|
||||||
)
|
),
|
||||||
],
|
),
|
||||||
),
|
Text(
|
||||||
)),
|
sendViewModel.balance,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.primaryTextTheme
|
||||||
|
.headlineSmall!
|
||||||
|
.decorationColor!),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
if (!sendViewModel.isFiatDisabled)
|
if (!sendViewModel.isFiatDisabled)
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(top: 20),
|
padding: const EdgeInsets.only(top: 20),
|
||||||
|
@ -379,171 +404,155 @@ class SendCardState extends State<SendCard>
|
||||||
focusNode: fiatAmountFocus,
|
focusNode: fiatAmountFocus,
|
||||||
controller: fiatAmountController,
|
controller: fiatAmountController,
|
||||||
keyboardType:
|
keyboardType:
|
||||||
TextInputType.numberWithOptions(
|
TextInputType.numberWithOptions(signed: false, decimal: true),
|
||||||
signed: false, decimal: true),
|
|
||||||
inputFormatters: [
|
inputFormatters: [
|
||||||
FilteringTextInputFormatter.deny(RegExp('[\\-|\\ ]'))
|
FilteringTextInputFormatter.deny(
|
||||||
|
RegExp('[\\-|\\ ]'),
|
||||||
|
)
|
||||||
],
|
],
|
||||||
prefixIcon: Padding(
|
prefixIcon: Padding(
|
||||||
padding: EdgeInsets.only(top: 9),
|
padding: EdgeInsets.only(top: 9),
|
||||||
child:
|
child: Text(
|
||||||
Text(sendViewModel.fiat.title + ':',
|
sendViewModel.fiat.title + ':',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
)),
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
hintText: '0.00',
|
hintText: '0.00',
|
||||||
borderColor: Theme.of(context)
|
borderColor: Theme.of(context).primaryTextTheme.headlineSmall!.color!,
|
||||||
.primaryTextTheme
|
|
||||||
.headlineSmall!
|
|
||||||
.color!,
|
|
||||||
textStyle: TextStyle(
|
textStyle: TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
color: Colors.white),
|
|
||||||
placeholderTextStyle: TextStyle(
|
placeholderTextStyle: TextStyle(
|
||||||
color: Theme.of(context)
|
color: Theme.of(context)
|
||||||
.primaryTextTheme.headlineSmall!.decorationColor!,
|
.primaryTextTheme
|
||||||
|
.headlineSmall!
|
||||||
|
.decorationColor!,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
fontSize: 14),
|
fontSize: 14),
|
||||||
)),
|
),
|
||||||
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.only(top: 20),
|
padding: EdgeInsets.only(top: 20),
|
||||||
child: BaseTextFormField(
|
child: BaseTextFormField(
|
||||||
controller: noteController,
|
controller: noteController,
|
||||||
keyboardType: TextInputType.multiline,
|
keyboardType: TextInputType.multiline,
|
||||||
maxLines: null,
|
maxLines: null,
|
||||||
borderColor: Theme.of(context)
|
borderColor: Theme.of(context).primaryTextTheme.headlineSmall!.color!,
|
||||||
.primaryTextTheme
|
|
||||||
.headlineSmall!
|
|
||||||
.color!,
|
|
||||||
textStyle: TextStyle(
|
textStyle: TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
color: Colors.white),
|
|
||||||
hintText: S.of(context).note_optional,
|
hintText: S.of(context).note_optional,
|
||||||
placeholderTextStyle: TextStyle(
|
placeholderTextStyle: TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
color: Theme.of(context)
|
color:
|
||||||
.primaryTextTheme
|
Theme.of(context).primaryTextTheme.headlineSmall!.decorationColor!),
|
||||||
.headlineSmall!
|
|
||||||
.decorationColor!),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Observer(
|
Observer(
|
||||||
builder: (_) => GestureDetector(
|
builder: (_) => GestureDetector(
|
||||||
onTap: () =>
|
onTap: () => _setTransactionPriority(context),
|
||||||
_setTransactionPriority(context),
|
child: Container(
|
||||||
child: Container(
|
padding: EdgeInsets.only(top: 24),
|
||||||
padding: EdgeInsets.only(top: 24),
|
child: Row(
|
||||||
child: Row(
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
mainAxisAlignment:
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
MainAxisAlignment.spaceBetween,
|
children: <Widget>[
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
Text(
|
||||||
children: <Widget>[
|
S.of(context).send_estimated_fee,
|
||||||
Text(
|
style: TextStyle(
|
||||||
S
|
fontSize: 12,
|
||||||
.of(context)
|
fontWeight: FontWeight.w500,
|
||||||
.send_estimated_fee,
|
//color: Theme.of(context).primaryTextTheme!.displaySmall!.color!,
|
||||||
style: TextStyle(
|
color: Colors.white),
|
||||||
fontSize: 12,
|
),
|
||||||
fontWeight:
|
Container(
|
||||||
FontWeight.w500,
|
child: Row(
|
||||||
//color: Theme.of(context).primaryTextTheme!.displaySmall!.color!,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
color: Colors.white)),
|
children: <Widget>[
|
||||||
Container(
|
Column(
|
||||||
child: Row(
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
children: <Widget>[
|
children: [
|
||||||
Column(
|
Text(
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
output.estimatedFee.toString() +
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
' ' +
|
||||||
children: [
|
sendViewModel.selectedCryptoCurrency.toString(),
|
||||||
Text(
|
style: TextStyle(
|
||||||
output
|
fontSize: 12,
|
||||||
.estimatedFee
|
fontWeight: FontWeight.w600,
|
||||||
.toString() +
|
//color: Theme.of(context).primaryTextTheme!.displaySmall!.color!,
|
||||||
' ' +
|
color: Colors.white,
|
||||||
sendViewModel
|
|
||||||
.selectedCryptoCurrency.toString(),
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
fontWeight:
|
|
||||||
FontWeight.w600,
|
|
||||||
//color: Theme.of(context).primaryTextTheme!.displaySmall!.color!,
|
|
||||||
color:
|
|
||||||
Colors.white)),
|
|
||||||
Padding(
|
|
||||||
padding:
|
|
||||||
EdgeInsets.only(top: 5),
|
|
||||||
child: sendViewModel.isFiatDisabled
|
|
||||||
? const SizedBox(height: 14)
|
|
||||||
: Text(output
|
|
||||||
.estimatedFeeFiatAmount
|
|
||||||
+ ' ' +
|
|
||||||
sendViewModel
|
|
||||||
.fiat.title,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
fontWeight:
|
|
||||||
FontWeight.w600,
|
|
||||||
color: Theme
|
|
||||||
.of(context)
|
|
||||||
.primaryTextTheme
|
|
||||||
.headlineSmall!
|
|
||||||
.decorationColor!))
|
|
||||||
),
|
),
|
||||||
],
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: EdgeInsets.only(
|
|
||||||
top: 2,
|
|
||||||
left: 5),
|
|
||||||
child: Icon(
|
|
||||||
Icons.arrow_forward_ios,
|
|
||||||
size: 12,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
),
|
||||||
)
|
Padding(
|
||||||
],
|
padding: EdgeInsets.only(top: 5),
|
||||||
),
|
child: sendViewModel.isFiatDisabled
|
||||||
)
|
? const SizedBox(height: 14)
|
||||||
|
: Text(
|
||||||
|
output.estimatedFeeFiatAmount +
|
||||||
|
' ' +
|
||||||
|
sendViewModel.fiat.title,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.primaryTextTheme
|
||||||
|
.headlineSmall!
|
||||||
|
.decorationColor!,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.only(top: 2, left: 5),
|
||||||
|
child: Icon(
|
||||||
|
Icons.arrow_forward_ios,
|
||||||
|
size: 12,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (sendViewModel.isElectrumWallet)
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.only(top: 6),
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () => Navigator.of(context).pushNamed(Routes.unspentCoinsList),
|
||||||
|
child: Container(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
S.of(context).coin_control,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Colors.white),
|
||||||
|
),
|
||||||
|
Icon(
|
||||||
|
Icons.arrow_forward_ios,
|
||||||
|
size: 12,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)),
|
),
|
||||||
if (sendViewModel.isElectrumWallet) Padding(
|
),
|
||||||
padding: EdgeInsets.only(top: 6),
|
|
||||||
child: GestureDetector(
|
|
||||||
onTap: () => Navigator.of(context)
|
|
||||||
.pushNamed(Routes.unspentCoinsList),
|
|
||||||
child: Container(
|
|
||||||
color: Colors.transparent,
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment:
|
|
||||||
MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
S.of(context).coin_control,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
color: Colors.white)),
|
|
||||||
Icon(
|
|
||||||
Icons.arrow_forward_ios,
|
|
||||||
size: 12,
|
|
||||||
color: Colors.white,
|
|
||||||
)
|
|
||||||
],
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
],
|
],
|
||||||
))
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -552,10 +561,10 @@ class SendCardState extends State<SendCard>
|
||||||
}
|
}
|
||||||
|
|
||||||
void _setEffects(BuildContext context) {
|
void _setEffects(BuildContext context) {
|
||||||
if (_effectsInstalled) {
|
if (_effectsInstalled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (output.address.isNotEmpty) {
|
if (output.address.isNotEmpty) {
|
||||||
addressController.text = output.address;
|
addressController.text = output.address;
|
||||||
}
|
}
|
||||||
|
@ -664,16 +673,30 @@ class SendCardState extends State<SendCard>
|
||||||
final selectedItem = items.indexOf(sendViewModel.transactionPriority);
|
final selectedItem = items.indexOf(sendViewModel.transactionPriority);
|
||||||
|
|
||||||
await showPopUp<void>(
|
await showPopUp<void>(
|
||||||
builder: (_) => Picker(
|
context: context,
|
||||||
items: items,
|
builder: (_) => Picker(
|
||||||
displayItem: sendViewModel.displayFeeRate,
|
items: items,
|
||||||
selectedAtIndex: selectedItem,
|
displayItem: sendViewModel.displayFeeRate,
|
||||||
title: S.of(context).please_select,
|
selectedAtIndex: selectedItem,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
title: S.of(context).please_select,
|
||||||
onItemSelected: (TransactionPriority priority) =>
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
sendViewModel.setTransactionPriority(priority),
|
onItemSelected: (TransactionPriority priority) =>
|
||||||
),
|
sendViewModel.setTransactionPriority(priority),
|
||||||
context: context);
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _presentPicker(BuildContext context) {
|
||||||
|
showPopUp<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (_) => CurrencyPicker(
|
||||||
|
selectedAtIndex: sendViewModel.currencies.indexOf(sendViewModel.selectedCryptoCurrency),
|
||||||
|
items: sendViewModel.currencies,
|
||||||
|
hintText: S.of(context).search_currency,
|
||||||
|
onItemSelected: (Currency cur) =>
|
||||||
|
sendViewModel.selectedCryptoCurrency = (cur as CryptoCurrency),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
|
import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart';
|
||||||
import 'package:cake_wallet/src/screens/send/widgets/prefix_currency_icon_widget.dart';
|
import 'package:cake_wallet/src/screens/send/widgets/prefix_currency_icon_widget.dart';
|
||||||
import 'package:cake_wallet/utils/payment_request.dart';
|
import 'package:cake_wallet/utils/payment_request.dart';
|
||||||
|
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||||
import 'package:cake_wallet/view_model/send/template_view_model.dart';
|
import 'package:cake_wallet/view_model/send/template_view_model.dart';
|
||||||
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
|
import 'package:cw_core/currency.dart';
|
||||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
@ -35,161 +39,140 @@ class SendTemplateCard extends StatelessWidget {
|
||||||
_setEffects(context);
|
_setEffects(context);
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.only(
|
borderRadius:
|
||||||
bottomLeft: Radius.circular(24),
|
BorderRadius.only(bottomLeft: Radius.circular(24), bottomRight: Radius.circular(24)),
|
||||||
bottomRight: Radius.circular(24)),
|
gradient: LinearGradient(colors: [
|
||||||
gradient: LinearGradient(colors: [
|
Theme.of(context).primaryTextTheme.titleMedium!.color!,
|
||||||
Theme.of(context).primaryTextTheme.titleMedium!.color!,
|
Theme.of(context).primaryTextTheme.titleMedium!.decorationColor!
|
||||||
Theme.of(context).primaryTextTheme.titleMedium!.decorationColor!
|
], begin: Alignment.topLeft, end: Alignment.bottomRight)),
|
||||||
], begin: Alignment.topLeft, end: Alignment.bottomRight)),
|
child: Column(
|
||||||
child: Column(children: <Widget>[
|
children: <Widget>[
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.fromLTRB(24, 90, 24, 32),
|
padding: EdgeInsets.fromLTRB(24, 90, 24, 32),
|
||||||
child: Column(children: <Widget>[
|
child: Column(
|
||||||
|
children: <Widget>[
|
||||||
if (index == 0)
|
if (index == 0)
|
||||||
BaseTextFormField(
|
BaseTextFormField(
|
||||||
controller: _nameController,
|
controller: _nameController,
|
||||||
hintText: sendTemplateViewModel.recipients.length > 1
|
hintText: sendTemplateViewModel.recipients.length > 1
|
||||||
? S.of(context).template_name
|
? S.of(context).template_name
|
||||||
: S.of(context).send_name,
|
: S.of(context).send_name,
|
||||||
borderColor: Theme.of(context)
|
borderColor: Theme.of(context).primaryTextTheme.headlineSmall!.color!,
|
||||||
.primaryTextTheme
|
textStyle:
|
||||||
.headlineSmall!
|
TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
|
||||||
.color!,
|
|
||||||
textStyle: TextStyle(
|
|
||||||
fontSize: 14,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
color: Colors.white),
|
|
||||||
placeholderTextStyle: TextStyle(
|
placeholderTextStyle: TextStyle(
|
||||||
color: Theme.of(context)
|
color: Theme.of(context).primaryTextTheme.headlineSmall!.decorationColor!,
|
||||||
.primaryTextTheme
|
|
||||||
.headlineSmall!
|
|
||||||
.decorationColor!,
|
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
fontSize: 14),
|
fontSize: 14),
|
||||||
validator: sendTemplateViewModel.templateValidator),
|
validator: sendTemplateViewModel.templateValidator),
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.only(top: 20),
|
padding: EdgeInsets.only(top: 20),
|
||||||
child: AddressTextField(
|
child: AddressTextField(
|
||||||
selectedCurrency: sendTemplateViewModel.cryptoCurrency,
|
selectedCurrency: sendTemplateViewModel.cryptoCurrency,
|
||||||
controller: _addressController,
|
controller: _addressController,
|
||||||
onURIScanned: (uri) {
|
onURIScanned: (uri) {
|
||||||
final paymentRequest = PaymentRequest.fromUri(uri);
|
final paymentRequest = PaymentRequest.fromUri(uri);
|
||||||
_addressController.text = paymentRequest.address;
|
_addressController.text = paymentRequest.address;
|
||||||
_cryptoAmountController.text = paymentRequest.amount;
|
_cryptoAmountController.text = paymentRequest.amount;
|
||||||
},
|
},
|
||||||
options: [
|
options: [
|
||||||
AddressTextFieldOption.paste,
|
AddressTextFieldOption.paste,
|
||||||
AddressTextFieldOption.qrCode,
|
AddressTextFieldOption.qrCode,
|
||||||
AddressTextFieldOption.addressBook
|
AddressTextFieldOption.addressBook
|
||||||
],
|
],
|
||||||
onPushPasteButton: (context) async {
|
onPushPasteButton: (context) async {
|
||||||
template.output.resetParsedAddress();
|
template.output.resetParsedAddress();
|
||||||
await template.output.fetchParsedAddress(context);
|
await template.output.fetchParsedAddress(context);
|
||||||
},
|
},
|
||||||
onPushAddressBookButton: (context) async {
|
onPushAddressBookButton: (context) async {
|
||||||
template.output.resetParsedAddress();
|
template.output.resetParsedAddress();
|
||||||
await template.output.fetchParsedAddress(context);
|
await template.output.fetchParsedAddress(context);
|
||||||
},
|
},
|
||||||
buttonColor: Theme.of(context)
|
buttonColor: Theme.of(context).primaryTextTheme.headlineMedium!.color!,
|
||||||
.primaryTextTheme
|
borderColor: Theme.of(context).primaryTextTheme.headlineSmall!.color!,
|
||||||
.headlineMedium!
|
textStyle: TextStyle(
|
||||||
.color!,
|
fontSize: 14,
|
||||||
borderColor: Theme.of(context)
|
fontWeight: FontWeight.w500,
|
||||||
.primaryTextTheme
|
color: Colors.white,
|
||||||
.headlineSmall!
|
),
|
||||||
.color!,
|
hintStyle: TextStyle(
|
||||||
textStyle: TextStyle(
|
fontSize: 14,
|
||||||
fontSize: 14,
|
fontWeight: FontWeight.w500,
|
||||||
fontWeight: FontWeight.w500,
|
color: Theme.of(context).primaryTextTheme.headlineSmall!.decorationColor!,
|
||||||
color: Colors.white),
|
),
|
||||||
hintStyle: TextStyle(
|
validator: sendTemplateViewModel.addressValidator,
|
||||||
fontSize: 14,
|
),
|
||||||
fontWeight: FontWeight.w500,
|
),
|
||||||
color: Theme.of(context)
|
|
||||||
.primaryTextTheme
|
|
||||||
.headlineSmall!
|
|
||||||
.decorationColor!),
|
|
||||||
validator: sendTemplateViewModel.addressValidator)),
|
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(top: 20),
|
padding: const EdgeInsets.only(top: 20),
|
||||||
child: Focus(
|
child: Focus(
|
||||||
onFocusChange: (hasFocus) {
|
onFocusChange: (hasFocus) {
|
||||||
if (hasFocus) {
|
if (hasFocus) {
|
||||||
template.selectCurrency();
|
template.selectCurrency();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: BaseTextFormField(
|
child: BaseTextFormField(
|
||||||
focusNode: _cryptoAmountFocus,
|
focusNode: _cryptoAmountFocus,
|
||||||
controller: _cryptoAmountController,
|
controller: _cryptoAmountController,
|
||||||
keyboardType: TextInputType.numberWithOptions(
|
keyboardType: TextInputType.numberWithOptions(signed: false, decimal: true),
|
||||||
signed: false, decimal: true),
|
inputFormatters: [FilteringTextInputFormatter.deny(RegExp('[\\-|\\ ]'))],
|
||||||
inputFormatters: [
|
prefixIcon: Observer(
|
||||||
FilteringTextInputFormatter.deny(
|
builder: (_) => PrefixCurrencyIcon(
|
||||||
RegExp('[\\-|\\ ]'))
|
title: template.selectedCurrency.title,
|
||||||
],
|
isSelected: template.isCurrencySelected,
|
||||||
prefixIcon: Observer(
|
onTap: sendTemplateViewModel.walletCurrencies.length > 1
|
||||||
builder: (_) => PrefixCurrencyIcon(
|
? () => _presentPicker(context)
|
||||||
title: sendTemplateViewModel
|
: null,
|
||||||
.cryptoCurrency.title,
|
),
|
||||||
isSelected: template.isCurrencySelected)),
|
),
|
||||||
hintText: '0.0000',
|
hintText: '0.0000',
|
||||||
borderColor: Theme.of(context)
|
borderColor: Theme.of(context).primaryTextTheme.headlineSmall!.color!,
|
||||||
.primaryTextTheme
|
textStyle:
|
||||||
.headlineSmall!
|
TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
|
||||||
.color!,
|
placeholderTextStyle: TextStyle(
|
||||||
textStyle: TextStyle(
|
color: Theme.of(context).primaryTextTheme.headlineSmall!.decorationColor!,
|
||||||
fontSize: 14,
|
fontWeight: FontWeight.w500,
|
||||||
fontWeight: FontWeight.w500,
|
fontSize: 14),
|
||||||
color: Colors.white),
|
validator: sendTemplateViewModel.amountValidator,
|
||||||
placeholderTextStyle: TextStyle(
|
),
|
||||||
color: Theme.of(context)
|
),
|
||||||
.primaryTextTheme
|
),
|
||||||
.headlineSmall!
|
|
||||||
.decorationColor!,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
fontSize: 14),
|
|
||||||
validator: sendTemplateViewModel.amountValidator))),
|
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(top: 20),
|
padding: const EdgeInsets.only(top: 20),
|
||||||
child: Focus(
|
child: Focus(
|
||||||
onFocusChange: (hasFocus) {
|
onFocusChange: (hasFocus) {
|
||||||
if (hasFocus) {
|
if (hasFocus) {
|
||||||
template.selectFiat();
|
template.selectFiat();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: BaseTextFormField(
|
child: BaseTextFormField(
|
||||||
focusNode: _fiatAmountFocus,
|
focusNode: _fiatAmountFocus,
|
||||||
controller: _fiatAmountController,
|
controller: _fiatAmountController,
|
||||||
keyboardType: TextInputType.numberWithOptions(
|
keyboardType: TextInputType.numberWithOptions(signed: false, decimal: true),
|
||||||
signed: false, decimal: true),
|
inputFormatters: [FilteringTextInputFormatter.deny(RegExp('[\\-|\\ ]'))],
|
||||||
inputFormatters: [
|
prefixIcon: Observer(
|
||||||
FilteringTextInputFormatter.deny(
|
builder: (_) => PrefixCurrencyIcon(
|
||||||
RegExp('[\\-|\\ ]'))
|
title: sendTemplateViewModel.fiatCurrency,
|
||||||
],
|
isSelected: template.isFiatSelected)),
|
||||||
prefixIcon: Observer(
|
hintText: '0.00',
|
||||||
builder: (_) => PrefixCurrencyIcon(
|
borderColor: Theme.of(context).primaryTextTheme.headlineSmall!.color!,
|
||||||
title: sendTemplateViewModel.fiatCurrency,
|
textStyle:
|
||||||
isSelected: template.isFiatSelected)),
|
TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
|
||||||
hintText: '0.00',
|
placeholderTextStyle: TextStyle(
|
||||||
borderColor: Theme.of(context)
|
color: Theme.of(context).primaryTextTheme.headlineSmall!.decorationColor!,
|
||||||
.primaryTextTheme
|
fontWeight: FontWeight.w500,
|
||||||
.headlineSmall!
|
fontSize: 14,
|
||||||
.color!,
|
),
|
||||||
textStyle: TextStyle(
|
),
|
||||||
fontSize: 14,
|
),
|
||||||
fontWeight: FontWeight.w500,
|
),
|
||||||
color: Colors.white),
|
],
|
||||||
placeholderTextStyle: TextStyle(
|
),
|
||||||
color: Theme.of(context)
|
)
|
||||||
.primaryTextTheme
|
],
|
||||||
.headlineSmall!
|
),
|
||||||
.decorationColor!,
|
);
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
fontSize: 14))))
|
|
||||||
]))
|
|
||||||
]));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _setEffects(BuildContext context) {
|
void _setEffects(BuildContext context) {
|
||||||
|
@ -264,4 +247,16 @@ class SendTemplateCard extends StatelessWidget {
|
||||||
|
|
||||||
_effectsInstalled = true;
|
_effectsInstalled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _presentPicker(BuildContext context) {
|
||||||
|
showPopUp<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (_) => CurrencyPicker(
|
||||||
|
selectedAtIndex: sendTemplateViewModel.walletCurrencies.indexOf(template.selectedCurrency),
|
||||||
|
items: sendTemplateViewModel.walletCurrencies,
|
||||||
|
hintText: S.of(context).search_currency,
|
||||||
|
onItemSelected: (Currency cur) => template.changeSelectedCurrency(cur as CryptoCurrency),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,7 +40,8 @@ class PrivacyPage extends BasePage {
|
||||||
title: S.current.exchange,
|
title: S.current.exchange,
|
||||||
items: ExchangeApiMode.all,
|
items: ExchangeApiMode.all,
|
||||||
selectedItem: _privacySettingsViewModel.exchangeStatus,
|
selectedItem: _privacySettingsViewModel.exchangeStatus,
|
||||||
onItemSelected: (ExchangeApiMode mode) => _privacySettingsViewModel.setExchangeApiMode(mode),
|
onItemSelected: (ExchangeApiMode mode) =>
|
||||||
|
_privacySettingsViewModel.setExchangeApiMode(mode),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SettingsSwitcherCell(
|
SettingsSwitcherCell(
|
||||||
|
@ -68,6 +69,13 @@ class PrivacyPage extends BasePage {
|
||||||
onValueChange: (BuildContext _, bool value) {
|
onValueChange: (BuildContext _, bool value) {
|
||||||
_privacySettingsViewModel.setDisableSell(value);
|
_privacySettingsViewModel.setDisableSell(value);
|
||||||
}),
|
}),
|
||||||
|
if (_privacySettingsViewModel.canUseEtherscan)
|
||||||
|
SettingsSwitcherCell(
|
||||||
|
title: S.current.etherscan_history,
|
||||||
|
value: _privacySettingsViewModel.useEtherscan,
|
||||||
|
onValueChange: (BuildContext _, bool value) {
|
||||||
|
_privacySettingsViewModel.setUseEtherscan(value);
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -3,14 +3,23 @@ import 'package:cake_wallet/src/widgets/standard_list.dart';
|
||||||
import 'package:cake_wallet/src/widgets/standard_switch.dart';
|
import 'package:cake_wallet/src/widgets/standard_switch.dart';
|
||||||
|
|
||||||
class SettingsSwitcherCell extends StandardListRow {
|
class SettingsSwitcherCell extends StandardListRow {
|
||||||
SettingsSwitcherCell(
|
SettingsSwitcherCell({
|
||||||
{required String title, required this.value, this.onValueChange})
|
required String title,
|
||||||
: super(title: title, isSelected: false);
|
required this.value,
|
||||||
|
this.onValueChange,
|
||||||
|
Decoration? decoration,
|
||||||
|
this.leading,
|
||||||
|
void Function(BuildContext context)? onTap,
|
||||||
|
}) : super(title: title, isSelected: false, decoration: decoration, onTap: onTap);
|
||||||
|
|
||||||
final bool value;
|
final bool value;
|
||||||
final void Function(BuildContext context, bool value)? onValueChange;
|
final void Function(BuildContext context, bool value)? onValueChange;
|
||||||
|
final Widget? leading;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget buildTrailing(BuildContext context) => StandardSwitch(
|
Widget buildTrailing(BuildContext context) =>
|
||||||
value: value, onTaped: () => onValueChange?.call(context, !value));
|
StandardSwitch(value: value, onTaped: () => onValueChange?.call(context, !value));
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget? buildLeading(BuildContext context) => leading;
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,6 +46,7 @@ class WalletListBodyState extends State<WalletListBody> {
|
||||||
final litecoinIcon = Image.asset('assets/images/litecoin_icon.png', height: 24, width: 24);
|
final litecoinIcon = Image.asset('assets/images/litecoin_icon.png', height: 24, width: 24);
|
||||||
final nonWalletTypeIcon = Image.asset('assets/images/close.png', height: 24, width: 24);
|
final nonWalletTypeIcon = Image.asset('assets/images/close.png', height: 24, width: 24);
|
||||||
final havenIcon = Image.asset('assets/images/haven_logo.png', height: 24, width: 24);
|
final havenIcon = Image.asset('assets/images/haven_logo.png', height: 24, width: 24);
|
||||||
|
final ethereumIcon = Image.asset('assets/images/eth_icon.png', height: 24, width: 24);
|
||||||
final scrollController = ScrollController();
|
final scrollController = ScrollController();
|
||||||
final double tileHeight = 60;
|
final double tileHeight = 60;
|
||||||
Flushbar<void>? _progressBar;
|
Flushbar<void>? _progressBar;
|
||||||
|
@ -230,6 +231,8 @@ class WalletListBodyState extends State<WalletListBody> {
|
||||||
return litecoinIcon;
|
return litecoinIcon;
|
||||||
case WalletType.haven:
|
case WalletType.haven:
|
||||||
return havenIcon;
|
return havenIcon;
|
||||||
|
case WalletType.ethereum:
|
||||||
|
return ethereumIcon;
|
||||||
default:
|
default:
|
||||||
return nonWalletTypeIcon;
|
return nonWalletTypeIcon;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,8 @@
|
||||||
import 'dart:ui';
|
|
||||||
import 'package:cake_wallet/palette.dart';
|
import 'package:cake_wallet/palette.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class CheckboxWidget extends StatefulWidget {
|
class CheckboxWidget extends StatefulWidget {
|
||||||
CheckboxWidget({
|
CheckboxWidget({required this.value, required this.caption, required this.onChanged});
|
||||||
required this.value,
|
|
||||||
required this.caption,
|
|
||||||
required this.onChanged});
|
|
||||||
|
|
||||||
final bool value;
|
final bool value;
|
||||||
final String caption;
|
final String caption;
|
||||||
|
@ -26,55 +21,45 @@ class CheckboxWidgetState extends State<CheckboxWidget> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return GestureDetector(
|
return InkWell(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
value = !value;
|
value = !value;
|
||||||
onChanged(value);
|
onChanged(value);
|
||||||
setState(() {});
|
setState(() {});
|
||||||
},
|
},
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Container(
|
Container(
|
||||||
height: 16,
|
height: 24.0,
|
||||||
width: 16,
|
width: 24.0,
|
||||||
|
margin: EdgeInsets.only(right: 10.0),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: value
|
border: Border.all(
|
||||||
? Palette.blueCraiola
|
color: Theme.of(context).primaryTextTheme.bodySmall!.color!,
|
||||||
: Theme.of(context)
|
width: 1.0,
|
||||||
.accentTextTheme!
|
|
||||||
.titleMedium!
|
|
||||||
.decorationColor!,
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(2)),
|
|
||||||
border: Border.all(
|
|
||||||
color: value
|
|
||||||
? Palette.blueCraiola
|
|
||||||
: Theme.of(context)
|
|
||||||
.accentTextTheme!
|
|
||||||
.labelSmall!
|
|
||||||
.color!,
|
|
||||||
width: 1)),
|
|
||||||
child: value
|
|
||||||
? Center(
|
|
||||||
child: Icon(
|
|
||||||
Icons.done,
|
|
||||||
color: Colors.white,
|
|
||||||
size: 14,
|
|
||||||
),
|
),
|
||||||
)
|
borderRadius: BorderRadius.all(
|
||||||
: Offstage(),
|
Radius.circular(8.0),
|
||||||
|
),
|
||||||
|
color: Theme.of(context).colorScheme.background,
|
||||||
|
),
|
||||||
|
child: value
|
||||||
|
? Icon(
|
||||||
|
Icons.check,
|
||||||
|
color: Colors.blue,
|
||||||
|
size: 20.0,
|
||||||
|
)
|
||||||
|
: null,
|
||||||
),
|
),
|
||||||
Padding(
|
Expanded(
|
||||||
padding: EdgeInsets.only(left: 16),
|
|
||||||
child: Text(
|
child: Text(
|
||||||
caption,
|
caption,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Theme.of(context).primaryTextTheme!.titleLarge!.color!,
|
fontWeight: FontWeight.bold,
|
||||||
fontSize: 18,
|
fontSize: 14.0,
|
||||||
fontFamily: 'Lato',
|
color: Theme.of(context).primaryTextTheme.titleLarge!.color!,
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
decoration: TextDecoration.none
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -82,4 +67,4 @@ class CheckboxWidgetState extends State<CheckboxWidget> {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
// ignore_for_file: deprecated_member_use
|
// ignore_for_file: deprecated_member_use
|
||||||
|
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:cake_wallet/src/widgets/search_bar_widget.dart';
|
import 'package:cake_wallet/src/widgets/search_bar_widget.dart';
|
||||||
import 'package:cake_wallet/utils/responsive_layout_util.dart';
|
import 'package:cake_wallet/utils/responsive_layout_util.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
@ -145,8 +147,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
||||||
borderRadius: BorderRadius.all(Radius.circular(30)),
|
borderRadius: BorderRadius.all(Radius.circular(30)),
|
||||||
child: Container(
|
child: Container(
|
||||||
color: Theme.of(context)
|
color: Theme.of(context)
|
||||||
.accentTextTheme!
|
.accentTextTheme.titleLarge!
|
||||||
.titleLarge!
|
|
||||||
.color!,
|
.color!,
|
||||||
child: ConstrainedBox(
|
child: ConstrainedBox(
|
||||||
constraints: BoxConstraints(
|
constraints: BoxConstraints(
|
||||||
|
@ -163,8 +164,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
||||||
),
|
),
|
||||||
Divider(
|
Divider(
|
||||||
color: Theme.of(context)
|
color: Theme.of(context)
|
||||||
.accentTextTheme!
|
.accentTextTheme.titleLarge!
|
||||||
.titleLarge!
|
|
||||||
.backgroundColor!,
|
.backgroundColor!,
|
||||||
height: 1,
|
height: 1,
|
||||||
),
|
),
|
||||||
|
@ -194,8 +194,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
||||||
fontFamily: 'Lato',
|
fontFamily: 'Lato',
|
||||||
decoration: TextDecoration.none,
|
decoration: TextDecoration.none,
|
||||||
color: Theme.of(context)
|
color: Theme.of(context)
|
||||||
.primaryTextTheme!
|
.primaryTextTheme.titleLarge!
|
||||||
.titleLarge!
|
|
||||||
.color!,
|
.color!,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -217,8 +216,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
||||||
Widget itemsList() {
|
Widget itemsList() {
|
||||||
return Container(
|
return Container(
|
||||||
color: Theme.of(context)
|
color: Theme.of(context)
|
||||||
.accentTextTheme!
|
.accentTextTheme.titleLarge!
|
||||||
.titleLarge!
|
|
||||||
.backgroundColor!,
|
.backgroundColor!,
|
||||||
child: widget.isGridView
|
child: widget.isGridView
|
||||||
? GridView.builder(
|
? GridView.builder(
|
||||||
|
@ -240,8 +238,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
||||||
separatorBuilder: (context, index) => widget.isSeparated
|
separatorBuilder: (context, index) => widget.isSeparated
|
||||||
? Divider(
|
? Divider(
|
||||||
color: Theme.of(context)
|
color: Theme.of(context)
|
||||||
.accentTextTheme!
|
.accentTextTheme.titleLarge!
|
||||||
.titleLarge!
|
|
||||||
.backgroundColor!,
|
.backgroundColor!,
|
||||||
height: 1,
|
height: 1,
|
||||||
)
|
)
|
||||||
|
@ -254,15 +251,9 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
||||||
|
|
||||||
Widget buildItem(int index) {
|
Widget buildItem(int index) {
|
||||||
final item = filteredItems[index];
|
final item = filteredItems[index];
|
||||||
final tag = item is Currency ? item.tag : null;
|
|
||||||
|
|
||||||
final icon = item is Currency && item.iconPath != null
|
final tag = item is Currency ? item.tag : null;
|
||||||
? Image.asset(
|
final icon = _getItemIcon(item);
|
||||||
item.iconPath!,
|
|
||||||
height: 20.0,
|
|
||||||
width: 20.0,
|
|
||||||
)
|
|
||||||
: null;
|
|
||||||
|
|
||||||
final image = images.isNotEmpty ? filteredImages[index] : icon;
|
final image = images.isNotEmpty ? filteredImages[index] : icon;
|
||||||
|
|
||||||
|
@ -274,8 +265,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
||||||
child: Container(
|
child: Container(
|
||||||
height: 55,
|
height: 55,
|
||||||
color: Theme.of(context)
|
color: Theme.of(context)
|
||||||
.accentTextTheme!
|
.accentTextTheme.titleLarge!
|
||||||
.titleLarge!
|
|
||||||
.color!,
|
.color!,
|
||||||
padding: EdgeInsets.symmetric(horizontal: 24),
|
padding: EdgeInsets.symmetric(horizontal: 24),
|
||||||
child: Row(
|
child: Row(
|
||||||
|
@ -298,8 +288,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
||||||
fontFamily: 'Lato',
|
fontFamily: 'Lato',
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
color: Theme.of(context)
|
color: Theme.of(context)
|
||||||
.primaryTextTheme!
|
.primaryTextTheme.titleLarge!
|
||||||
.titleLarge!
|
|
||||||
.color!,
|
.color!,
|
||||||
decoration: TextDecoration.none,
|
decoration: TextDecoration.none,
|
||||||
),
|
),
|
||||||
|
@ -318,8 +307,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
||||||
fontSize: 7.0,
|
fontSize: 7.0,
|
||||||
fontFamily: 'Lato',
|
fontFamily: 'Lato',
|
||||||
color: Theme.of(context)
|
color: Theme.of(context)
|
||||||
.textTheme!
|
.textTheme.bodyMedium!
|
||||||
.bodyMedium!
|
|
||||||
.color!),
|
.color!),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -327,8 +315,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
||||||
borderRadius: BorderRadius.circular(6.0),
|
borderRadius: BorderRadius.circular(6.0),
|
||||||
//border: Border.all(color: ),
|
//border: Border.all(color: ),
|
||||||
color: Theme.of(context)
|
color: Theme.of(context)
|
||||||
.textTheme!
|
.textTheme.bodyMedium!
|
||||||
.bodyMedium!
|
|
||||||
.decorationColor!,
|
.decorationColor!,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -345,15 +332,9 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
||||||
|
|
||||||
Widget buildSelectedItem(int index) {
|
Widget buildSelectedItem(int index) {
|
||||||
final item = items[index];
|
final item = items[index];
|
||||||
final tag = item is Currency ? item.tag : null;
|
|
||||||
|
|
||||||
final icon = item is Currency && item.iconPath != null
|
final tag = item is Currency ? item.tag : null;
|
||||||
? Image.asset(
|
final icon = _getItemIcon(item);
|
||||||
item.iconPath!,
|
|
||||||
height: 20.0,
|
|
||||||
width: 20.0,
|
|
||||||
)
|
|
||||||
: null;
|
|
||||||
|
|
||||||
final image = images.isNotEmpty ? images[index] : icon;
|
final image = images.isNotEmpty ? images[index] : icon;
|
||||||
|
|
||||||
|
@ -364,8 +345,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
||||||
child: Container(
|
child: Container(
|
||||||
height: 55,
|
height: 55,
|
||||||
color: Theme.of(context)
|
color: Theme.of(context)
|
||||||
.accentTextTheme!
|
.accentTextTheme.titleLarge!
|
||||||
.titleLarge!
|
|
||||||
.color!,
|
.color!,
|
||||||
padding: EdgeInsets.symmetric(horizontal: 24),
|
padding: EdgeInsets.symmetric(horizontal: 24),
|
||||||
child: Row(
|
child: Row(
|
||||||
|
@ -388,8 +368,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
||||||
fontFamily: 'Lato',
|
fontFamily: 'Lato',
|
||||||
fontWeight: FontWeight.w700,
|
fontWeight: FontWeight.w700,
|
||||||
color: Theme.of(context)
|
color: Theme.of(context)
|
||||||
.primaryTextTheme!
|
.primaryTextTheme.titleLarge!
|
||||||
.titleLarge!
|
|
||||||
.color!,
|
.color!,
|
||||||
decoration: TextDecoration.none,
|
decoration: TextDecoration.none,
|
||||||
),
|
),
|
||||||
|
@ -408,8 +387,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
||||||
fontSize: 7.0,
|
fontSize: 7.0,
|
||||||
fontFamily: 'Lato',
|
fontFamily: 'Lato',
|
||||||
color: Theme.of(context)
|
color: Theme.of(context)
|
||||||
.textTheme!
|
.textTheme.bodyMedium!
|
||||||
.bodyMedium!
|
|
||||||
.color!),
|
.color!),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -417,8 +395,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
||||||
borderRadius: BorderRadius.circular(6.0),
|
borderRadius: BorderRadius.circular(6.0),
|
||||||
//border: Border.all(color: ),
|
//border: Border.all(color: ),
|
||||||
color: Theme.of(context)
|
color: Theme.of(context)
|
||||||
.textTheme!
|
.textTheme.bodyMedium!
|
||||||
.bodyMedium!
|
|
||||||
.decorationColor!,
|
.decorationColor!,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -429,12 +406,40 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
||||||
),
|
),
|
||||||
Icon(Icons.check_circle,
|
Icon(Icons.check_circle,
|
||||||
color: Theme.of(context)
|
color: Theme.of(context)
|
||||||
.accentTextTheme!
|
.accentTextTheme.bodyLarge!
|
||||||
.bodyLarge!
|
|
||||||
.color!),
|
.color!),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget? _getItemIcon(Item item) {
|
||||||
|
if (item is Currency) {
|
||||||
|
if (item.iconPath != null) {
|
||||||
|
return Image.asset(
|
||||||
|
item.iconPath!,
|
||||||
|
height: 20.0,
|
||||||
|
width: 20.0,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Container(
|
||||||
|
height: 20.0,
|
||||||
|
width: 20.0,
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
item.name.substring(0, min(item.name.length, 2)).toUpperCase(),
|
||||||
|
style: TextStyle(fontSize: 11),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
color: Colors.grey.shade400,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,11 +5,12 @@ import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class StandardListRow extends StatelessWidget {
|
class StandardListRow extends StatelessWidget {
|
||||||
StandardListRow(
|
StandardListRow(
|
||||||
{required this.title, required this.isSelected, this.onTap});
|
{required this.title, required this.isSelected, this.onTap, this.decoration});
|
||||||
|
|
||||||
final String title;
|
final String title;
|
||||||
final bool isSelected;
|
final bool isSelected;
|
||||||
final void Function(BuildContext context)? onTap;
|
final void Function(BuildContext context)? onTap;
|
||||||
|
final Decoration? decoration;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -19,9 +20,11 @@ class StandardListRow extends StatelessWidget {
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: () => onTap?.call(context),
|
onTap: () => onTap?.call(context),
|
||||||
child: Container(
|
child: Container(
|
||||||
color: _backgroundColor(context),
|
|
||||||
height: 56,
|
height: 56,
|
||||||
padding: EdgeInsets.only(left: 24, right: 24),
|
padding: EdgeInsets.only(left: 24, right: 24),
|
||||||
|
decoration: decoration ?? BoxDecoration(
|
||||||
|
color: _backgroundColor(context),
|
||||||
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
|
@ -54,7 +57,7 @@ class StandardListRow extends StatelessWidget {
|
||||||
|
|
||||||
Color titleColor(BuildContext context) => isSelected
|
Color titleColor(BuildContext context) => isSelected
|
||||||
? Palette.blueCraiola
|
? Palette.blueCraiola
|
||||||
: Theme.of(context).primaryTextTheme!.titleLarge!.color!;
|
: Theme.of(context).primaryTextTheme.titleLarge!.color!;
|
||||||
|
|
||||||
Color _backgroundColor(BuildContext context) {
|
Color _backgroundColor(BuildContext context) {
|
||||||
return Theme.of(context).colorScheme.background;
|
return Theme.of(context).colorScheme.background;
|
||||||
|
@ -89,7 +92,7 @@ class StandardListSeparator extends StatelessWidget {
|
||||||
child: Container(
|
child: Container(
|
||||||
height: height,
|
height: height,
|
||||||
color: Theme.of(context)
|
color: Theme.of(context)
|
||||||
.primaryTextTheme!
|
.primaryTextTheme
|
||||||
.titleLarge
|
.titleLarge
|
||||||
?.backgroundColor));
|
?.backgroundColor));
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,9 @@ import 'package:cake_wallet/entities/cake_2fa_preset_options.dart';
|
||||||
import 'package:cake_wallet/entities/exchange_api_mode.dart';
|
import 'package:cake_wallet/entities/exchange_api_mode.dart';
|
||||||
import 'package:cake_wallet/entities/pin_code_required_duration.dart';
|
import 'package:cake_wallet/entities/pin_code_required_duration.dart';
|
||||||
import 'package:cake_wallet/entities/preferences_key.dart';
|
import 'package:cake_wallet/entities/preferences_key.dart';
|
||||||
|
import 'package:cake_wallet/entities/sort_balance_types.dart';
|
||||||
import 'package:cake_wallet/utils/device_info.dart';
|
import 'package:cake_wallet/utils/device_info.dart';
|
||||||
|
import 'package:cake_wallet/ethereum/ethereum.dart';
|
||||||
import 'package:cw_core/transaction_priority.dart';
|
import 'package:cw_core/transaction_priority.dart';
|
||||||
import 'package:cake_wallet/themes/theme_base.dart';
|
import 'package:cake_wallet/themes/theme_base.dart';
|
||||||
import 'package:cake_wallet/themes/theme_list.dart';
|
import 'package:cake_wallet/themes/theme_list.dart';
|
||||||
|
@ -66,10 +68,14 @@ abstract class SettingsStoreBase with Store {
|
||||||
required bool initialShouldRequireTOTP2FAForAddingContacts,
|
required bool initialShouldRequireTOTP2FAForAddingContacts,
|
||||||
required bool initialShouldRequireTOTP2FAForCreatingNewWallets,
|
required bool initialShouldRequireTOTP2FAForCreatingNewWallets,
|
||||||
required bool initialShouldRequireTOTP2FAForAllSecurityAndBackupSettings,
|
required bool initialShouldRequireTOTP2FAForAllSecurityAndBackupSettings,
|
||||||
|
required this.sortBalanceBy,
|
||||||
|
required this.pinNativeTokenAtTop,
|
||||||
|
required this.useEtherscan,
|
||||||
TransactionPriority? initialBitcoinTransactionPriority,
|
TransactionPriority? initialBitcoinTransactionPriority,
|
||||||
TransactionPriority? initialMoneroTransactionPriority,
|
TransactionPriority? initialMoneroTransactionPriority,
|
||||||
TransactionPriority? initialHavenTransactionPriority,
|
TransactionPriority? initialHavenTransactionPriority,
|
||||||
TransactionPriority? initialLitecoinTransactionPriority})
|
TransactionPriority? initialLitecoinTransactionPriority,
|
||||||
|
TransactionPriority? initialEthereumTransactionPriority})
|
||||||
: nodes = ObservableMap<WalletType, Node>.of(nodes),
|
: nodes = ObservableMap<WalletType, Node>.of(nodes),
|
||||||
_sharedPreferences = sharedPreferences,
|
_sharedPreferences = sharedPreferences,
|
||||||
fiatCurrency = initialFiatCurrency,
|
fiatCurrency = initialFiatCurrency,
|
||||||
|
@ -120,6 +126,10 @@ abstract class SettingsStoreBase with Store {
|
||||||
priority[WalletType.litecoin] = initialLitecoinTransactionPriority;
|
priority[WalletType.litecoin] = initialLitecoinTransactionPriority;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (initialEthereumTransactionPriority != null) {
|
||||||
|
priority[WalletType.ethereum] = initialEthereumTransactionPriority;
|
||||||
|
}
|
||||||
|
|
||||||
reaction(
|
reaction(
|
||||||
(_) => fiatCurrency,
|
(_) => fiatCurrency,
|
||||||
(FiatCurrency fiatCurrency) => sharedPreferences.setString(
|
(FiatCurrency fiatCurrency) => sharedPreferences.setString(
|
||||||
|
@ -145,6 +155,9 @@ abstract class SettingsStoreBase with Store {
|
||||||
case WalletType.haven:
|
case WalletType.haven:
|
||||||
key = PreferencesKey.havenTransactionPriority;
|
key = PreferencesKey.havenTransactionPriority;
|
||||||
break;
|
break;
|
||||||
|
case WalletType.ethereum:
|
||||||
|
key = PreferencesKey.ethereumTransactionPriority;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
key = null;
|
key = null;
|
||||||
}
|
}
|
||||||
|
@ -279,6 +292,21 @@ abstract class SettingsStoreBase with Store {
|
||||||
(ExchangeApiMode mode) =>
|
(ExchangeApiMode mode) =>
|
||||||
sharedPreferences.setInt(PreferencesKey.exchangeStatusKey, mode.serialize()));
|
sharedPreferences.setInt(PreferencesKey.exchangeStatusKey, mode.serialize()));
|
||||||
|
|
||||||
|
reaction(
|
||||||
|
(_) => sortBalanceBy,
|
||||||
|
(SortBalanceBy sortBalanceBy) =>
|
||||||
|
_sharedPreferences.setInt(PreferencesKey.sortBalanceBy, sortBalanceBy.index));
|
||||||
|
|
||||||
|
reaction(
|
||||||
|
(_) => pinNativeTokenAtTop,
|
||||||
|
(bool pinNativeTokenAtTop) =>
|
||||||
|
_sharedPreferences.setBool(PreferencesKey.pinNativeTokenAtTop, pinNativeTokenAtTop));
|
||||||
|
|
||||||
|
reaction(
|
||||||
|
(_) => useEtherscan,
|
||||||
|
(bool useEtherscan) =>
|
||||||
|
_sharedPreferences.setBool(PreferencesKey.useEtherscan, useEtherscan));
|
||||||
|
|
||||||
this.nodes.observe((change) {
|
this.nodes.observe((change) {
|
||||||
if (change.newValue != null && change.key != null) {
|
if (change.newValue != null && change.key != null) {
|
||||||
_saveCurrentNode(change.newValue!, change.key!);
|
_saveCurrentNode(change.newValue!, change.key!);
|
||||||
|
@ -385,6 +413,15 @@ abstract class SettingsStoreBase with Store {
|
||||||
@observable
|
@observable
|
||||||
ObservableMap<WalletType, TransactionPriority> priority;
|
ObservableMap<WalletType, TransactionPriority> priority;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
SortBalanceBy sortBalanceBy;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
bool pinNativeTokenAtTop;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
bool useEtherscan;
|
||||||
|
|
||||||
String appVersion;
|
String appVersion;
|
||||||
|
|
||||||
String deviceName;
|
String deviceName;
|
||||||
|
@ -429,6 +466,7 @@ abstract class SettingsStoreBase with Store {
|
||||||
|
|
||||||
TransactionPriority? havenTransactionPriority;
|
TransactionPriority? havenTransactionPriority;
|
||||||
TransactionPriority? litecoinTransactionPriority;
|
TransactionPriority? litecoinTransactionPriority;
|
||||||
|
TransactionPriority? ethereumTransactionPriority;
|
||||||
|
|
||||||
if (sharedPreferences.getInt(PreferencesKey.havenTransactionPriority) != null) {
|
if (sharedPreferences.getInt(PreferencesKey.havenTransactionPriority) != null) {
|
||||||
havenTransactionPriority = monero?.deserializeMoneroTransactionPriority(
|
havenTransactionPriority = monero?.deserializeMoneroTransactionPriority(
|
||||||
|
@ -438,11 +476,16 @@ abstract class SettingsStoreBase with Store {
|
||||||
litecoinTransactionPriority = bitcoin?.deserializeLitecoinTransactionPriority(
|
litecoinTransactionPriority = bitcoin?.deserializeLitecoinTransactionPriority(
|
||||||
sharedPreferences.getInt(PreferencesKey.litecoinTransactionPriority)!);
|
sharedPreferences.getInt(PreferencesKey.litecoinTransactionPriority)!);
|
||||||
}
|
}
|
||||||
|
if (sharedPreferences.getInt(PreferencesKey.ethereumTransactionPriority) != null) {
|
||||||
|
ethereumTransactionPriority = bitcoin?.deserializeLitecoinTransactionPriority(
|
||||||
|
sharedPreferences.getInt(PreferencesKey.ethereumTransactionPriority)!);
|
||||||
|
}
|
||||||
|
|
||||||
moneroTransactionPriority ??= monero?.getDefaultTransactionPriority();
|
moneroTransactionPriority ??= monero?.getDefaultTransactionPriority();
|
||||||
bitcoinTransactionPriority ??= bitcoin?.getMediumTransactionPriority();
|
bitcoinTransactionPriority ??= bitcoin?.getMediumTransactionPriority();
|
||||||
havenTransactionPriority ??= monero?.getDefaultTransactionPriority();
|
havenTransactionPriority ??= monero?.getDefaultTransactionPriority();
|
||||||
litecoinTransactionPriority ??= bitcoin?.getLitecoinTransactionPriorityMedium();
|
litecoinTransactionPriority ??= bitcoin?.getLitecoinTransactionPriorityMedium();
|
||||||
|
ethereumTransactionPriority ??= ethereum?.getDefaultTransactionPriority();
|
||||||
|
|
||||||
final currentBalanceDisplayMode = BalanceDisplayMode.deserialize(
|
final currentBalanceDisplayMode = BalanceDisplayMode.deserialize(
|
||||||
raw: sharedPreferences.getInt(PreferencesKey.currentBalanceDisplayModeKey)!);
|
raw: sharedPreferences.getInt(PreferencesKey.currentBalanceDisplayModeKey)!);
|
||||||
|
@ -502,6 +545,12 @@ abstract class SettingsStoreBase with Store {
|
||||||
final pinCodeTimeOutDuration = timeOutDuration != null
|
final pinCodeTimeOutDuration = timeOutDuration != null
|
||||||
? PinCodeRequiredDuration.deserialize(raw: timeOutDuration)
|
? PinCodeRequiredDuration.deserialize(raw: timeOutDuration)
|
||||||
: defaultPinCodeTimeOutDuration;
|
: defaultPinCodeTimeOutDuration;
|
||||||
|
final sortBalanceBy =
|
||||||
|
SortBalanceBy.values[sharedPreferences.getInt(PreferencesKey.sortBalanceBy) ?? 0];
|
||||||
|
final pinNativeTokenAtTop =
|
||||||
|
sharedPreferences.getBool(PreferencesKey.pinNativeTokenAtTop) ?? true;
|
||||||
|
final useEtherscan =
|
||||||
|
sharedPreferences.getBool(PreferencesKey.useEtherscan) ?? true;
|
||||||
|
|
||||||
// If no value
|
// If no value
|
||||||
if (pinLength == null || pinLength == 0) {
|
if (pinLength == null || pinLength == 0) {
|
||||||
|
@ -516,10 +565,12 @@ abstract class SettingsStoreBase with Store {
|
||||||
final litecoinElectrumServerId =
|
final litecoinElectrumServerId =
|
||||||
sharedPreferences.getInt(PreferencesKey.currentLitecoinElectrumSererIdKey);
|
sharedPreferences.getInt(PreferencesKey.currentLitecoinElectrumSererIdKey);
|
||||||
final havenNodeId = sharedPreferences.getInt(PreferencesKey.currentHavenNodeIdKey);
|
final havenNodeId = sharedPreferences.getInt(PreferencesKey.currentHavenNodeIdKey);
|
||||||
|
final ethereumNodeId = sharedPreferences.getInt(PreferencesKey.currentEthereumNodeIdKey);
|
||||||
final moneroNode = nodeSource.get(nodeId);
|
final moneroNode = nodeSource.get(nodeId);
|
||||||
final bitcoinElectrumServer = nodeSource.get(bitcoinElectrumServerId);
|
final bitcoinElectrumServer = nodeSource.get(bitcoinElectrumServerId);
|
||||||
final litecoinElectrumServer = nodeSource.get(litecoinElectrumServerId);
|
final litecoinElectrumServer = nodeSource.get(litecoinElectrumServerId);
|
||||||
final havenNode = nodeSource.get(havenNodeId);
|
final havenNode = nodeSource.get(havenNodeId);
|
||||||
|
final ethereumNode = nodeSource.get(ethereumNodeId);
|
||||||
final packageInfo = await PackageInfo.fromPlatform();
|
final packageInfo = await PackageInfo.fromPlatform();
|
||||||
final deviceName = await _getDeviceName() ?? '';
|
final deviceName = await _getDeviceName() ?? '';
|
||||||
final shouldShowYatPopup = sharedPreferences.getBool(PreferencesKey.shouldShowYatPopup) ?? true;
|
final shouldShowYatPopup = sharedPreferences.getBool(PreferencesKey.shouldShowYatPopup) ?? true;
|
||||||
|
@ -542,6 +593,10 @@ abstract class SettingsStoreBase with Store {
|
||||||
nodes[WalletType.haven] = havenNode;
|
nodes[WalletType.haven] = havenNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ethereumNode != null) {
|
||||||
|
nodes[WalletType.ethereum] = ethereumNode;
|
||||||
|
}
|
||||||
|
|
||||||
return SettingsStore(
|
return SettingsStore(
|
||||||
sharedPreferences: sharedPreferences,
|
sharedPreferences: sharedPreferences,
|
||||||
initialShouldShowMarketPlaceInDashboard: shouldShowMarketPlaceInDashboard,
|
initialShouldShowMarketPlaceInDashboard: shouldShowMarketPlaceInDashboard,
|
||||||
|
@ -567,6 +622,9 @@ abstract class SettingsStoreBase with Store {
|
||||||
initialPinLength: pinLength,
|
initialPinLength: pinLength,
|
||||||
pinTimeOutDuration: pinCodeTimeOutDuration,
|
pinTimeOutDuration: pinCodeTimeOutDuration,
|
||||||
initialLanguageCode: savedLanguageCode,
|
initialLanguageCode: savedLanguageCode,
|
||||||
|
sortBalanceBy: sortBalanceBy,
|
||||||
|
pinNativeTokenAtTop: pinNativeTokenAtTop,
|
||||||
|
useEtherscan: useEtherscan,
|
||||||
initialMoneroTransactionPriority: moneroTransactionPriority,
|
initialMoneroTransactionPriority: moneroTransactionPriority,
|
||||||
initialBitcoinTransactionPriority: bitcoinTransactionPriority,
|
initialBitcoinTransactionPriority: bitcoinTransactionPriority,
|
||||||
initialHavenTransactionPriority: havenTransactionPriority,
|
initialHavenTransactionPriority: havenTransactionPriority,
|
||||||
|
@ -582,6 +640,7 @@ abstract class SettingsStoreBase with Store {
|
||||||
initialShouldRequireTOTP2FAForCreatingNewWallets: shouldRequireTOTP2FAForCreatingNewWallets,
|
initialShouldRequireTOTP2FAForCreatingNewWallets: shouldRequireTOTP2FAForCreatingNewWallets,
|
||||||
initialShouldRequireTOTP2FAForAllSecurityAndBackupSettings:
|
initialShouldRequireTOTP2FAForAllSecurityAndBackupSettings:
|
||||||
shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
|
shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
|
||||||
|
initialEthereumTransactionPriority: ethereumTransactionPriority,
|
||||||
shouldShowYatPopup: shouldShowYatPopup);
|
shouldShowYatPopup: shouldShowYatPopup);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -608,6 +667,11 @@ abstract class SettingsStoreBase with Store {
|
||||||
sharedPreferences.getInt(PreferencesKey.litecoinTransactionPriority)!) ??
|
sharedPreferences.getInt(PreferencesKey.litecoinTransactionPriority)!) ??
|
||||||
priority[WalletType.litecoin]!;
|
priority[WalletType.litecoin]!;
|
||||||
}
|
}
|
||||||
|
if (sharedPreferences.getInt(PreferencesKey.ethereumTransactionPriority) != null) {
|
||||||
|
priority[WalletType.ethereum] = ethereum?.deserializeEthereumTransactionPriority(
|
||||||
|
sharedPreferences.getInt(PreferencesKey.ethereumTransactionPriority)!) ??
|
||||||
|
priority[WalletType.ethereum]!;
|
||||||
|
}
|
||||||
|
|
||||||
balanceDisplayMode = BalanceDisplayMode.deserialize(
|
balanceDisplayMode = BalanceDisplayMode.deserialize(
|
||||||
raw: sharedPreferences.getInt(PreferencesKey.currentBalanceDisplayModeKey)!);
|
raw: sharedPreferences.getInt(PreferencesKey.currentBalanceDisplayModeKey)!);
|
||||||
|
@ -616,7 +680,7 @@ abstract class SettingsStoreBase with Store {
|
||||||
shouldSaveRecipientAddress;
|
shouldSaveRecipientAddress;
|
||||||
totpSecretKey = sharedPreferences.getString(PreferencesKey.totpSecretKey) ?? totpSecretKey;
|
totpSecretKey = sharedPreferences.getString(PreferencesKey.totpSecretKey) ?? totpSecretKey;
|
||||||
useTOTP2FA = sharedPreferences.getBool(PreferencesKey.useTOTP2FA) ?? useTOTP2FA;
|
useTOTP2FA = sharedPreferences.getBool(PreferencesKey.useTOTP2FA) ?? useTOTP2FA;
|
||||||
|
|
||||||
numberOfFailedTokenTrials =
|
numberOfFailedTokenTrials =
|
||||||
sharedPreferences.getInt(PreferencesKey.failedTotpTokenTrials) ?? numberOfFailedTokenTrials;
|
sharedPreferences.getInt(PreferencesKey.failedTotpTokenTrials) ?? numberOfFailedTokenTrials;
|
||||||
sharedPreferences.getBool(PreferencesKey.shouldSaveRecipientAddressKey) ??
|
sharedPreferences.getBool(PreferencesKey.shouldSaveRecipientAddressKey) ??
|
||||||
|
@ -677,6 +741,10 @@ abstract class SettingsStoreBase with Store {
|
||||||
languageCode = sharedPreferences.getString(PreferencesKey.currentLanguageCode) ?? languageCode;
|
languageCode = sharedPreferences.getString(PreferencesKey.currentLanguageCode) ?? languageCode;
|
||||||
shouldShowYatPopup =
|
shouldShowYatPopup =
|
||||||
sharedPreferences.getBool(PreferencesKey.shouldShowYatPopup) ?? shouldShowYatPopup;
|
sharedPreferences.getBool(PreferencesKey.shouldShowYatPopup) ?? shouldShowYatPopup;
|
||||||
|
sortBalanceBy = SortBalanceBy
|
||||||
|
.values[sharedPreferences.getInt(PreferencesKey.sortBalanceBy) ?? sortBalanceBy.index];
|
||||||
|
pinNativeTokenAtTop = sharedPreferences.getBool(PreferencesKey.pinNativeTokenAtTop) ?? true;
|
||||||
|
useEtherscan = sharedPreferences.getBool(PreferencesKey.useEtherscan) ?? true;
|
||||||
|
|
||||||
final nodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
|
final nodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
|
||||||
final bitcoinElectrumServerId =
|
final bitcoinElectrumServerId =
|
||||||
|
@ -684,10 +752,12 @@ abstract class SettingsStoreBase with Store {
|
||||||
final litecoinElectrumServerId =
|
final litecoinElectrumServerId =
|
||||||
sharedPreferences.getInt(PreferencesKey.currentLitecoinElectrumSererIdKey);
|
sharedPreferences.getInt(PreferencesKey.currentLitecoinElectrumSererIdKey);
|
||||||
final havenNodeId = sharedPreferences.getInt(PreferencesKey.currentHavenNodeIdKey);
|
final havenNodeId = sharedPreferences.getInt(PreferencesKey.currentHavenNodeIdKey);
|
||||||
|
final ethereumNodeId = sharedPreferences.getInt(PreferencesKey.currentEthereumNodeIdKey);
|
||||||
final moneroNode = nodeSource.get(nodeId);
|
final moneroNode = nodeSource.get(nodeId);
|
||||||
final bitcoinElectrumServer = nodeSource.get(bitcoinElectrumServerId);
|
final bitcoinElectrumServer = nodeSource.get(bitcoinElectrumServerId);
|
||||||
final litecoinElectrumServer = nodeSource.get(litecoinElectrumServerId);
|
final litecoinElectrumServer = nodeSource.get(litecoinElectrumServerId);
|
||||||
final havenNode = nodeSource.get(havenNodeId);
|
final havenNode = nodeSource.get(havenNodeId);
|
||||||
|
final ethereumNode = nodeSource.get(ethereumNodeId);
|
||||||
|
|
||||||
if (moneroNode != null) {
|
if (moneroNode != null) {
|
||||||
nodes[WalletType.monero] = moneroNode;
|
nodes[WalletType.monero] = moneroNode;
|
||||||
|
@ -704,6 +774,10 @@ abstract class SettingsStoreBase with Store {
|
||||||
if (havenNode != null) {
|
if (havenNode != null) {
|
||||||
nodes[WalletType.haven] = havenNode;
|
nodes[WalletType.haven] = havenNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ethereumNode != null) {
|
||||||
|
nodes[WalletType.ethereum] = ethereumNode;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _saveCurrentNode(Node node, WalletType walletType) async {
|
Future<void> _saveCurrentNode(Node node, WalletType walletType) async {
|
||||||
|
@ -722,6 +796,9 @@ abstract class SettingsStoreBase with Store {
|
||||||
case WalletType.haven:
|
case WalletType.haven:
|
||||||
await _sharedPreferences.setInt(PreferencesKey.currentHavenNodeIdKey, node.key as int);
|
await _sharedPreferences.setInt(PreferencesKey.currentHavenNodeIdKey, node.key as int);
|
||||||
break;
|
break;
|
||||||
|
case WalletType.ethereum:
|
||||||
|
await _sharedPreferences.setInt(PreferencesKey.currentEthereumNodeIdKey, node.key as int);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -144,6 +144,7 @@ class ExceptionHandler {
|
||||||
"Connection closed before full header was received",
|
"Connection closed before full header was received",
|
||||||
"Connection terminated during handshake",
|
"Connection terminated during handshake",
|
||||||
"PERMISSION_NOT_GRANTED",
|
"PERMISSION_NOT_GRANTED",
|
||||||
|
"Failed host lookup: ",
|
||||||
];
|
];
|
||||||
|
|
||||||
static Future<void> _addDeviceInfo(File file) async {
|
static Future<void> _addDeviceInfo(File file) async {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'package:cake_wallet/entities/contact_base.dart';
|
||||||
import 'package:cake_wallet/entities/wallet_contact.dart';
|
import 'package:cake_wallet/entities/wallet_contact.dart';
|
||||||
import 'package:cake_wallet/store/settings_store.dart';
|
import 'package:cake_wallet/store/settings_store.dart';
|
||||||
import 'package:cw_core/wallet_info.dart';
|
import 'package:cw_core/wallet_info.dart';
|
||||||
|
@ -57,11 +58,15 @@ abstract class ContactListViewModelBase with Store {
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
List<ContactRecord> get contactsToShow => contacts
|
List<ContactRecord> get contactsToShow => contacts
|
||||||
.where((element) => _currency == null || element.type == _currency)
|
.where((element) => _isValidForCurrency(element))
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
List<WalletContact> get walletContactsToShow => walletContacts
|
List<WalletContact> get walletContactsToShow => walletContacts
|
||||||
.where((element) => _currency == null || element.type == _currency)
|
.where((element) => _isValidForCurrency(element))
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
|
bool _isValidForCurrency(ContactBase element) {
|
||||||
|
return _currency == null || element.type == _currency || element.type.title == _currency!.tag;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:cake_wallet/entities/fiat_api_mode.dart';
|
import 'package:cake_wallet/entities/fiat_api_mode.dart';
|
||||||
|
import 'package:cake_wallet/entities/sort_balance_types.dart';
|
||||||
import 'package:cw_core/transaction_history.dart';
|
import 'package:cw_core/transaction_history.dart';
|
||||||
import 'package:cw_core/wallet_base.dart';
|
import 'package:cw_core/wallet_base.dart';
|
||||||
import 'package:cw_core/balance.dart';
|
import 'package:cw_core/balance.dart';
|
||||||
|
@ -79,6 +80,15 @@ abstract class BalanceViewModelBase with Store {
|
||||||
@computed
|
@computed
|
||||||
bool get isFiatDisabled => settingsStore.fiatApiMode == FiatApiMode.disabled;
|
bool get isFiatDisabled => settingsStore.fiatApiMode == FiatApiMode.disabled;
|
||||||
|
|
||||||
|
@computed
|
||||||
|
bool get isHomeScreenSettingsEnabled => wallet.type == WalletType.ethereum;
|
||||||
|
|
||||||
|
@computed
|
||||||
|
SortBalanceBy get sortBalanceBy => settingsStore.sortBalanceBy;
|
||||||
|
|
||||||
|
@computed
|
||||||
|
bool get pinNativeToken => settingsStore.pinNativeTokenAtTop;
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
String get asset {
|
String get asset {
|
||||||
final typeFormatted = walletTypeToString(appStore.wallet!.type);
|
final typeFormatted = walletTypeToString(appStore.wallet!.type);
|
||||||
|
@ -109,6 +119,7 @@ abstract class BalanceViewModelBase with Store {
|
||||||
switch(wallet.type) {
|
switch(wallet.type) {
|
||||||
case WalletType.monero:
|
case WalletType.monero:
|
||||||
case WalletType.haven:
|
case WalletType.haven:
|
||||||
|
case WalletType.ethereum:
|
||||||
return S.current.xmr_available_balance;
|
return S.current.xmr_available_balance;
|
||||||
default:
|
default:
|
||||||
return S.current.confirmed;
|
return S.current.confirmed;
|
||||||
|
@ -120,6 +131,7 @@ abstract class BalanceViewModelBase with Store {
|
||||||
switch(wallet.type) {
|
switch(wallet.type) {
|
||||||
case WalletType.monero:
|
case WalletType.monero:
|
||||||
case WalletType.haven:
|
case WalletType.haven:
|
||||||
|
case WalletType.ethereum:
|
||||||
return S.current.xmr_full_balance;
|
return S.current.xmr_full_balance;
|
||||||
default:
|
default:
|
||||||
return S.current.unconfirmed;
|
return S.current.unconfirmed;
|
||||||
|
@ -262,32 +274,58 @@ abstract class BalanceViewModelBase with Store {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@computed
|
||||||
|
bool get hasAdditionalBalance => wallet.type != WalletType.ethereum;
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
List<BalanceRecord> get formattedBalances {
|
List<BalanceRecord> get formattedBalances {
|
||||||
final balance = balances.values.toList();
|
final balance = balances.values.toList();
|
||||||
|
|
||||||
balance.sort((BalanceRecord a, BalanceRecord b) {
|
balance.sort((BalanceRecord a, BalanceRecord b) {
|
||||||
if (b.asset == CryptoCurrency.xhv) {
|
if (wallet.currency == CryptoCurrency.xhv) {
|
||||||
return 1;
|
if (b.asset == CryptoCurrency.xhv) {
|
||||||
}
|
return 1;
|
||||||
|
|
||||||
if (b.asset == CryptoCurrency.xusd) {
|
|
||||||
if (a.asset == CryptoCurrency.xhv) {
|
|
||||||
return -1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 1;
|
if (b.asset == CryptoCurrency.xusd) {
|
||||||
|
if (a.asset == CryptoCurrency.xhv) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (b.asset == CryptoCurrency.xbtc) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (b.asset == CryptoCurrency.xeur) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (b.asset == CryptoCurrency.xbtc) {
|
if (pinNativeToken) {
|
||||||
return 1;
|
if (b.asset == wallet.currency) return 1;
|
||||||
|
if (a.asset == wallet.currency) return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (b.asset == CryptoCurrency.xeur) {
|
switch (sortBalanceBy) {
|
||||||
return 1;
|
case SortBalanceBy.FiatBalance:
|
||||||
}
|
final aFiatBalance = _getFiatBalance(
|
||||||
|
price: fiatConvertationStore.prices[a.asset] ?? 0, cryptoAmount: a.availableBalance);
|
||||||
|
final bFiatBalance = _getFiatBalance(
|
||||||
|
price: fiatConvertationStore.prices[b.asset] ?? 0, cryptoAmount: b.availableBalance);
|
||||||
|
|
||||||
return 0;
|
return (double.tryParse(bFiatBalance) ?? 0)
|
||||||
|
.compareTo((double.tryParse(aFiatBalance)) ?? 0);
|
||||||
|
case SortBalanceBy.GrossBalance:
|
||||||
|
return (double.tryParse(b.availableBalance) ?? 0)
|
||||||
|
.compareTo(double.tryParse(a.availableBalance) ?? 0);
|
||||||
|
case SortBalanceBy.Alphabetical:
|
||||||
|
return a.asset.title.compareTo(b.asset.title);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return balance;
|
return balance;
|
||||||
|
@ -335,7 +373,7 @@ abstract class BalanceViewModelBase with Store {
|
||||||
}
|
}
|
||||||
|
|
||||||
String _getFiatBalance({required double price, String? cryptoAmount}) {
|
String _getFiatBalance({required double price, String? cryptoAmount}) {
|
||||||
if (cryptoAmount == null || cryptoAmount.isEmpty) {
|
if (cryptoAmount == null || cryptoAmount.isEmpty || double.tryParse(cryptoAmount) == null) {
|
||||||
return '0.00';
|
return '0.00';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
121
lib/view_model/dashboard/home_settings_view_model.dart
Normal file
121
lib/view_model/dashboard/home_settings_view_model.dart
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
import 'package:cake_wallet/core/fiat_conversion_service.dart';
|
||||||
|
import 'package:cake_wallet/entities/fiat_api_mode.dart';
|
||||||
|
import 'package:cake_wallet/entities/sort_balance_types.dart';
|
||||||
|
import 'package:cake_wallet/ethereum/ethereum.dart';
|
||||||
|
import 'package:cake_wallet/store/settings_store.dart';
|
||||||
|
import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart';
|
||||||
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
|
import 'package:cw_core/erc20_token.dart';
|
||||||
|
import 'package:mobx/mobx.dart';
|
||||||
|
|
||||||
|
part 'home_settings_view_model.g.dart';
|
||||||
|
|
||||||
|
class HomeSettingsViewModel = HomeSettingsViewModelBase with _$HomeSettingsViewModel;
|
||||||
|
|
||||||
|
abstract class HomeSettingsViewModelBase with Store {
|
||||||
|
HomeSettingsViewModelBase(this._settingsStore, this._balanceViewModel)
|
||||||
|
: tokens = ObservableSet<Erc20Token>() {
|
||||||
|
_updateTokensList();
|
||||||
|
}
|
||||||
|
|
||||||
|
final SettingsStore _settingsStore;
|
||||||
|
final BalanceViewModel _balanceViewModel;
|
||||||
|
|
||||||
|
final ObservableSet<Erc20Token> tokens;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
String searchText = '';
|
||||||
|
|
||||||
|
@computed
|
||||||
|
SortBalanceBy get sortBalanceBy => _settingsStore.sortBalanceBy;
|
||||||
|
|
||||||
|
@action
|
||||||
|
void setSortBalanceBy(SortBalanceBy value) {
|
||||||
|
_settingsStore.sortBalanceBy = value;
|
||||||
|
_updateTokensList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@computed
|
||||||
|
bool get pinNativeToken => _settingsStore.pinNativeTokenAtTop;
|
||||||
|
|
||||||
|
@action
|
||||||
|
void setPinNativeToken(bool value) => _settingsStore.pinNativeTokenAtTop = value;
|
||||||
|
|
||||||
|
Future<void> addErc20Token(Erc20Token token) async {
|
||||||
|
await ethereum!.addErc20Token(_balanceViewModel.wallet, token);
|
||||||
|
_updateTokensList();
|
||||||
|
_updateFiatPrices(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> deleteErc20Token(Erc20Token token) async {
|
||||||
|
await ethereum!.deleteErc20Token(_balanceViewModel.wallet, token);
|
||||||
|
_updateTokensList();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Erc20Token?> getErc20Token(String contractAddress) async =>
|
||||||
|
await ethereum!.getErc20Token(_balanceViewModel.wallet, contractAddress);
|
||||||
|
|
||||||
|
CryptoCurrency get nativeToken => _balanceViewModel.wallet.currency;
|
||||||
|
|
||||||
|
void _updateFiatPrices(Erc20Token token) async {
|
||||||
|
try {
|
||||||
|
_balanceViewModel.fiatConvertationStore.prices[token] =
|
||||||
|
await FiatConversionService.fetchPrice(
|
||||||
|
crypto: token,
|
||||||
|
fiat: _settingsStore.fiatCurrency,
|
||||||
|
torOnly: _settingsStore.fiatApiMode == FiatApiMode.torOnly);
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
void changeTokenAvailability(Erc20Token token, bool value) async {
|
||||||
|
token.enabled = value;
|
||||||
|
ethereum!.addErc20Token(_balanceViewModel.wallet, token);
|
||||||
|
_refreshTokensList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
void _updateTokensList() {
|
||||||
|
int _sortFunc(Erc20Token e1, Erc20Token e2) {
|
||||||
|
int index1 = _balanceViewModel.formattedBalances.indexWhere((element) => element.asset == e1);
|
||||||
|
int index2 = _balanceViewModel.formattedBalances.indexWhere((element) => element.asset == e2);
|
||||||
|
|
||||||
|
if (e1.enabled && !e2.enabled) {
|
||||||
|
return -1;
|
||||||
|
} else if (e2.enabled && !e1.enabled) {
|
||||||
|
return 1;
|
||||||
|
} else if (!e1.enabled && !e2.enabled) { // if both are disabled then sort alphabetically
|
||||||
|
return e1.name.compareTo(e2.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return index1.compareTo(index2);
|
||||||
|
}
|
||||||
|
|
||||||
|
tokens.clear();
|
||||||
|
|
||||||
|
tokens.addAll(ethereum!
|
||||||
|
.getERC20Currencies(_balanceViewModel.wallet)
|
||||||
|
.where((element) => _matchesSearchText(element))
|
||||||
|
.toList()
|
||||||
|
..sort(_sortFunc));
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
void _refreshTokensList() {
|
||||||
|
final _tokens = Set.of(tokens);
|
||||||
|
tokens.clear();
|
||||||
|
tokens.addAll(_tokens);
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
void changeSearchText(String text) {
|
||||||
|
searchText = text;
|
||||||
|
_updateTokensList();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _matchesSearchText(Erc20Token asset) {
|
||||||
|
return searchText.isEmpty ||
|
||||||
|
asset.fullName!.toLowerCase().contains(searchText.toLowerCase()) ||
|
||||||
|
asset.title.toLowerCase().contains(searchText.toLowerCase()) ||
|
||||||
|
asset.contractAddress == searchText;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
import 'package:cake_wallet/entities/balance_display_mode.dart';
|
import 'package:cake_wallet/entities/balance_display_mode.dart';
|
||||||
import 'package:cake_wallet/entities/fiat_currency.dart';
|
import 'package:cake_wallet/entities/fiat_currency.dart';
|
||||||
|
import 'package:cake_wallet/ethereum/ethereum.dart';
|
||||||
import 'package:cake_wallet/generated/i18n.dart';
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
import 'package:cw_core/transaction_direction.dart';
|
import 'package:cw_core/transaction_direction.dart';
|
||||||
import 'package:cw_core/transaction_info.dart';
|
import 'package:cw_core/transaction_info.dart';
|
||||||
|
@ -84,6 +85,13 @@ class TransactionListItem extends ActionListItem with Keyable {
|
||||||
cryptoAmount: haven!.formatterMoneroAmountToDouble(amount: transaction.amount),
|
cryptoAmount: haven!.formatterMoneroAmountToDouble(amount: transaction.amount),
|
||||||
price: price);
|
price: price);
|
||||||
break;
|
break;
|
||||||
|
case WalletType.ethereum:
|
||||||
|
final asset = ethereum!.assetOfTransaction(balanceViewModel.wallet, transaction);
|
||||||
|
final price = balanceViewModel.fiatConvertationStore.prices[asset];
|
||||||
|
amount = calculateFiatAmountRaw(
|
||||||
|
cryptoAmount: ethereum!.formatterEthereumAmountToDouble(transaction: transaction),
|
||||||
|
price: price);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -699,6 +699,10 @@ abstract class ExchangeViewModelBase with Store {
|
||||||
depositCurrency = CryptoCurrency.xhv;
|
depositCurrency = CryptoCurrency.xhv;
|
||||||
receiveCurrency = CryptoCurrency.btc;
|
receiveCurrency = CryptoCurrency.btc;
|
||||||
break;
|
break;
|
||||||
|
case WalletType.ethereum:
|
||||||
|
depositCurrency = CryptoCurrency.eth;
|
||||||
|
receiveCurrency = CryptoCurrency.xmr;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,6 +63,9 @@ abstract class NodeListViewModelBase with Store {
|
||||||
case WalletType.haven:
|
case WalletType.haven:
|
||||||
node = getHavenDefaultNode(nodes: _nodeSource)!;
|
node = getHavenDefaultNode(nodes: _nodeSource)!;
|
||||||
break;
|
break;
|
||||||
|
case WalletType.ethereum:
|
||||||
|
node = getEthereumDefaultNode(nodes: _nodeSource)!;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw Exception('Unexpected wallet type: ${_appStore.wallet!.type}');
|
throw Exception('Unexpected wallet type: ${_appStore.wallet!.type}');
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ import 'package:cake_wallet/di.dart';
|
||||||
import 'package:cake_wallet/entities/calculate_fiat_amount_raw.dart';
|
import 'package:cake_wallet/entities/calculate_fiat_amount_raw.dart';
|
||||||
import 'package:cake_wallet/entities/parse_address_from_domain.dart';
|
import 'package:cake_wallet/entities/parse_address_from_domain.dart';
|
||||||
import 'package:cake_wallet/entities/parsed_address.dart';
|
import 'package:cake_wallet/entities/parsed_address.dart';
|
||||||
|
import 'package:cake_wallet/ethereum/ethereum.dart';
|
||||||
import 'package:cake_wallet/haven/haven.dart';
|
import 'package:cake_wallet/haven/haven.dart';
|
||||||
import 'package:cake_wallet/src/screens/send/widgets/extract_address_from_parsed.dart';
|
import 'package:cake_wallet/src/screens/send/widgets/extract_address_from_parsed.dart';
|
||||||
import 'package:cw_core/crypto_currency.dart';
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
|
@ -90,6 +91,9 @@ abstract class OutputBase with Store {
|
||||||
case WalletType.haven:
|
case WalletType.haven:
|
||||||
_amount = haven!.formatterMoneroParseAmount(amount: _cryptoAmount);
|
_amount = haven!.formatterMoneroParseAmount(amount: _cryptoAmount);
|
||||||
break;
|
break;
|
||||||
|
case WalletType.ethereum:
|
||||||
|
_amount = ethereum!.formatterEthereumParseAmount(_cryptoAmount);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -123,6 +127,10 @@ abstract class OutputBase with Store {
|
||||||
if (_wallet.type == WalletType.haven) {
|
if (_wallet.type == WalletType.haven) {
|
||||||
return haven!.formatterMoneroAmountToDouble(amount: fee);
|
return haven!.formatterMoneroAmountToDouble(amount: fee);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_wallet.type == WalletType.ethereum) {
|
||||||
|
return ethereum!.formatterEthereumAmountToDouble(amount: BigInt.from(fee));
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print(e.toString());
|
print(e.toString());
|
||||||
}
|
}
|
||||||
|
@ -133,8 +141,9 @@ abstract class OutputBase with Store {
|
||||||
@computed
|
@computed
|
||||||
String get estimatedFeeFiatAmount {
|
String get estimatedFeeFiatAmount {
|
||||||
try {
|
try {
|
||||||
|
final currency = _wallet.type == WalletType.ethereum ? _wallet.currency : cryptoCurrencyHandler();
|
||||||
final fiat = calculateFiatAmountRaw(
|
final fiat = calculateFiatAmountRaw(
|
||||||
price: _fiatConversationStore.prices[cryptoCurrencyHandler()]!,
|
price: _fiatConversationStore.prices[currency]!,
|
||||||
cryptoAmount: estimatedFee);
|
cryptoAmount: estimatedFee);
|
||||||
return fiat;
|
return fiat;
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
|
@ -228,6 +237,9 @@ abstract class OutputBase with Store {
|
||||||
case WalletType.haven:
|
case WalletType.haven:
|
||||||
maximumFractionDigits = 12;
|
maximumFractionDigits = 12;
|
||||||
break;
|
break;
|
||||||
|
case WalletType.ethereum:
|
||||||
|
maximumFractionDigits = 12;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,8 +13,7 @@ import 'package:cake_wallet/store/settings_store.dart';
|
||||||
|
|
||||||
part 'send_template_view_model.g.dart';
|
part 'send_template_view_model.g.dart';
|
||||||
|
|
||||||
class SendTemplateViewModel = SendTemplateViewModelBase
|
class SendTemplateViewModel = SendTemplateViewModelBase with _$SendTemplateViewModel;
|
||||||
with _$SendTemplateViewModel;
|
|
||||||
|
|
||||||
abstract class SendTemplateViewModelBase with Store {
|
abstract class SendTemplateViewModelBase with Store {
|
||||||
final WalletBase _wallet;
|
final WalletBase _wallet;
|
||||||
|
@ -22,8 +21,8 @@ abstract class SendTemplateViewModelBase with Store {
|
||||||
final SendTemplateStore _sendTemplateStore;
|
final SendTemplateStore _sendTemplateStore;
|
||||||
final FiatConversionStore _fiatConversationStore;
|
final FiatConversionStore _fiatConversationStore;
|
||||||
|
|
||||||
SendTemplateViewModelBase(this._wallet, this._settingsStore,
|
SendTemplateViewModelBase(
|
||||||
this._sendTemplateStore, this._fiatConversationStore)
|
this._wallet, this._settingsStore, this._sendTemplateStore, this._fiatConversationStore)
|
||||||
: recipients = ObservableList<TemplateViewModel>() {
|
: recipients = ObservableList<TemplateViewModel>() {
|
||||||
addRecipient();
|
addRecipient();
|
||||||
}
|
}
|
||||||
|
@ -33,7 +32,6 @@ abstract class SendTemplateViewModelBase with Store {
|
||||||
@action
|
@action
|
||||||
void addRecipient() {
|
void addRecipient() {
|
||||||
recipients.add(TemplateViewModel(
|
recipients.add(TemplateViewModel(
|
||||||
cryptoCurrency: cryptoCurrency,
|
|
||||||
wallet: _wallet,
|
wallet: _wallet,
|
||||||
settingsStore: _settingsStore,
|
settingsStore: _settingsStore,
|
||||||
fiatConversationStore: _fiatConversationStore));
|
fiatConversationStore: _fiatConversationStore));
|
||||||
|
@ -47,11 +45,13 @@ abstract class SendTemplateViewModelBase with Store {
|
||||||
AmountValidator get amountValidator =>
|
AmountValidator get amountValidator =>
|
||||||
AmountValidator(currency: walletTypeToCryptoCurrency(_wallet.type));
|
AmountValidator(currency: walletTypeToCryptoCurrency(_wallet.type));
|
||||||
|
|
||||||
AddressValidator get addressValidator =>
|
AddressValidator get addressValidator => AddressValidator(type: _wallet.currency);
|
||||||
AddressValidator(type: _wallet.currency);
|
|
||||||
|
|
||||||
TemplateValidator get templateValidator => TemplateValidator();
|
TemplateValidator get templateValidator => TemplateValidator();
|
||||||
|
|
||||||
|
bool get hasMultiRecipient =>
|
||||||
|
_wallet.type != WalletType.haven && _wallet.type != WalletType.ethereum;
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
CryptoCurrency get cryptoCurrency => _wallet.currency;
|
CryptoCurrency get cryptoCurrency => _wallet.currency;
|
||||||
|
|
||||||
|
@ -68,6 +68,7 @@ abstract class SendTemplateViewModelBase with Store {
|
||||||
void addTemplate(
|
void addTemplate(
|
||||||
{required String name,
|
{required String name,
|
||||||
required bool isCurrencySelected,
|
required bool isCurrencySelected,
|
||||||
|
required String cryptoCurrency,
|
||||||
required String address,
|
required String address,
|
||||||
required String amount,
|
required String amount,
|
||||||
required String amountFiat,
|
required String amountFiat,
|
||||||
|
@ -76,7 +77,7 @@ abstract class SendTemplateViewModelBase with Store {
|
||||||
name: name,
|
name: name,
|
||||||
isCurrencySelected: isCurrencySelected,
|
isCurrencySelected: isCurrencySelected,
|
||||||
address: address,
|
address: address,
|
||||||
cryptoCurrency: cryptoCurrency.title,
|
cryptoCurrency: cryptoCurrency,
|
||||||
fiatCurrency: fiatCurrency,
|
fiatCurrency: fiatCurrency,
|
||||||
amount: amount,
|
amount: amount,
|
||||||
amountFiat: amountFiat,
|
amountFiat: amountFiat,
|
||||||
|
@ -89,4 +90,7 @@ abstract class SendTemplateViewModelBase with Store {
|
||||||
_sendTemplateStore.remove(template: template);
|
_sendTemplateStore.remove(template: template);
|
||||||
updateTemplate();
|
updateTemplate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@computed
|
||||||
|
List<CryptoCurrency> get walletCurrencies => _wallet.balance.keys.toList();
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ import 'package:cake_wallet/entities/priority_for_wallet_type.dart';
|
||||||
import 'package:cake_wallet/entities/transaction_description.dart';
|
import 'package:cake_wallet/entities/transaction_description.dart';
|
||||||
import 'package:cake_wallet/entities/wallet_contact.dart';
|
import 'package:cake_wallet/entities/wallet_contact.dart';
|
||||||
import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart';
|
import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart';
|
||||||
|
import 'package:cake_wallet/ethereum/ethereum.dart';
|
||||||
import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart';
|
import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart';
|
||||||
import 'package:cw_core/transaction_priority.dart';
|
import 'package:cw_core/transaction_priority.dart';
|
||||||
import 'package:cake_wallet/view_model/send/output.dart';
|
import 'package:cake_wallet/view_model/send/output.dart';
|
||||||
|
@ -45,6 +46,7 @@ abstract class SendViewModelBase with Store {
|
||||||
: state = InitialExecutionState(),
|
: state = InitialExecutionState(),
|
||||||
currencies = _wallet.balance.keys.toList(),
|
currencies = _wallet.balance.keys.toList(),
|
||||||
selectedCryptoCurrency = _wallet.currency,
|
selectedCryptoCurrency = _wallet.currency,
|
||||||
|
hasMultipleTokens = _wallet.type == WalletType.ethereum,
|
||||||
outputs = ObservableList<Output>(),
|
outputs = ObservableList<Output>(),
|
||||||
fiatFromSettings = _settingsStore.fiatCurrency {
|
fiatFromSettings = _settingsStore.fiatCurrency {
|
||||||
final priority = _settingsStore.priority[_wallet.type];
|
final priority = _settingsStore.priority[_wallet.type];
|
||||||
|
@ -105,8 +107,11 @@ abstract class SendViewModelBase with Store {
|
||||||
String get pendingTransactionFeeFiatAmount {
|
String get pendingTransactionFeeFiatAmount {
|
||||||
try {
|
try {
|
||||||
if (pendingTransaction != null) {
|
if (pendingTransaction != null) {
|
||||||
|
final currency = walletType == WalletType.ethereum
|
||||||
|
? _wallet.currency
|
||||||
|
: selectedCryptoCurrency;
|
||||||
final fiat = calculateFiatAmount(
|
final fiat = calculateFiatAmount(
|
||||||
price: _fiatConversationStore.prices[selectedCryptoCurrency]!,
|
price: _fiatConversationStore.prices[currency]!,
|
||||||
cryptoAmount: pendingTransaction!.feeFormatted);
|
cryptoAmount: pendingTransaction!.feeFormatted);
|
||||||
return fiat;
|
return fiat;
|
||||||
} else {
|
} else {
|
||||||
|
@ -131,14 +136,14 @@ abstract class SendViewModelBase with Store {
|
||||||
|
|
||||||
CryptoCurrency get currency => _wallet.currency;
|
CryptoCurrency get currency => _wallet.currency;
|
||||||
|
|
||||||
Validator get amountValidator =>
|
Validator<String> get amountValidator =>
|
||||||
AmountValidator(currency: walletTypeToCryptoCurrency(_wallet.type));
|
AmountValidator(currency: walletTypeToCryptoCurrency(_wallet.type));
|
||||||
|
|
||||||
Validator get allAmountValidator => AllAmountValidator();
|
Validator<String> get allAmountValidator => AllAmountValidator();
|
||||||
|
|
||||||
Validator get addressValidator => AddressValidator(type: selectedCryptoCurrency);
|
Validator<String> get addressValidator => AddressValidator(type: selectedCryptoCurrency);
|
||||||
|
|
||||||
Validator get textValidator => TextValidator();
|
Validator<String> get textValidator => TextValidator();
|
||||||
|
|
||||||
final FiatCurrency fiatFromSettings;
|
final FiatCurrency fiatFromSettings;
|
||||||
|
|
||||||
|
@ -146,7 +151,7 @@ abstract class SendViewModelBase with Store {
|
||||||
PendingTransaction? pendingTransaction;
|
PendingTransaction? pendingTransaction;
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
String get balance => balanceViewModel.availableBalance;
|
String get balance => _wallet.balance[selectedCryptoCurrency]!.formattedAvailableBalance;
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
bool get isFiatDisabled => balanceViewModel.isFiatDisabled;
|
bool get isFiatDisabled => balanceViewModel.isFiatDisabled;
|
||||||
|
@ -176,10 +181,9 @@ abstract class SendViewModelBase with Store {
|
||||||
|
|
||||||
List<CryptoCurrency> currencies;
|
List<CryptoCurrency> currencies;
|
||||||
|
|
||||||
bool get hasMultiRecipient => _wallet.type != WalletType.haven;
|
bool get hasYat => outputs.any((out) =>
|
||||||
|
out.isParsedAddress &&
|
||||||
bool get hasYat => outputs
|
out.parsedAddress.parseFrom == ParseFrom.yatRecord);
|
||||||
.any((out) => out.isParsedAddress && out.parsedAddress.parseFrom == ParseFrom.yatRecord);
|
|
||||||
|
|
||||||
WalletType get walletType => _wallet.type;
|
WalletType get walletType => _wallet.type;
|
||||||
|
|
||||||
|
@ -198,6 +202,7 @@ abstract class SendViewModelBase with Store {
|
||||||
final ContactListViewModel contactListViewModel;
|
final ContactListViewModel contactListViewModel;
|
||||||
final FiatConversionStore _fiatConversationStore;
|
final FiatConversionStore _fiatConversationStore;
|
||||||
final Box<TransactionDescription> transactionDescriptionBox;
|
final Box<TransactionDescription> transactionDescriptionBox;
|
||||||
|
final bool hasMultipleTokens;
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
List<ContactRecord> get contactsToShow => contactListViewModel.contacts
|
List<ContactRecord> get contactsToShow => contactListViewModel.contacts
|
||||||
|
@ -351,6 +356,15 @@ abstract class SendViewModelBase with Store {
|
||||||
|
|
||||||
return haven!.createHavenTransactionCreationCredentials(
|
return haven!.createHavenTransactionCreationCredentials(
|
||||||
outputs: outputs, priority: priority, assetType: selectedCryptoCurrency.title);
|
outputs: outputs, priority: priority, assetType: selectedCryptoCurrency.title);
|
||||||
|
case WalletType.ethereum:
|
||||||
|
final priority = _settingsStore.priority[_wallet.type];
|
||||||
|
|
||||||
|
if (priority == null) {
|
||||||
|
throw Exception('Priority is null for wallet type: ${_wallet.type}');
|
||||||
|
}
|
||||||
|
|
||||||
|
return ethereum!.createEthereumTransactionCredentials(
|
||||||
|
outputs, priority: priority, currency: selectedCryptoCurrency);
|
||||||
default:
|
default:
|
||||||
throw Exception('Unexpected wallet type: ${_wallet.type}');
|
throw Exception('Unexpected wallet type: ${_wallet.type}');
|
||||||
}
|
}
|
||||||
|
@ -369,11 +383,22 @@ abstract class SendViewModelBase with Store {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _isEqualCurrency(String currency) =>
|
bool _isEqualCurrency(String currency) =>
|
||||||
currency.toLowerCase() == _wallet.currency.title.toLowerCase();
|
_wallet.balance.keys.any((e) => currency.toLowerCase() == e.title.toLowerCase());
|
||||||
|
|
||||||
@action
|
@action
|
||||||
void onClose() => _settingsStore.fiatCurrency = fiatFromSettings;
|
void onClose() => _settingsStore.fiatCurrency = fiatFromSettings;
|
||||||
|
|
||||||
@action
|
@action
|
||||||
void setFiatCurrency(FiatCurrency fiat) => _settingsStore.fiatCurrency = fiat;
|
void setFiatCurrency(FiatCurrency fiat) =>
|
||||||
|
_settingsStore.fiatCurrency = fiat;
|
||||||
|
|
||||||
|
@action
|
||||||
|
void setSelectedCryptoCurrency(String cryptoCurrency) {
|
||||||
|
try {
|
||||||
|
selectedCryptoCurrency = _wallet.balance.keys
|
||||||
|
.firstWhere((e) => cryptoCurrency.toLowerCase() == e.title.toLowerCase());
|
||||||
|
} catch (e) {
|
||||||
|
selectedCryptoCurrency = _wallet.currency;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,23 +11,20 @@ part 'template_view_model.g.dart';
|
||||||
class TemplateViewModel = TemplateViewModelBase with _$TemplateViewModel;
|
class TemplateViewModel = TemplateViewModelBase with _$TemplateViewModel;
|
||||||
|
|
||||||
abstract class TemplateViewModelBase with Store {
|
abstract class TemplateViewModelBase with Store {
|
||||||
final CryptoCurrency cryptoCurrency;
|
|
||||||
final WalletBase _wallet;
|
final WalletBase _wallet;
|
||||||
final SettingsStore _settingsStore;
|
final SettingsStore _settingsStore;
|
||||||
final FiatConversionStore _fiatConversationStore;
|
final FiatConversionStore _fiatConversationStore;
|
||||||
|
|
||||||
TemplateViewModelBase(
|
TemplateViewModelBase({
|
||||||
{required this.cryptoCurrency,
|
required WalletBase wallet,
|
||||||
required WalletBase wallet,
|
required SettingsStore settingsStore,
|
||||||
required SettingsStore settingsStore,
|
required FiatConversionStore fiatConversationStore,
|
||||||
required FiatConversionStore fiatConversationStore})
|
}) : _wallet = wallet,
|
||||||
: _wallet = wallet,
|
|
||||||
_settingsStore = settingsStore,
|
_settingsStore = settingsStore,
|
||||||
_fiatConversationStore = fiatConversationStore,
|
_fiatConversationStore = fiatConversationStore,
|
||||||
output = Output(wallet, settingsStore, fiatConversationStore,
|
_currency = wallet.currency,
|
||||||
() => wallet.currency) {
|
output = Output(wallet, settingsStore, fiatConversationStore, () => wallet.currency) {
|
||||||
output = Output(
|
output = Output(_wallet, _settingsStore, _fiatConversationStore, () => _currency);
|
||||||
_wallet, _settingsStore, _fiatConversationStore, () => cryptoCurrency);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
|
@ -39,6 +36,9 @@ abstract class TemplateViewModelBase with Store {
|
||||||
@observable
|
@observable
|
||||||
String address = '';
|
String address = '';
|
||||||
|
|
||||||
|
@observable
|
||||||
|
CryptoCurrency _currency;
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
bool isCurrencySelected = true;
|
bool isCurrencySelected = true;
|
||||||
|
|
||||||
|
@ -66,8 +66,7 @@ abstract class TemplateViewModelBase with Store {
|
||||||
output.reset();
|
output.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
Template toTemplate(
|
Template toTemplate({required String cryptoCurrency, required String fiatCurrency}) {
|
||||||
{required String cryptoCurrency, required String fiatCurrency}) {
|
|
||||||
return Template(
|
return Template(
|
||||||
isCurrencySelectedRaw: isCurrencySelected,
|
isCurrencySelectedRaw: isCurrencySelected,
|
||||||
nameRaw: name,
|
nameRaw: name,
|
||||||
|
@ -77,4 +76,13 @@ abstract class TemplateViewModelBase with Store {
|
||||||
amountRaw: output.cryptoAmount,
|
amountRaw: output.cryptoAmount,
|
||||||
amountFiatRaw: output.fiatAmount);
|
amountFiatRaw: output.fiatAmount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
void changeSelectedCurrency(CryptoCurrency currency) {
|
||||||
|
isCurrencySelected = true;
|
||||||
|
_currency = currency;
|
||||||
|
}
|
||||||
|
|
||||||
|
@computed
|
||||||
|
CryptoCurrency get selectedCurrency => _currency;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
import 'package:cake_wallet/entities/exchange_api_mode.dart';
|
import 'package:cake_wallet/entities/exchange_api_mode.dart';
|
||||||
|
import 'package:cake_wallet/ethereum/ethereum.dart';
|
||||||
import 'package:cake_wallet/store/settings_store.dart';
|
import 'package:cake_wallet/store/settings_store.dart';
|
||||||
|
import 'package:cw_core/wallet_base.dart';
|
||||||
|
import 'package:cw_core/wallet_type.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
import 'package:cake_wallet/entities/fiat_api_mode.dart';
|
import 'package:cake_wallet/entities/fiat_api_mode.dart';
|
||||||
|
|
||||||
|
@ -8,9 +11,10 @@ part 'privacy_settings_view_model.g.dart';
|
||||||
class PrivacySettingsViewModel = PrivacySettingsViewModelBase with _$PrivacySettingsViewModel;
|
class PrivacySettingsViewModel = PrivacySettingsViewModelBase with _$PrivacySettingsViewModel;
|
||||||
|
|
||||||
abstract class PrivacySettingsViewModelBase with Store {
|
abstract class PrivacySettingsViewModelBase with Store {
|
||||||
PrivacySettingsViewModelBase(this._settingsStore);
|
PrivacySettingsViewModelBase(this._settingsStore, this._wallet);
|
||||||
|
|
||||||
final SettingsStore _settingsStore;
|
final SettingsStore _settingsStore;
|
||||||
|
final WalletBase _wallet;
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
ExchangeApiMode get exchangeStatus => _settingsStore.exchangeStatus;
|
ExchangeApiMode get exchangeStatus => _settingsStore.exchangeStatus;
|
||||||
|
@ -30,8 +34,14 @@ abstract class PrivacySettingsViewModelBase with Store {
|
||||||
@computed
|
@computed
|
||||||
bool get disableSell => _settingsStore.disableSell;
|
bool get disableSell => _settingsStore.disableSell;
|
||||||
|
|
||||||
|
@computed
|
||||||
|
bool get useEtherscan => _settingsStore.useEtherscan;
|
||||||
|
|
||||||
|
bool get canUseEtherscan => _wallet.type == WalletType.ethereum;
|
||||||
|
|
||||||
@action
|
@action
|
||||||
void setShouldSaveRecipientAddress(bool value) => _settingsStore.shouldSaveRecipientAddress = value;
|
void setShouldSaveRecipientAddress(bool value) =>
|
||||||
|
_settingsStore.shouldSaveRecipientAddress = value;
|
||||||
|
|
||||||
@action
|
@action
|
||||||
void setExchangeApiMode(ExchangeApiMode value) => _settingsStore.exchangeStatus = value;
|
void setExchangeApiMode(ExchangeApiMode value) => _settingsStore.exchangeStatus = value;
|
||||||
|
@ -48,4 +58,9 @@ abstract class PrivacySettingsViewModelBase with Store {
|
||||||
@action
|
@action
|
||||||
void setDisableSell(bool value) => _settingsStore.disableSell = value;
|
void setDisableSell(bool value) => _settingsStore.disableSell = value;
|
||||||
|
|
||||||
|
@action
|
||||||
|
void setUseEtherscan(bool value) {
|
||||||
|
_settingsStore.useEtherscan = value;
|
||||||
|
ethereum!.updateEtherscanUsageState(_wallet, value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import 'package:cw_core/transaction_direction.dart';
|
||||||
import 'package:cake_wallet/utils/date_formatter.dart';
|
import 'package:cake_wallet/utils/date_formatter.dart';
|
||||||
import 'package:cake_wallet/entities/transaction_description.dart';
|
import 'package:cake_wallet/entities/transaction_description.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
|
import 'package:intl/src/intl/date_format.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
import 'package:cake_wallet/store/settings_store.dart';
|
import 'package:cake_wallet/store/settings_store.dart';
|
||||||
import 'package:cake_wallet/generated/i18n.dart';
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
|
@ -27,105 +28,27 @@ abstract class TransactionDetailsViewModelBase with Store {
|
||||||
required this.wallet,
|
required this.wallet,
|
||||||
required this.settingsStore})
|
required this.settingsStore})
|
||||||
: items = [],
|
: items = [],
|
||||||
isRecipientAddressShown = false,
|
isRecipientAddressShown = false,
|
||||||
showRecipientAddress = settingsStore.shouldSaveRecipientAddress {
|
showRecipientAddress = settingsStore.shouldSaveRecipientAddress {
|
||||||
final dateFormat = DateFormatter.withCurrentLocal();
|
final dateFormat = DateFormatter.withCurrentLocal();
|
||||||
final tx = transactionInfo;
|
final tx = transactionInfo;
|
||||||
|
|
||||||
if (wallet.type == WalletType.monero) {
|
switch (wallet.type) {
|
||||||
final key = tx.additionalInfo['key'] as String?;
|
case WalletType.monero:
|
||||||
final accountIndex = tx.additionalInfo['accountIndex'] as int;
|
_addMoneroListItems(tx, dateFormat);
|
||||||
final addressIndex = tx.additionalInfo['addressIndex'] as int;
|
break;
|
||||||
final feeFormatted = tx.feeFormatted();
|
case WalletType.bitcoin:
|
||||||
final _items = [
|
case WalletType.litecoin:
|
||||||
StandartListItem(
|
_addElectrumListItems(tx, dateFormat);
|
||||||
title: S.current.transaction_details_transaction_id, value: tx.id),
|
break;
|
||||||
StandartListItem(
|
case WalletType.haven:
|
||||||
title: S.current.transaction_details_date,
|
_addHavenListItems(tx, dateFormat);
|
||||||
value: dateFormat.format(tx.date)),
|
break;
|
||||||
StandartListItem(
|
case WalletType.ethereum:
|
||||||
title: S.current.transaction_details_height, value: '${tx.height}'),
|
_addEthereumListItems(tx, dateFormat);
|
||||||
StandartListItem(
|
break;
|
||||||
title: S.current.transaction_details_amount,
|
default:
|
||||||
value: tx.amountFormatted()),
|
break;
|
||||||
if (feeFormatted != null)
|
|
||||||
StandartListItem(
|
|
||||||
title: S.current.transaction_details_fee, value: feeFormatted),
|
|
||||||
if (key?.isNotEmpty ?? false)
|
|
||||||
StandartListItem(title: S.current.transaction_key, value: key!)
|
|
||||||
];
|
|
||||||
|
|
||||||
if (tx.direction == TransactionDirection.incoming &&
|
|
||||||
accountIndex != null &&
|
|
||||||
addressIndex != null) {
|
|
||||||
try {
|
|
||||||
final address = monero!.getTransactionAddress(wallet, accountIndex, addressIndex);
|
|
||||||
final label = monero!.getSubaddressLabel(wallet, accountIndex, addressIndex);
|
|
||||||
|
|
||||||
if (address?.isNotEmpty ?? false) {
|
|
||||||
isRecipientAddressShown = true;
|
|
||||||
_items.add(
|
|
||||||
StandartListItem(
|
|
||||||
title: S.current.transaction_details_recipient_address,
|
|
||||||
value: address));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (label?.isNotEmpty ?? false) {
|
|
||||||
_items.add(
|
|
||||||
StandartListItem(
|
|
||||||
title: S.current.address_label,
|
|
||||||
value: label)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
print(e.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
items.addAll(_items);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (wallet.type == WalletType.bitcoin
|
|
||||||
|| wallet.type == WalletType.litecoin) {
|
|
||||||
final _items = [
|
|
||||||
StandartListItem(
|
|
||||||
title: S.current.transaction_details_transaction_id, value: tx.id),
|
|
||||||
StandartListItem(
|
|
||||||
title: S.current.transaction_details_date,
|
|
||||||
value: dateFormat.format(tx.date)),
|
|
||||||
StandartListItem(
|
|
||||||
title: S.current.confirmations,
|
|
||||||
value: tx.confirmations.toString()),
|
|
||||||
StandartListItem(
|
|
||||||
title: S.current.transaction_details_height, value: '${tx.height}'),
|
|
||||||
StandartListItem(
|
|
||||||
title: S.current.transaction_details_amount,
|
|
||||||
value: tx.amountFormatted()),
|
|
||||||
if (tx.feeFormatted()?.isNotEmpty ?? false)
|
|
||||||
StandartListItem(
|
|
||||||
title: S.current.transaction_details_fee,
|
|
||||||
value: tx.feeFormatted()!),
|
|
||||||
];
|
|
||||||
|
|
||||||
items.addAll(_items);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (wallet.type == WalletType.haven) {
|
|
||||||
items.addAll([
|
|
||||||
StandartListItem(
|
|
||||||
title: S.current.transaction_details_transaction_id, value: tx.id),
|
|
||||||
StandartListItem(
|
|
||||||
title: S.current.transaction_details_date,
|
|
||||||
value: dateFormat.format(tx.date)),
|
|
||||||
StandartListItem(
|
|
||||||
title: S.current.transaction_details_height, value: '${tx.height}'),
|
|
||||||
StandartListItem(
|
|
||||||
title: S.current.transaction_details_amount,
|
|
||||||
value: tx.amountFormatted()),
|
|
||||||
if (tx.feeFormatted()?.isNotEmpty ?? false)
|
|
||||||
StandartListItem(
|
|
||||||
title: S.current.transaction_details_fee, value: tx.feeFormatted()!),
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showRecipientAddress && !isRecipientAddressShown) {
|
if (showRecipientAddress && !isRecipientAddressShown) {
|
||||||
|
@ -136,10 +59,9 @@ abstract class TransactionDetailsViewModelBase with Store {
|
||||||
|
|
||||||
if (recipientAddress?.isNotEmpty ?? false) {
|
if (recipientAddress?.isNotEmpty ?? false) {
|
||||||
items.add(StandartListItem(
|
items.add(StandartListItem(
|
||||||
title: S.current.transaction_details_recipient_address,
|
title: S.current.transaction_details_recipient_address, value: recipientAddress!));
|
||||||
value: recipientAddress!));
|
|
||||||
}
|
}
|
||||||
} catch(_) {
|
} catch (_) {
|
||||||
// FIX-ME: Unhandled exception
|
// FIX-ME: Unhandled exception
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -192,6 +114,8 @@ abstract class TransactionDetailsViewModelBase with Store {
|
||||||
return 'https://blockchair.com/litecoin/transaction/${txId}';
|
return 'https://blockchair.com/litecoin/transaction/${txId}';
|
||||||
case WalletType.haven:
|
case WalletType.haven:
|
||||||
return 'https://explorer.havenprotocol.org/search?value=${txId}';
|
return 'https://explorer.havenprotocol.org/search?value=${txId}';
|
||||||
|
case WalletType.ethereum:
|
||||||
|
return 'https://etherscan.io/tx/${txId}';
|
||||||
default:
|
default:
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
@ -207,8 +131,92 @@ abstract class TransactionDetailsViewModelBase with Store {
|
||||||
return S.current.view_transaction_on + 'Blockchair.com';
|
return S.current.view_transaction_on + 'Blockchair.com';
|
||||||
case WalletType.haven:
|
case WalletType.haven:
|
||||||
return S.current.view_transaction_on + 'explorer.havenprotocol.org';
|
return S.current.view_transaction_on + 'explorer.havenprotocol.org';
|
||||||
|
case WalletType.ethereum:
|
||||||
|
return S.current.view_transaction_on + 'etherscan.io';
|
||||||
default:
|
default:
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _addMoneroListItems(TransactionInfo tx, DateFormat dateFormat) {
|
||||||
|
final key = tx.additionalInfo['key'] as String?;
|
||||||
|
final accountIndex = tx.additionalInfo['accountIndex'] as int;
|
||||||
|
final addressIndex = tx.additionalInfo['addressIndex'] as int;
|
||||||
|
final feeFormatted = tx.feeFormatted();
|
||||||
|
final _items = [
|
||||||
|
StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.id),
|
||||||
|
StandartListItem(
|
||||||
|
title: S.current.transaction_details_date, value: dateFormat.format(tx.date)),
|
||||||
|
StandartListItem(title: S.current.transaction_details_height, value: '${tx.height}'),
|
||||||
|
StandartListItem(title: S.current.transaction_details_amount, value: tx.amountFormatted()),
|
||||||
|
if (feeFormatted != null)
|
||||||
|
StandartListItem(title: S.current.transaction_details_fee, value: feeFormatted),
|
||||||
|
if (key?.isNotEmpty ?? false) StandartListItem(title: S.current.transaction_key, value: key!),
|
||||||
|
];
|
||||||
|
|
||||||
|
if (tx.direction == TransactionDirection.incoming) {
|
||||||
|
try {
|
||||||
|
final address = monero!.getTransactionAddress(wallet, accountIndex, addressIndex);
|
||||||
|
final label = monero!.getSubaddressLabel(wallet, accountIndex, addressIndex);
|
||||||
|
|
||||||
|
if (address.isNotEmpty) {
|
||||||
|
isRecipientAddressShown = true;
|
||||||
|
_items.add(StandartListItem(
|
||||||
|
title: S.current.transaction_details_recipient_address,
|
||||||
|
value: address,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (label.isNotEmpty) {
|
||||||
|
_items.add(StandartListItem(title: S.current.address_label, value: label));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print(e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
items.addAll(_items);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _addElectrumListItems(TransactionInfo tx, DateFormat dateFormat) {
|
||||||
|
final _items = [
|
||||||
|
StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.id),
|
||||||
|
StandartListItem(
|
||||||
|
title: S.current.transaction_details_date, value: dateFormat.format(tx.date)),
|
||||||
|
StandartListItem(title: S.current.confirmations, value: tx.confirmations.toString()),
|
||||||
|
StandartListItem(title: S.current.transaction_details_height, value: '${tx.height}'),
|
||||||
|
StandartListItem(title: S.current.transaction_details_amount, value: tx.amountFormatted()),
|
||||||
|
if (tx.feeFormatted()?.isNotEmpty ?? false)
|
||||||
|
StandartListItem(title: S.current.transaction_details_fee, value: tx.feeFormatted()!),
|
||||||
|
];
|
||||||
|
|
||||||
|
items.addAll(_items);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _addHavenListItems(TransactionInfo tx, DateFormat dateFormat) {
|
||||||
|
items.addAll([
|
||||||
|
StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.id),
|
||||||
|
StandartListItem(
|
||||||
|
title: S.current.transaction_details_date, value: dateFormat.format(tx.date)),
|
||||||
|
StandartListItem(title: S.current.transaction_details_height, value: '${tx.height}'),
|
||||||
|
StandartListItem(title: S.current.transaction_details_amount, value: tx.amountFormatted()),
|
||||||
|
if (tx.feeFormatted()?.isNotEmpty ?? false)
|
||||||
|
StandartListItem(title: S.current.transaction_details_fee, value: tx.feeFormatted()!),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _addEthereumListItems(TransactionInfo tx, DateFormat dateFormat) {
|
||||||
|
final _items = [
|
||||||
|
StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.id),
|
||||||
|
StandartListItem(
|
||||||
|
title: S.current.transaction_details_date, value: dateFormat.format(tx.date)),
|
||||||
|
StandartListItem(title: S.current.confirmations, value: tx.confirmations.toString()),
|
||||||
|
StandartListItem(title: S.current.transaction_details_height, value: '${tx.height}'),
|
||||||
|
StandartListItem(title: S.current.transaction_details_amount, value: tx.amountFormatted()),
|
||||||
|
if (tx.feeFormatted()?.isNotEmpty ?? false)
|
||||||
|
StandartListItem(title: S.current.transaction_details_fee, value: tx.feeFormatted()!),
|
||||||
|
];
|
||||||
|
|
||||||
|
items.addAll(_items);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:cake_wallet/ethereum/ethereum.dart';
|
||||||
import 'package:cake_wallet/entities/fiat_currency.dart';
|
import 'package:cake_wallet/entities/fiat_currency.dart';
|
||||||
import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
|
import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
|
||||||
import 'package:cake_wallet/store/yat/yat_store.dart';
|
import 'package:cake_wallet/store/yat/yat_store.dart';
|
||||||
|
@ -93,6 +94,22 @@ class LitecoinURI extends PaymentURI {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class EthereumURI extends PaymentURI {
|
||||||
|
EthereumURI({required String amount, required String address})
|
||||||
|
: super(amount: amount, address: address);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
var base = 'ethereum:' + address;
|
||||||
|
|
||||||
|
if (amount.isNotEmpty) {
|
||||||
|
base += '?amount=${amount.replaceAll(',', '.')}';
|
||||||
|
}
|
||||||
|
|
||||||
|
return base;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
abstract class WalletAddressListViewModelBase with Store {
|
abstract class WalletAddressListViewModelBase with Store {
|
||||||
WalletAddressListViewModelBase({
|
WalletAddressListViewModelBase({
|
||||||
required AppStore appStore,
|
required AppStore appStore,
|
||||||
|
@ -151,6 +168,10 @@ abstract class WalletAddressListViewModelBase with Store {
|
||||||
return LitecoinURI(amount: amount, address: address.address);
|
return LitecoinURI(amount: amount, address: address.address);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_wallet.type == WalletType.ethereum) {
|
||||||
|
return EthereumURI(amount: amount, address: address.address);
|
||||||
|
}
|
||||||
|
|
||||||
throw Exception('Unexpected type: ${type.toString()}');
|
throw Exception('Unexpected type: ${type.toString()}');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -202,6 +223,12 @@ abstract class WalletAddressListViewModelBase with Store {
|
||||||
addressList.addAll(bitcoinAddresses);
|
addressList.addAll(bitcoinAddresses);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (wallet.type == WalletType.ethereum) {
|
||||||
|
final primaryAddress = ethereum!.getAddress(wallet);
|
||||||
|
|
||||||
|
addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress));
|
||||||
|
}
|
||||||
|
|
||||||
return addressList;
|
return addressList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -226,6 +253,10 @@ abstract class WalletAddressListViewModelBase with Store {
|
||||||
@computed
|
@computed
|
||||||
bool get hasAddressList => _wallet.type == WalletType.monero || _wallet.type == WalletType.haven;
|
bool get hasAddressList => _wallet.type == WalletType.monero || _wallet.type == WalletType.haven;
|
||||||
|
|
||||||
|
@computed
|
||||||
|
bool get showElectrumAddressDisclaimer =>
|
||||||
|
_wallet.type == WalletType.bitcoin || _wallet.type == WalletType.litecoin;
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> _wallet;
|
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> _wallet;
|
||||||
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue