mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2024-11-17 01:37:40 +00:00
Merge branch 'dev' into redesign
# Conflicts: # lib/generated/i18n.dart # lib/src/screens/dashboard/wallet_menu.dart # lib/src/screens/exchange/widgets/base_exchange_widget.dart # lib/src/screens/send/widgets/base_send_widget.dart
This commit is contained in:
commit
cc4a1f1d80
80 changed files with 3511 additions and 1520 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -90,3 +90,5 @@ android/key.properties
|
||||||
**/lib/.secrets.g.dart
|
**/lib/.secrets.g.dart
|
||||||
|
|
||||||
vendor/
|
vendor/
|
||||||
|
|
||||||
|
android/app/.cxx/**
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
-
|
-
|
||||||
uri: electrum2.hodlister.co:50002
|
uri: electrumx.cakewallet.com:50002
|
1
ios/Flutter/.last_build_id
Normal file
1
ios/Flutter/.last_build_id
Normal file
|
@ -0,0 +1 @@
|
||||||
|
a2dce69f54a78f5b00e19850e4b2d402
|
|
@ -44,6 +44,8 @@ PODS:
|
||||||
- Flutter
|
- Flutter
|
||||||
- shared_preferences (0.0.1):
|
- shared_preferences (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
- shared_preferences_linux (0.0.1):
|
||||||
|
- Flutter
|
||||||
- shared_preferences_macos (0.0.1):
|
- shared_preferences_macos (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- shared_preferences_web (0.0.1):
|
- shared_preferences_web (0.0.1):
|
||||||
|
@ -51,6 +53,8 @@ PODS:
|
||||||
- SwiftProtobuf (1.8.0)
|
- SwiftProtobuf (1.8.0)
|
||||||
- url_launcher (0.0.1):
|
- url_launcher (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
- url_launcher_linux (0.0.1):
|
||||||
|
- Flutter
|
||||||
- url_launcher_macos (0.0.1):
|
- url_launcher_macos (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- url_launcher_web (0.0.1):
|
- url_launcher_web (0.0.1):
|
||||||
|
@ -71,9 +75,11 @@ DEPENDENCIES:
|
||||||
- path_provider_macos (from `.symlinks/plugins/path_provider_macos/ios`)
|
- path_provider_macos (from `.symlinks/plugins/path_provider_macos/ios`)
|
||||||
- share (from `.symlinks/plugins/share/ios`)
|
- share (from `.symlinks/plugins/share/ios`)
|
||||||
- shared_preferences (from `.symlinks/plugins/shared_preferences/ios`)
|
- shared_preferences (from `.symlinks/plugins/shared_preferences/ios`)
|
||||||
|
- shared_preferences_linux (from `.symlinks/plugins/shared_preferences_linux/ios`)
|
||||||
- shared_preferences_macos (from `.symlinks/plugins/shared_preferences_macos/ios`)
|
- shared_preferences_macos (from `.symlinks/plugins/shared_preferences_macos/ios`)
|
||||||
- shared_preferences_web (from `.symlinks/plugins/shared_preferences_web/ios`)
|
- shared_preferences_web (from `.symlinks/plugins/shared_preferences_web/ios`)
|
||||||
- url_launcher (from `.symlinks/plugins/url_launcher/ios`)
|
- url_launcher (from `.symlinks/plugins/url_launcher/ios`)
|
||||||
|
- url_launcher_linux (from `.symlinks/plugins/url_launcher_linux/ios`)
|
||||||
- url_launcher_macos (from `.symlinks/plugins/url_launcher_macos/ios`)
|
- url_launcher_macos (from `.symlinks/plugins/url_launcher_macos/ios`)
|
||||||
- url_launcher_web (from `.symlinks/plugins/url_launcher_web/ios`)
|
- url_launcher_web (from `.symlinks/plugins/url_launcher_web/ios`)
|
||||||
|
|
||||||
|
@ -111,12 +117,16 @@ EXTERNAL SOURCES:
|
||||||
:path: ".symlinks/plugins/share/ios"
|
:path: ".symlinks/plugins/share/ios"
|
||||||
shared_preferences:
|
shared_preferences:
|
||||||
:path: ".symlinks/plugins/shared_preferences/ios"
|
:path: ".symlinks/plugins/shared_preferences/ios"
|
||||||
|
shared_preferences_linux:
|
||||||
|
:path: ".symlinks/plugins/shared_preferences_linux/ios"
|
||||||
shared_preferences_macos:
|
shared_preferences_macos:
|
||||||
:path: ".symlinks/plugins/shared_preferences_macos/ios"
|
:path: ".symlinks/plugins/shared_preferences_macos/ios"
|
||||||
shared_preferences_web:
|
shared_preferences_web:
|
||||||
:path: ".symlinks/plugins/shared_preferences_web/ios"
|
:path: ".symlinks/plugins/shared_preferences_web/ios"
|
||||||
url_launcher:
|
url_launcher:
|
||||||
:path: ".symlinks/plugins/url_launcher/ios"
|
:path: ".symlinks/plugins/url_launcher/ios"
|
||||||
|
url_launcher_linux:
|
||||||
|
:path: ".symlinks/plugins/url_launcher_linux/ios"
|
||||||
url_launcher_macos:
|
url_launcher_macos:
|
||||||
:path: ".symlinks/plugins/url_launcher_macos/ios"
|
:path: ".symlinks/plugins/url_launcher_macos/ios"
|
||||||
url_launcher_web:
|
url_launcher_web:
|
||||||
|
@ -138,10 +148,12 @@ SPEC CHECKSUMS:
|
||||||
path_provider_macos: f760a3c5b04357c380e2fddb6f9db6f3015897e0
|
path_provider_macos: f760a3c5b04357c380e2fddb6f9db6f3015897e0
|
||||||
share: 0b2c3e82132f5888bccca3351c504d0003b3b410
|
share: 0b2c3e82132f5888bccca3351c504d0003b3b410
|
||||||
shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d
|
shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d
|
||||||
|
shared_preferences_linux: afefbfe8d921e207f01ede8b60373d9e3b566b78
|
||||||
shared_preferences_macos: f3f29b71ccbb56bf40c9dd6396c9acf15e214087
|
shared_preferences_macos: f3f29b71ccbb56bf40c9dd6396c9acf15e214087
|
||||||
shared_preferences_web: 141cce0c3ed1a1c5bf2a0e44f52d31eeb66e5ea9
|
shared_preferences_web: 141cce0c3ed1a1c5bf2a0e44f52d31eeb66e5ea9
|
||||||
SwiftProtobuf: 2cbd9409689b7df170d82a92a33443c8e3e14a70
|
SwiftProtobuf: 2cbd9409689b7df170d82a92a33443c8e3e14a70
|
||||||
url_launcher: 6fef411d543ceb26efce54b05a0a40bfd74cbbef
|
url_launcher: 6fef411d543ceb26efce54b05a0a40bfd74cbbef
|
||||||
|
url_launcher_linux: ac237cb7a8058736e4aae38bdbcc748a4b394cc0
|
||||||
url_launcher_macos: fd7894421cd39320dce5f292fc99ea9270b2a313
|
url_launcher_macos: fd7894421cd39320dce5f292fc99ea9270b2a313
|
||||||
url_launcher_web: e5527357f037c87560776e36436bf2b0288b965c
|
url_launcher_web: e5527357f037c87560776e36436bf2b0288b965c
|
||||||
|
|
||||||
|
|
|
@ -350,7 +350,6 @@
|
||||||
/* Begin XCBuildConfiguration section */
|
/* Begin XCBuildConfiguration section */
|
||||||
249021D3217E4FDB00AE95B9 /* Profile */ = {
|
249021D3217E4FDB00AE95B9 /* Profile */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
CLANG_ANALYZER_NONNULL = YES;
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
@ -404,7 +403,7 @@
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
CURRENT_PROJECT_VERSION = 12;
|
||||||
DEVELOPMENT_TEAM = 32J6BB6VUS;
|
DEVELOPMENT_TEAM = 32J6BB6VUS;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
FRAMEWORK_SEARCH_PATHS = (
|
FRAMEWORK_SEARCH_PATHS = (
|
||||||
|
@ -418,7 +417,7 @@
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"$(PROJECT_DIR)/Flutter",
|
"$(PROJECT_DIR)/Flutter",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 3.1.28;
|
MARKETING_VERSION = 3.2.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.cakewallet.cakewallet;
|
PRODUCT_BUNDLE_IDENTIFIER = com.cakewallet.cakewallet;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
|
@ -430,7 +429,6 @@
|
||||||
};
|
};
|
||||||
97C147031CF9000F007C117D /* Debug */ = {
|
97C147031CF9000F007C117D /* Debug */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
|
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
CLANG_ANALYZER_NONNULL = YES;
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
@ -486,7 +484,6 @@
|
||||||
};
|
};
|
||||||
97C147041CF9000F007C117D /* Release */ = {
|
97C147041CF9000F007C117D /* Release */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
CLANG_ANALYZER_NONNULL = YES;
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
@ -540,7 +537,7 @@
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
CURRENT_PROJECT_VERSION = 12;
|
||||||
DEVELOPMENT_TEAM = 32J6BB6VUS;
|
DEVELOPMENT_TEAM = 32J6BB6VUS;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
FRAMEWORK_SEARCH_PATHS = (
|
FRAMEWORK_SEARCH_PATHS = (
|
||||||
|
@ -554,7 +551,7 @@
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"$(PROJECT_DIR)/Flutter",
|
"$(PROJECT_DIR)/Flutter",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 3.1.28;
|
MARKETING_VERSION = 3.2.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.cakewallet.cakewallet;
|
PRODUCT_BUNDLE_IDENTIFIER = com.cakewallet.cakewallet;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
|
@ -571,7 +568,7 @@
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
CURRENT_PROJECT_VERSION = 12;
|
||||||
DEVELOPMENT_TEAM = 32J6BB6VUS;
|
DEVELOPMENT_TEAM = 32J6BB6VUS;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
FRAMEWORK_SEARCH_PATHS = (
|
FRAMEWORK_SEARCH_PATHS = (
|
||||||
|
@ -585,7 +582,7 @@
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"$(PROJECT_DIR)/Flutter",
|
"$(PROJECT_DIR)/Flutter",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 3.1.28;
|
MARKETING_VERSION = 3.2.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.cakewallet.cakewallet;
|
PRODUCT_BUNDLE_IDENTIFIER = com.cakewallet.cakewallet;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
|
<key>NSFaceIDUsageDescription</key>
|
||||||
|
<string>Enable Face ID for fast and secure access to wallets and private keys</string>
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
|
@ -19,9 +21,11 @@
|
||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
<key>NSCameraUsageDescription</key>
|
||||||
|
<string>Used for scan QR code</string>
|
||||||
<key>UILaunchStoryboardName</key>
|
<key>UILaunchStoryboardName</key>
|
||||||
<string>LaunchScreen</string>
|
<string>LaunchScreen</string>
|
||||||
<key>UIMainStoryboardFile</key>
|
<key>UIMainStoryboardFile</key>
|
||||||
|
|
|
@ -1,17 +1,19 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
class BitcoinAddressRecord {
|
class BitcoinAddressRecord {
|
||||||
BitcoinAddressRecord(this.address, {this.label});
|
BitcoinAddressRecord(this.address, {this.label, this.index});
|
||||||
|
|
||||||
factory BitcoinAddressRecord.fromJSON(String jsonSource) {
|
factory BitcoinAddressRecord.fromJSON(String jsonSource) {
|
||||||
final decoded = json.decode(jsonSource) as Map;
|
final decoded = json.decode(jsonSource) as Map;
|
||||||
|
|
||||||
return BitcoinAddressRecord(decoded['address'] as String,
|
return BitcoinAddressRecord(decoded['address'] as String,
|
||||||
label: decoded['label'] as String);
|
label: decoded['label'] as String, index: decoded['index'] as int);
|
||||||
}
|
}
|
||||||
|
|
||||||
final String address;
|
final String address;
|
||||||
|
int index;
|
||||||
String label;
|
String label;
|
||||||
|
|
||||||
String toJSON() => json.encode({'label': label, 'address': address});
|
String toJSON() =>
|
||||||
|
json.encode({'label': label, 'address': address, 'index': index});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
|
import 'package:cake_wallet/src/domain/common/transaction_priority.dart';
|
||||||
|
|
||||||
class BitcoinTransactionCredentials {
|
class BitcoinTransactionCredentials {
|
||||||
const BitcoinTransactionCredentials(this.address, this.amount);
|
BitcoinTransactionCredentials(this.address, this.amount, this.priority);
|
||||||
|
|
||||||
final String address;
|
final String address;
|
||||||
final double amount;
|
final double amount;
|
||||||
|
TransactionPriority priority;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,8 +6,6 @@ import 'package:cake_wallet/bitcoin/file.dart';
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart';
|
import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart';
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_transaction_info.dart';
|
import 'package:cake_wallet/bitcoin/bitcoin_transaction_info.dart';
|
||||||
import 'package:cake_wallet/bitcoin/electrum.dart';
|
import 'package:cake_wallet/bitcoin/electrum.dart';
|
||||||
import 'package:cake_wallet/src/domain/common/transaction_info.dart';
|
|
||||||
import 'package:cake_wallet/src/domain/common/transaction_direction.dart';
|
|
||||||
|
|
||||||
part 'bitcoin_transaction_history.g.dart';
|
part 'bitcoin_transaction_history.g.dart';
|
||||||
|
|
||||||
|
@ -24,100 +22,176 @@ abstract class BitcoinTransactionHistoryBase
|
||||||
{this.eclient, String dirPath, @required String password})
|
{this.eclient, String dirPath, @required String password})
|
||||||
: path = '$dirPath/$_transactionsHistoryFileName',
|
: path = '$dirPath/$_transactionsHistoryFileName',
|
||||||
_password = password,
|
_password = password,
|
||||||
_height = 0;
|
_height = 0,
|
||||||
|
_isUpdating = false {
|
||||||
|
transactions = ObservableMap<String, BitcoinTransactionInfo>();
|
||||||
|
}
|
||||||
|
|
||||||
BitcoinWalletBase wallet;
|
BitcoinWalletBase wallet;
|
||||||
final ElectrumClient eclient;
|
final ElectrumClient eclient;
|
||||||
final String path;
|
final String path;
|
||||||
final String _password;
|
final String _password;
|
||||||
int _height;
|
int _height;
|
||||||
|
bool _isUpdating;
|
||||||
|
|
||||||
Future<void> init() async {
|
Future<void> init() async {
|
||||||
final info = await _read();
|
await _load();
|
||||||
_height = info['height'] as int ?? _height;
|
|
||||||
transactions = ObservableList.of(
|
|
||||||
info['transactions'] as List<BitcoinTransactionInfo> ??
|
|
||||||
<BitcoinTransactionInfo>[]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future update() async {
|
Future update() async {
|
||||||
await super.update();
|
if (_isUpdating) {
|
||||||
_updateHeight();
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
_isUpdating = true;
|
||||||
|
final txs = await fetchTransactions();
|
||||||
|
await add(txs);
|
||||||
|
_isUpdating = false;
|
||||||
|
} catch (_) {
|
||||||
|
_isUpdating = false;
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<BitcoinTransactionInfo>> fetchTransactions() async {
|
Future<Map<String, BitcoinTransactionInfo>> fetchTransactions() async {
|
||||||
final addresses = wallet.addresses;
|
|
||||||
final histories =
|
final histories =
|
||||||
addresses.map((record) => eclient.getHistory(address: record.address));
|
wallet.scriptHashes.map((scriptHash) => eclient.getHistory(scriptHash));
|
||||||
final _historiesWithDetails = await Future.wait(histories)
|
final _historiesWithDetails = await Future.wait(histories)
|
||||||
.then((histories) => histories
|
.then((histories) => histories
|
||||||
.map((h) => h.where((tx) => (tx['height'] as int) > _height))
|
// .map((h) => h.where((tx) {
|
||||||
|
// final height = tx['height'] as int ?? 0;
|
||||||
|
// // FIXME: Filter only needed transactions
|
||||||
|
// final _tx = get(tx['tx_hash'] as String);
|
||||||
|
//
|
||||||
|
// return height == 0 || height > _height;
|
||||||
|
// }))
|
||||||
.expand((i) => i)
|
.expand((i) => i)
|
||||||
.toList())
|
.toList())
|
||||||
.then((histories) => histories.map((tx) => fetchTransactionInfo(
|
.then((histories) => histories.map((tx) => fetchTransactionInfo(
|
||||||
hash: tx['tx_hash'] as String, height: tx['height'] as int)));
|
hash: tx['tx_hash'] as String, height: tx['height'] as int)));
|
||||||
final historiesWithDetails = await Future.wait(_historiesWithDetails);
|
final historiesWithDetails = await Future.wait(_historiesWithDetails);
|
||||||
|
|
||||||
return historiesWithDetails
|
return historiesWithDetails.fold<Map<String, BitcoinTransactionInfo>>(
|
||||||
.map((info) => BitcoinTransactionInfo.fromHexAndHeader(
|
<String, BitcoinTransactionInfo>{}, (acc, tx) {
|
||||||
info['raw'] as String, info['header'] as Map<String, Object>,
|
acc[tx.id] = tx;
|
||||||
addresses: addresses.map((record) => record.address).toList()))
|
return acc;
|
||||||
.toList();
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Map<String, Object>> fetchTransactionInfo(
|
Future<BitcoinTransactionInfo> fetchTransactionInfo(
|
||||||
{@required String hash, @required int height}) async {
|
{@required String hash, @required int height}) async {
|
||||||
final rawFetching = eclient.getTransactionRaw(hash: hash);
|
final tx = await eclient.getTransactionExpanded(hash: hash);
|
||||||
final headerFetching = eclient.getHeader(height: height);
|
return BitcoinTransactionInfo.fromElectrumVerbose(tx,
|
||||||
final result = await Future.wait([rawFetching, headerFetching]);
|
height: height, addresses: wallet.addresses);
|
||||||
final raw = result.first as String;
|
|
||||||
final header = result[1] as Map<String, Object>;
|
|
||||||
|
|
||||||
return {'raw': raw, 'header': header};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> add(List<BitcoinTransactionInfo> transactions) async {
|
Future<void> add(Map<String, BitcoinTransactionInfo> transactionsList) async {
|
||||||
this.transactions.addAll(transactions);
|
transactionsList.entries.forEach((entry) {
|
||||||
|
_updateOrInsert(entry.value);
|
||||||
|
|
||||||
|
if (entry.value.height > _height) {
|
||||||
|
_height = entry.value.height;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
await save();
|
await save();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> addOne(BitcoinTransactionInfo tx) async {
|
Future<void> addOne(BitcoinTransactionInfo tx) async {
|
||||||
transactions.add(tx);
|
_updateOrInsert(tx);
|
||||||
|
|
||||||
|
if (tx.height > _height) {
|
||||||
|
_height = tx.height;
|
||||||
|
}
|
||||||
|
|
||||||
await save();
|
await save();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> save() async => writeData(
|
BitcoinTransactionInfo get(String id) => transactions[id];
|
||||||
path: path,
|
|
||||||
password: _password,
|
Future<void> save() async {
|
||||||
data: json.encode({'height': _height, 'transactions': transactions}));
|
final data = json.encode({'height': _height, 'transactions': transactions});
|
||||||
|
|
||||||
|
print('data');
|
||||||
|
print(data);
|
||||||
|
|
||||||
|
await writeData(path: path, password: _password, data: data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void updateAsync({void Function() onFinished}) {
|
||||||
|
fetchTransactionsAsync((transaction) => _updateOrInsert(transaction),
|
||||||
|
onFinished: onFinished);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void fetchTransactionsAsync(
|
||||||
|
void Function(BitcoinTransactionInfo transaction) onTransactionLoaded,
|
||||||
|
{void Function() onFinished}) async {
|
||||||
|
final histories = await Future.wait(wallet.scriptHashes
|
||||||
|
.map((scriptHash) async => await eclient.getHistory(scriptHash)));
|
||||||
|
final transactionsCount =
|
||||||
|
histories.fold<int>(0, (acc, m) => acc + m.length);
|
||||||
|
var counter = 0;
|
||||||
|
|
||||||
|
final batches = histories.map((metaList) =>
|
||||||
|
_fetchBatchOfTransactions(metaList, onTransactionLoaded: (transaction) {
|
||||||
|
onTransactionLoaded(transaction);
|
||||||
|
counter += 1;
|
||||||
|
|
||||||
|
if (counter == transactionsCount) {
|
||||||
|
onFinished?.call();
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
await Future.wait(batches);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _fetchBatchOfTransactions(
|
||||||
|
Iterable<Map<String, dynamic>> metaList,
|
||||||
|
{void Function(BitcoinTransactionInfo tranasaction)
|
||||||
|
onTransactionLoaded}) async =>
|
||||||
|
metaList.forEach((txMeta) => fetchTransactionInfo(
|
||||||
|
hash: txMeta['tx_hash'] as String,
|
||||||
|
height: txMeta['height'] as int)
|
||||||
|
.then((transaction) => onTransactionLoaded(transaction)));
|
||||||
|
|
||||||
Future<Map<String, Object>> _read() async {
|
Future<Map<String, Object>> _read() async {
|
||||||
try {
|
|
||||||
final content = await read(path: path, password: _password);
|
final content = await read(path: path, password: _password);
|
||||||
final jsoned = json.decode(content) as Map<String, Object>;
|
return json.decode(content) as Map<String, Object>;
|
||||||
final height = jsoned['height'] as int;
|
|
||||||
final transactions = (jsoned['transactions'] as List<dynamic>)
|
|
||||||
.map((dynamic row) {
|
|
||||||
if (row is Map<String, Object>) {
|
|
||||||
return BitcoinTransactionInfo.fromJson(row);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
Future<void> _load() async {
|
||||||
})
|
try {
|
||||||
.where((el) => el != null)
|
final content = await _read();
|
||||||
.toList();
|
final txs = content['transactions'] as Map<String, Object> ?? {};
|
||||||
|
|
||||||
return {'transactions': transactions, 'height': height};
|
txs.entries.forEach((entry) {
|
||||||
} catch (_) {
|
final val = entry.value;
|
||||||
return {'transactions': <BitcoinTransactionInfo>[], 'height': 0};
|
|
||||||
|
if (val is Map<String, Object>) {
|
||||||
|
final tx = BitcoinTransactionInfo.fromJson(val);
|
||||||
|
_updateOrInsert(tx);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
_height = content['height'] as int;
|
||||||
|
} catch (_) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _updateHeight() {
|
void _updateOrInsert(BitcoinTransactionInfo transaction) {
|
||||||
final newHeight = transactions.fold(
|
if (transactions[transaction.id] == null) {
|
||||||
0, (int acc, val) => val.height > acc ? val.height : acc);
|
transactions[transaction.id] = transaction;
|
||||||
_height = newHeight > _height ? newHeight : _height;
|
} else {
|
||||||
|
final originalTx = transactions[transaction.id];
|
||||||
|
originalTx.confirmations = transaction.confirmations;
|
||||||
|
originalTx.amount = transaction.amount;
|
||||||
|
originalTx.height = transaction.height;
|
||||||
|
originalTx.date ??= transaction.date;
|
||||||
|
originalTx.isPending = transaction.isPending;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
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:cake_wallet/bitcoin/bitcoin_address_record.dart';
|
||||||
import 'package:bitcoin_flutter/src/payments/index.dart' show PaymentData;
|
import 'package:bitcoin_flutter/src/payments/index.dart' show PaymentData;
|
||||||
|
import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart';
|
||||||
|
import 'package:cake_wallet/src/domain/bitcoin/bitcoin_amount_format.dart';
|
||||||
import 'package:cake_wallet/src/domain/common/transaction_direction.dart';
|
import 'package:cake_wallet/src/domain/common/transaction_direction.dart';
|
||||||
import 'package:cake_wallet/src/domain/common/transaction_info.dart';
|
import 'package:cake_wallet/src/domain/common/transaction_info.dart';
|
||||||
import 'package:cake_wallet/src/domain/common/format_amount.dart';
|
import 'package:cake_wallet/src/domain/common/format_amount.dart';
|
||||||
|
@ -13,21 +15,71 @@ class BitcoinTransactionInfo extends TransactionInfo {
|
||||||
@required int amount,
|
@required int amount,
|
||||||
@required TransactionDirection direction,
|
@required TransactionDirection direction,
|
||||||
@required bool isPending,
|
@required bool isPending,
|
||||||
@required DateTime date}) {
|
@required DateTime date,
|
||||||
|
@required int confirmations}) {
|
||||||
this.height = height;
|
this.height = height;
|
||||||
this.amount = amount;
|
this.amount = amount;
|
||||||
this.direction = direction;
|
this.direction = direction;
|
||||||
this.date = date;
|
this.date = date;
|
||||||
this.isPending = isPending;
|
this.isPending = isPending;
|
||||||
|
this.confirmations = confirmations;
|
||||||
}
|
}
|
||||||
|
|
||||||
factory BitcoinTransactionInfo.fromHexAndHeader(
|
factory BitcoinTransactionInfo.fromElectrumVerbose(Map<String, Object> obj,
|
||||||
String hex, Map<String, Object> header,
|
{@required List<BitcoinAddressRecord> addresses, @required int height}) {
|
||||||
{List<String> addresses}) {
|
final addressesSet = addresses.map((addr) => addr.address).toSet();
|
||||||
|
final id = obj['txid'] as String;
|
||||||
|
final vins = obj['vin'] as List<Object> ?? [];
|
||||||
|
final vout = (obj['vout'] as List<Object> ?? []);
|
||||||
|
final date = obj['time'] is int
|
||||||
|
? DateTime.fromMillisecondsSinceEpoch((obj['time'] as int) * 1000)
|
||||||
|
: DateTime.now();
|
||||||
|
final confirmations = obj['confirmations'] as int ?? 0;
|
||||||
|
var direction = TransactionDirection.incoming;
|
||||||
|
|
||||||
|
for (dynamic vin in vins) {
|
||||||
|
final vout = vin['vout'] as int;
|
||||||
|
final out = vin['tx']['vout'][vout] as Map;
|
||||||
|
final outAddresses =
|
||||||
|
(out['scriptPubKey']['addresses'] as List<Object>)?.toSet();
|
||||||
|
|
||||||
|
if (outAddresses?.intersection(addressesSet)?.isNotEmpty ?? false) {
|
||||||
|
direction = TransactionDirection.outgoing;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final amount = vout.fold(0, (int acc, dynamic out) {
|
||||||
|
final outAddresses =
|
||||||
|
out['scriptPubKey']['addresses'] as List<Object> ?? [];
|
||||||
|
final ntrs = outAddresses.toSet().intersection(addressesSet);
|
||||||
|
var amount = acc;
|
||||||
|
|
||||||
|
if ((direction == TransactionDirection.incoming && ntrs.isNotEmpty) ||
|
||||||
|
(direction == TransactionDirection.outgoing && ntrs.isEmpty)) {
|
||||||
|
amount += doubleToBitcoinAmount(out['value'] as double ?? 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return amount;
|
||||||
|
});
|
||||||
|
|
||||||
|
return BitcoinTransactionInfo(
|
||||||
|
id: id,
|
||||||
|
height: height,
|
||||||
|
isPending: false,
|
||||||
|
direction: direction,
|
||||||
|
amount: amount,
|
||||||
|
date: date,
|
||||||
|
confirmations: confirmations);
|
||||||
|
}
|
||||||
|
|
||||||
|
factory BitcoinTransactionInfo.fromHexAndHeader(String hex,
|
||||||
|
{List<String> addresses, int height, int timestamp, int confirmations}) {
|
||||||
final tx = bitcoin.Transaction.fromHex(hex);
|
final tx = bitcoin.Transaction.fromHex(hex);
|
||||||
var exist = false;
|
var exist = false;
|
||||||
var amount = 0;
|
var amount = 0;
|
||||||
|
|
||||||
|
if (addresses != null) {
|
||||||
tx.outs.forEach((out) {
|
tx.outs.forEach((out) {
|
||||||
try {
|
try {
|
||||||
final p2pkh = bitcoin.P2PKH(
|
final p2pkh = bitcoin.P2PKH(
|
||||||
|
@ -39,16 +91,21 @@ class BitcoinTransactionInfo extends TransactionInfo {
|
||||||
}
|
}
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
final date = timestamp != null
|
||||||
|
? DateTime.fromMillisecondsSinceEpoch(timestamp * 1000)
|
||||||
|
: DateTime.now();
|
||||||
|
|
||||||
// FIXME: Get transaction is pending
|
// FIXME: Get transaction is pending
|
||||||
return BitcoinTransactionInfo(
|
return BitcoinTransactionInfo(
|
||||||
id: tx.getId(),
|
id: tx.getId(),
|
||||||
height: header['block_height'] as int,
|
height: height,
|
||||||
isPending: false,
|
isPending: false,
|
||||||
direction: TransactionDirection.incoming,
|
direction: TransactionDirection.incoming,
|
||||||
amount: amount,
|
amount: amount,
|
||||||
date: DateTime.fromMillisecondsSinceEpoch(
|
date: date,
|
||||||
(header['timestamp'] as int) * 1000));
|
confirmations: confirmations);
|
||||||
}
|
}
|
||||||
|
|
||||||
factory BitcoinTransactionInfo.fromJson(Map<String, dynamic> data) {
|
factory BitcoinTransactionInfo.fromJson(Map<String, dynamic> data) {
|
||||||
|
@ -58,7 +115,8 @@ class BitcoinTransactionInfo extends TransactionInfo {
|
||||||
amount: data['amount'] as int,
|
amount: data['amount'] as int,
|
||||||
direction: parseTransactionDirectionFromInt(data['direction'] as int),
|
direction: parseTransactionDirectionFromInt(data['direction'] as int),
|
||||||
date: DateTime.fromMillisecondsSinceEpoch(data['date'] as int),
|
date: DateTime.fromMillisecondsSinceEpoch(data['date'] as int),
|
||||||
isPending: data['isPending'] as bool);
|
isPending: data['isPending'] as bool,
|
||||||
|
confirmations: data['confirmations'] as int);
|
||||||
}
|
}
|
||||||
|
|
||||||
final String id;
|
final String id;
|
||||||
|
@ -66,7 +124,8 @@ class BitcoinTransactionInfo extends TransactionInfo {
|
||||||
String _fiatAmount;
|
String _fiatAmount;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String amountFormatted() => '${formatAmount(bitcoinAmountToString(amount: amount))} BTC';
|
String amountFormatted() =>
|
||||||
|
'${formatAmount(bitcoinAmountToString(amount: amount))} BTC';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String fiatAmount() => _fiatAmount ?? '';
|
String fiatAmount() => _fiatAmount ?? '';
|
||||||
|
@ -75,13 +134,14 @@ class BitcoinTransactionInfo extends TransactionInfo {
|
||||||
void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount);
|
void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount);
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
final m = Map<String, dynamic>();
|
final m = <String, dynamic>{};
|
||||||
m['id'] = id;
|
m['id'] = id;
|
||||||
m['height'] = height;
|
m['height'] = height;
|
||||||
m['amount'] = amount;
|
m['amount'] = amount;
|
||||||
m['direction'] = direction.index;
|
m['direction'] = direction.index;
|
||||||
m['date'] = date.millisecondsSinceEpoch;
|
m['date'] = date.millisecondsSinceEpoch;
|
||||||
m['isPending'] = isPending;
|
m['isPending'] = isPending;
|
||||||
|
m['confirmations'] = confirmations;
|
||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
4
lib/bitcoin/bitcoin_transaction_no_inputs_exception.dart
Normal file
4
lib/bitcoin/bitcoin_transaction_no_inputs_exception.dart
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
class BitcoinTransactionNoInputsException implements Exception {
|
||||||
|
@override
|
||||||
|
String toString() => 'No inputs for the transaction.';
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
class BitcoinTransactionWrongBalanceException implements Exception {
|
||||||
|
@override
|
||||||
|
String toString() => 'Wrong balance. Not enough BTC on your balance.';
|
||||||
|
}
|
17
lib/bitcoin/bitcoin_unspent.dart
Normal file
17
lib/bitcoin/bitcoin_unspent.dart
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import 'package:cake_wallet/bitcoin/bitcoin_address_record.dart';
|
||||||
|
|
||||||
|
class BitcoinUnspent {
|
||||||
|
BitcoinUnspent(this.address, this.hash, this.value, this.vout);
|
||||||
|
|
||||||
|
factory BitcoinUnspent.fromJSON(
|
||||||
|
BitcoinAddressRecord address, Map<String, dynamic> json) =>
|
||||||
|
BitcoinUnspent(address, json['tx_hash'] as String, json['value'] as int,
|
||||||
|
json['tx_pos'] as int);
|
||||||
|
|
||||||
|
final BitcoinAddressRecord address;
|
||||||
|
final String hash;
|
||||||
|
final int value;
|
||||||
|
final int vout;
|
||||||
|
|
||||||
|
bool get isP2wpkh => address.address.startsWith('bc1');
|
||||||
|
}
|
|
@ -1,10 +1,21 @@
|
||||||
|
import 'dart:async';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_transaction_credentials.dart';
|
import 'package:cake_wallet/bitcoin/bitcoin_transaction_credentials.dart';
|
||||||
|
import 'package:cake_wallet/bitcoin/bitcoin_transaction_info.dart';
|
||||||
|
import 'package:cake_wallet/bitcoin/bitcoin_transaction_no_inputs_exception.dart';
|
||||||
|
import 'package:cake_wallet/bitcoin/bitcoin_transaction_wrong_balance_exception.dart';
|
||||||
|
import 'package:cake_wallet/bitcoin/bitcoin_unspent.dart';
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_wallet_keys.dart';
|
import 'package:cake_wallet/bitcoin/bitcoin_wallet_keys.dart';
|
||||||
|
import 'package:cake_wallet/bitcoin/pending_bitcoin_transaction.dart';
|
||||||
|
import 'package:cake_wallet/bitcoin/script_hash.dart';
|
||||||
|
import 'package:cake_wallet/bitcoin/utils.dart';
|
||||||
import 'package:cake_wallet/src/domain/bitcoin/bitcoin_amount_format.dart';
|
import 'package:cake_wallet/src/domain/bitcoin/bitcoin_amount_format.dart';
|
||||||
import 'package:cake_wallet/src/domain/common/crypto_currency.dart';
|
import 'package:cake_wallet/src/domain/common/crypto_currency.dart';
|
||||||
import 'package:cake_wallet/src/domain/common/sync_status.dart';
|
import 'package:cake_wallet/src/domain/common/sync_status.dart';
|
||||||
|
import 'package:cake_wallet/src/domain/common/transaction_direction.dart';
|
||||||
|
import 'package:cake_wallet/src/domain/common/transaction_priority.dart';
|
||||||
|
import 'package:cw_monero/transaction_history.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
import 'package:bip39/bip39.dart' as bip39;
|
import 'package:bip39/bip39.dart' as bip39;
|
||||||
|
@ -20,12 +31,41 @@ import 'package:cake_wallet/bitcoin/bitcoin_balance.dart';
|
||||||
import 'package:cake_wallet/src/domain/common/node.dart';
|
import 'package:cake_wallet/src/domain/common/node.dart';
|
||||||
import 'package:cake_wallet/core/wallet_base.dart';
|
import 'package:cake_wallet/core/wallet_base.dart';
|
||||||
import 'package:rxdart/rxdart.dart';
|
import 'package:rxdart/rxdart.dart';
|
||||||
|
import 'package:hex/hex.dart';
|
||||||
|
import 'package:cake_wallet/di.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
part 'bitcoin_wallet.g.dart';
|
part 'bitcoin_wallet.g.dart';
|
||||||
|
|
||||||
class BitcoinWallet = BitcoinWalletBase with _$BitcoinWallet;
|
class BitcoinWallet = BitcoinWalletBase with _$BitcoinWallet;
|
||||||
|
|
||||||
abstract class BitcoinWalletBase extends WalletBase<BitcoinBalance> with Store {
|
abstract class BitcoinWalletBase extends WalletBase<BitcoinBalance> with Store {
|
||||||
|
BitcoinWalletBase._internal(
|
||||||
|
{@required this.eclient,
|
||||||
|
@required this.path,
|
||||||
|
@required String password,
|
||||||
|
@required this.name,
|
||||||
|
List<BitcoinAddressRecord> initialAddresses,
|
||||||
|
int accountIndex = 0,
|
||||||
|
this.transactionHistory,
|
||||||
|
this.mnemonic,
|
||||||
|
BitcoinBalance initialBalance})
|
||||||
|
: balance =
|
||||||
|
initialBalance ?? BitcoinBalance(confirmed: 0, unconfirmed: 0),
|
||||||
|
hd = bitcoin.HDWallet.fromSeed(bip39.mnemonicToSeed(mnemonic),
|
||||||
|
network: bitcoin.bitcoin),
|
||||||
|
addresses = initialAddresses != null
|
||||||
|
? ObservableList<BitcoinAddressRecord>.of(initialAddresses)
|
||||||
|
: ObservableList<BitcoinAddressRecord>(),
|
||||||
|
syncStatus = NotConnectedSyncStatus(),
|
||||||
|
_password = password,
|
||||||
|
_accountIndex = accountIndex,
|
||||||
|
_addressesKeys = {} {
|
||||||
|
type = WalletType.bitcoin;
|
||||||
|
currency = CryptoCurrency.btc;
|
||||||
|
_scripthashesUpdateSubject = {};
|
||||||
|
}
|
||||||
|
|
||||||
static BitcoinWallet fromJSON(
|
static BitcoinWallet fromJSON(
|
||||||
{@required String password,
|
{@required String password,
|
||||||
@required String name,
|
@required String name,
|
||||||
|
@ -37,12 +77,12 @@ abstract class BitcoinWalletBase extends WalletBase<BitcoinBalance> with Store {
|
||||||
(data['account_index'] == 'null' || data['account_index'] == null)
|
(data['account_index'] == 'null' || data['account_index'] == null)
|
||||||
? 0
|
? 0
|
||||||
: int.parse(data['account_index'] as String);
|
: int.parse(data['account_index'] as String);
|
||||||
final _addresses = data['addresses'] as List;
|
final _addresses = data['addresses'] as List ?? <Object>[];
|
||||||
final addresses = <BitcoinAddressRecord>[];
|
final addresses = <BitcoinAddressRecord>[];
|
||||||
final balance = BitcoinBalance.fromJSON(data['balance'] as String) ??
|
final balance = BitcoinBalance.fromJSON(data['balance'] as String) ??
|
||||||
BitcoinBalance(confirmed: 0, unconfirmed: 0);
|
BitcoinBalance(confirmed: 0, unconfirmed: 0);
|
||||||
|
|
||||||
_addresses?.forEach((Object el) {
|
_addresses.forEach((Object el) {
|
||||||
if (el is String) {
|
if (el is String) {
|
||||||
addresses.add(BitcoinAddressRecord.fromJSON(el));
|
addresses.add(BitcoinAddressRecord.fromJSON(el));
|
||||||
}
|
}
|
||||||
|
@ -83,34 +123,10 @@ abstract class BitcoinWalletBase extends WalletBase<BitcoinBalance> with Store {
|
||||||
transactionHistory: history);
|
transactionHistory: history);
|
||||||
}
|
}
|
||||||
|
|
||||||
BitcoinWalletBase._internal(
|
|
||||||
{@required this.eclient,
|
|
||||||
@required this.path,
|
|
||||||
@required String password,
|
|
||||||
@required this.name,
|
|
||||||
List<BitcoinAddressRecord> initialAddresses,
|
|
||||||
int accountIndex = 0,
|
|
||||||
this.transactionHistory,
|
|
||||||
this.mnemonic,
|
|
||||||
BitcoinBalance initialBalance}) {
|
|
||||||
type = WalletType.bitcoin;
|
|
||||||
currency = CryptoCurrency.btc;
|
|
||||||
balance = initialBalance ?? BitcoinBalance(confirmed: 0, unconfirmed: 0);
|
|
||||||
hd = bitcoin.HDWallet.fromSeed(bip39.mnemonicToSeed(mnemonic),
|
|
||||||
network: bitcoin.bitcoin);
|
|
||||||
addresses = initialAddresses != null
|
|
||||||
? ObservableList<BitcoinAddressRecord>.of(initialAddresses)
|
|
||||||
: ObservableList<BitcoinAddressRecord>();
|
|
||||||
syncStatus = NotConnectedSyncStatus();
|
|
||||||
|
|
||||||
_password = password;
|
|
||||||
_accountIndex = accountIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final BitcoinTransactionHistory transactionHistory;
|
final BitcoinTransactionHistory transactionHistory;
|
||||||
final String path;
|
final String path;
|
||||||
bitcoin.HDWallet hd;
|
final bitcoin.HDWallet hd;
|
||||||
final ElectrumClient eclient;
|
final ElectrumClient eclient;
|
||||||
final String mnemonic;
|
final String mnemonic;
|
||||||
|
|
||||||
|
@ -131,6 +147,11 @@ abstract class BitcoinWalletBase extends WalletBase<BitcoinBalance> with Store {
|
||||||
|
|
||||||
ObservableList<BitcoinAddressRecord> addresses;
|
ObservableList<BitcoinAddressRecord> addresses;
|
||||||
|
|
||||||
|
Map<String, bitcoin.ECPair> _addressesKeys;
|
||||||
|
|
||||||
|
List<String> get scriptHashes =>
|
||||||
|
addresses.map((addr) => scriptHash(addr.address)).toList();
|
||||||
|
|
||||||
String get xpub => hd.base58;
|
String get xpub => hd.base58;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -142,11 +163,13 @@ abstract class BitcoinWalletBase extends WalletBase<BitcoinBalance> with Store {
|
||||||
|
|
||||||
int _accountIndex;
|
int _accountIndex;
|
||||||
String _password;
|
String _password;
|
||||||
BehaviorSubject<Object> _addressUpdateSubject;
|
Map<String, BehaviorSubject<Object>> _scripthashesUpdateSubject;
|
||||||
|
|
||||||
Future<void> init() async {
|
Future<void> init() async {
|
||||||
if (addresses.isEmpty) {
|
if (addresses.isEmpty) {
|
||||||
addresses.add(BitcoinAddressRecord(_getAddress(hd: hd, index: 0)));
|
final index = 0;
|
||||||
|
addresses
|
||||||
|
.add(BitcoinAddressRecord(_getAddress(index: index), index: index));
|
||||||
}
|
}
|
||||||
|
|
||||||
address = addresses.first.address;
|
address = addresses.first.address;
|
||||||
|
@ -156,9 +179,8 @@ abstract class BitcoinWalletBase extends WalletBase<BitcoinBalance> with Store {
|
||||||
|
|
||||||
Future<BitcoinAddressRecord> generateNewAddress({String label}) async {
|
Future<BitcoinAddressRecord> generateNewAddress({String label}) async {
|
||||||
_accountIndex += 1;
|
_accountIndex += 1;
|
||||||
final address = BitcoinAddressRecord(
|
final address = BitcoinAddressRecord(_getAddress(index: _accountIndex),
|
||||||
_getAddress(hd: hd, index: _accountIndex),
|
index: _accountIndex, label: label);
|
||||||
label: label);
|
|
||||||
addresses.add(address);
|
addresses.add(address);
|
||||||
|
|
||||||
await save();
|
await save();
|
||||||
|
@ -181,9 +203,9 @@ abstract class BitcoinWalletBase extends WalletBase<BitcoinBalance> with Store {
|
||||||
Future<void> startSync() async {
|
Future<void> startSync() async {
|
||||||
try {
|
try {
|
||||||
syncStatus = StartingSyncStatus();
|
syncStatus = StartingSyncStatus();
|
||||||
await _addressUpdateSubject?.close();
|
transactionHistory.updateAsync(
|
||||||
_addressUpdateSubject = eclient.addressUpdate(address: address);
|
onFinished: () => print('transactionHistory update finished!'));
|
||||||
await transactionHistory.update();
|
_subscribeForUpdates();
|
||||||
await _updateBalance();
|
await _updateBalance();
|
||||||
syncStatus = SyncedSyncStatus();
|
syncStatus = SyncedSyncStatus();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -197,38 +219,102 @@ abstract class BitcoinWalletBase extends WalletBase<BitcoinBalance> with Store {
|
||||||
Future<void> connectToNode({@required Node node}) async {
|
Future<void> connectToNode({@required Node node}) async {
|
||||||
try {
|
try {
|
||||||
syncStatus = ConnectingSyncStatus();
|
syncStatus = ConnectingSyncStatus();
|
||||||
await eclient.connect(host: 'electrum2.hodlister.co', port: 50002);
|
await eclient.connectToUri(node.uri);
|
||||||
|
eclient.onConnectionStatusChange = (bool isConnected) {
|
||||||
|
if (!isConnected) {
|
||||||
|
syncStatus = LostConnectionSyncStatus();
|
||||||
|
}
|
||||||
|
};
|
||||||
syncStatus = ConnectedSyncStatus();
|
syncStatus = ConnectedSyncStatus();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print(e.toString);
|
print(e.toString());
|
||||||
syncStatus = FailedSyncStatus();
|
syncStatus = FailedSyncStatus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> createTransaction(Object credentials) async {
|
Future<PendingBitcoinTransaction> createTransaction(
|
||||||
|
Object credentials) async {
|
||||||
final transactionCredentials = credentials as BitcoinTransactionCredentials;
|
final transactionCredentials = credentials as BitcoinTransactionCredentials;
|
||||||
|
final inputs = <BitcoinUnspent>[];
|
||||||
|
final fee = _feeMultiplier(transactionCredentials.priority);
|
||||||
|
final amount = transactionCredentials.amount != null
|
||||||
|
? doubleToBitcoinAmount(transactionCredentials.amount)
|
||||||
|
: balance.total - fee;
|
||||||
|
final totalAmount = amount + fee;
|
||||||
final txb = bitcoin.TransactionBuilder(network: bitcoin.bitcoin);
|
final txb = bitcoin.TransactionBuilder(network: bitcoin.bitcoin);
|
||||||
final keyPair = bitcoin.ECPair.fromWIF(hd.wif);
|
var leftAmount = totalAmount;
|
||||||
final transactions = transactionHistory.transactions;
|
final changeAddress = address;
|
||||||
transactions.sort((q, w) => q.height.compareTo(w.height));
|
var totalInputAmount = 0;
|
||||||
final prevTx = transactions.first;
|
|
||||||
|
|
||||||
txb.setVersion(1);
|
final unspent = addresses.map((address) => eclient
|
||||||
txb.addInput(prevTx, 0);
|
.getListUnspentWithAddress(address.address)
|
||||||
txb.addOutput(transactionCredentials.address,
|
.then((unspent) => unspent
|
||||||
doubleToBitcoinAmount(transactionCredentials.amount));
|
.map((unspent) => BitcoinUnspent.fromJSON(address, unspent))));
|
||||||
txb.sign(vin: 0, keyPair: keyPair);
|
|
||||||
final encoded = txb.build().toHex();
|
|
||||||
|
|
||||||
print('Enoded transaction $encoded');
|
for (final unptsFutures in unspent) {
|
||||||
await eclient.broadcastTransaction(transactionRaw: encoded);
|
final utxs = await unptsFutures;
|
||||||
|
|
||||||
|
for (final utx in utxs) {
|
||||||
|
final inAmount = utx.value > totalAmount ? totalAmount : utx.value;
|
||||||
|
leftAmount = leftAmount - inAmount;
|
||||||
|
totalInputAmount += inAmount;
|
||||||
|
inputs.add(utx);
|
||||||
|
|
||||||
|
if (leftAmount <= 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
if (leftAmount <= 0) {
|
||||||
Future<void> save() async =>
|
break;
|
||||||
await write(path: path, password: _password, data: toJSON());
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inputs.isEmpty) {
|
||||||
|
throw BitcoinTransactionNoInputsException();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (amount <= 0 || totalInputAmount < amount) {
|
||||||
|
throw BitcoinTransactionWrongBalanceException();
|
||||||
|
}
|
||||||
|
|
||||||
|
final changeValue = totalInputAmount - amount - fee;
|
||||||
|
|
||||||
|
txb.setVersion(1);
|
||||||
|
|
||||||
|
inputs.forEach((input) {
|
||||||
|
if (input.isP2wpkh) {
|
||||||
|
final p2wpkh = bitcoin
|
||||||
|
.P2WPKH(
|
||||||
|
data: generatePaymentData(hd: hd, index: input.address.index),
|
||||||
|
network: bitcoin.bitcoin)
|
||||||
|
.data;
|
||||||
|
|
||||||
|
txb.addInput(input.hash, input.vout, null, p2wpkh.output);
|
||||||
|
} else {
|
||||||
|
txb.addInput(input.hash, input.vout);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
txb.addOutput(transactionCredentials.address, amount);
|
||||||
|
|
||||||
|
if (changeValue > 0) {
|
||||||
|
txb.addOutput(changeAddress, changeValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < inputs.length; i++) {
|
||||||
|
final input = inputs[i];
|
||||||
|
final keyPair = generateKeyPair(hd: hd, index: input.address.index);
|
||||||
|
final witnessValue = input.isP2wpkh ? input.value : null;
|
||||||
|
|
||||||
|
txb.sign(vin: i, keyPair: keyPair, witnessValue: witnessValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
return PendingBitcoinTransaction(txb.build(),
|
||||||
|
eclient: eclient, amount: amount, fee: fee)
|
||||||
|
..addListener((transaction) => transactionHistory.addOne(transaction));
|
||||||
|
}
|
||||||
|
|
||||||
String toJSON() => json.encode({
|
String toJSON() => json.encode({
|
||||||
'mnemonic': mnemonic,
|
'mnemonic': mnemonic,
|
||||||
|
@ -237,16 +323,31 @@ abstract class BitcoinWalletBase extends WalletBase<BitcoinBalance> with Store {
|
||||||
'balance': balance?.toJSON()
|
'balance': balance?.toJSON()
|
||||||
});
|
});
|
||||||
|
|
||||||
String _getAddress({bitcoin.HDWallet hd, int index}) => bitcoin
|
@override
|
||||||
.P2WPKH(
|
double calculateEstimatedFee(TransactionPriority priority) =>
|
||||||
data: PaymentData(
|
bitcoinAmountToDouble(amount: _feeMultiplier(priority));
|
||||||
pubkey: Uint8List.fromList(hd.derive(index).pubKey.codeUnits)))
|
|
||||||
.data
|
@override
|
||||||
.address;
|
Future<void> save() async =>
|
||||||
|
await write(path: path, password: _password, data: toJSON());
|
||||||
|
|
||||||
|
bitcoin.ECPair keyPairFor({@required int index}) =>
|
||||||
|
generateKeyPair(hd: hd, index: index);
|
||||||
|
|
||||||
|
void _subscribeForUpdates() {
|
||||||
|
scriptHashes.forEach((sh) async {
|
||||||
|
await _scripthashesUpdateSubject[sh]?.close();
|
||||||
|
_scripthashesUpdateSubject[sh] = eclient.scripthashUpdate(sh);
|
||||||
|
_scripthashesUpdateSubject[sh].listen((event) async {
|
||||||
|
transactionHistory.updateAsync();
|
||||||
|
await _updateBalance();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Future<BitcoinBalance> _fetchBalances() async {
|
Future<BitcoinBalance> _fetchBalances() async {
|
||||||
final balances = await Future.wait(
|
final balances = await Future.wait(
|
||||||
addresses.map((record) => eclient.getBalance(address: record.address)));
|
scriptHashes.map((sHash) => eclient.getBalance(sHash)));
|
||||||
final balance = balances.fold(
|
final balance = balances.fold(
|
||||||
BitcoinBalance(confirmed: 0, unconfirmed: 0),
|
BitcoinBalance(confirmed: 0, unconfirmed: 0),
|
||||||
(BitcoinBalance acc, val) => BitcoinBalance(
|
(BitcoinBalance acc, val) => BitcoinBalance(
|
||||||
|
@ -261,4 +362,20 @@ abstract class BitcoinWalletBase extends WalletBase<BitcoinBalance> with Store {
|
||||||
balance = await _fetchBalances();
|
balance = await _fetchBalances();
|
||||||
await save();
|
await save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String _getAddress({@required int index}) =>
|
||||||
|
generateAddress(hd: hd, index: index);
|
||||||
|
|
||||||
|
int _feeMultiplier(TransactionPriority priority) {
|
||||||
|
switch (priority) {
|
||||||
|
case TransactionPriority.slow:
|
||||||
|
return 6000;
|
||||||
|
case TransactionPriority.regular:
|
||||||
|
return 9000;
|
||||||
|
case TransactionPriority.fast:
|
||||||
|
return 15000;
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:convert';
|
|
||||||
import 'package:bip39/bip39.dart' as bip39;
|
import 'package:bip39/bip39.dart' as bip39;
|
||||||
import 'package:cake_wallet/bitcoin/file.dart';
|
import 'package:cake_wallet/bitcoin/file.dart';
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_wallet_creation_credentials.dart';
|
import 'package:cake_wallet/bitcoin/bitcoin_wallet_creation_credentials.dart';
|
||||||
|
@ -49,10 +48,9 @@ class BitcoinWalletService extends WalletService<
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> remove(String wallet) {
|
Future<void> remove(String wallet) async =>
|
||||||
// TODO: implement remove
|
File(await pathForWalletDir(name: wallet, type: WalletType.bitcoin))
|
||||||
throw UnimplementedError();
|
.delete(recursive: true);
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<BitcoinWallet> restoreFromKeys(
|
Future<BitcoinWallet> restoreFromKeys(
|
||||||
|
|
|
@ -1,17 +1,29 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
import 'package:cake_wallet/bitcoin/script_hash.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:rxdart/rxdart.dart';
|
import 'package:rxdart/rxdart.dart';
|
||||||
|
|
||||||
|
class UriParseException implements Exception {
|
||||||
|
UriParseException(this.uri);
|
||||||
|
|
||||||
|
final String uri;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() =>
|
||||||
|
'Cannot parse host and port from uri. Invalid uri format. Uri: $uri';
|
||||||
|
}
|
||||||
|
|
||||||
String jsonrpcparams(List<Object> params) {
|
String jsonrpcparams(List<Object> params) {
|
||||||
final _params = params?.map((val) => '"${val.toString()}"')?.join(',');
|
final _params = params?.map((val) => '"${val.toString()}"')?.join(',');
|
||||||
return "[$_params]";
|
return '[$_params]';
|
||||||
}
|
}
|
||||||
|
|
||||||
String jsonrpc(
|
String jsonrpc(
|
||||||
{String method, List<Object> params, int id, double version = 2.0}) =>
|
{String method, List<Object> params, int id, double version = 2.0}) =>
|
||||||
'{"jsonrpc": "$version", "method": "$method", "id": "$id", "params": ${jsonrpcparams(params)}}\n';
|
'{"jsonrpc": "$version", "method": "$method", "id": "$id", "params": ${json.encode(params)}}\n';
|
||||||
|
|
||||||
class SocketTask {
|
class SocketTask {
|
||||||
SocketTask({this.completer, this.isSubscription, this.subject});
|
SocketTask({this.completer, this.isSubscription, this.subject});
|
||||||
|
@ -31,58 +43,72 @@ class ElectrumClient {
|
||||||
|
|
||||||
bool get isConnected => _isConnected;
|
bool get isConnected => _isConnected;
|
||||||
Socket socket;
|
Socket socket;
|
||||||
|
void Function(bool) onConnectionStatusChange;
|
||||||
int _id;
|
int _id;
|
||||||
final Map<String, SocketTask> _tasks;
|
final Map<String, SocketTask> _tasks;
|
||||||
bool _isConnected;
|
bool _isConnected;
|
||||||
Timer _aliveTimer;
|
Timer _aliveTimer;
|
||||||
|
|
||||||
Future<void> connect({@required String host, @required int port}) async {
|
Future<void> connectToUri(String uri) async {
|
||||||
if (socket != null) {
|
final splittedUri = uri.split(':');
|
||||||
await socket.close();
|
|
||||||
|
if (splittedUri.length != 2) {
|
||||||
|
throw UriParseException(uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
final start = DateTime.now();
|
final host = splittedUri.first;
|
||||||
|
final port = int.parse(splittedUri.last);
|
||||||
|
await connect(host: host, port: port);
|
||||||
|
}
|
||||||
|
|
||||||
socket = await SecureSocket.connect(host, port, timeout: connectionTimeout);
|
Future<void> connect({@required String host, @required int port}) async {
|
||||||
|
|
||||||
_isConnected = true;
|
|
||||||
|
|
||||||
socket.listen((List<int> event) {
|
|
||||||
try {
|
try {
|
||||||
final jsoned = json.decode(utf8.decode(event)) as Map<String, Object>;
|
await socket?.close();
|
||||||
|
} catch (_) {}
|
||||||
|
|
||||||
|
socket = await SecureSocket.connect(host, port,
|
||||||
|
timeout: connectionTimeout, onBadCertificate: (_) => true);
|
||||||
|
_setIsConnected(true);
|
||||||
|
|
||||||
|
socket.listen((Uint8List event) {
|
||||||
|
try {
|
||||||
|
final jsoned =
|
||||||
|
json.decode(utf8.decode(event.toList())) as Map<String, Object>;
|
||||||
|
print(jsoned);
|
||||||
final method = jsoned['method'];
|
final method = jsoned['method'];
|
||||||
|
final id = jsoned['id'] as String;
|
||||||
|
final params = jsoned['result'];
|
||||||
|
|
||||||
if (method is String) {
|
if (method is String) {
|
||||||
_methodHandler(method: method, request: jsoned);
|
_methodHandler(method: method, request: jsoned);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final id = jsoned['id'] as String;
|
|
||||||
final params = jsoned['result'];
|
|
||||||
|
|
||||||
_finish(id, params);
|
_finish(id, params);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print(e);
|
print(e);
|
||||||
}
|
}
|
||||||
}, onError: (Object error) {
|
}, onError: (Object error) {
|
||||||
print('ElectrumClient error: ${error.toString()}');
|
print(error.toString());
|
||||||
}, onDone: () {
|
_setIsConnected(false);
|
||||||
final end = DateTime.now();
|
}, onDone: () => _setIsConnected(false));
|
||||||
final diff = end.millisecondsSinceEpoch - start.millisecondsSinceEpoch;
|
|
||||||
print('On done: $diff');
|
|
||||||
});
|
|
||||||
|
|
||||||
print('Connected to ${socket.remoteAddress}');
|
|
||||||
keepAlive();
|
keepAlive();
|
||||||
}
|
}
|
||||||
|
|
||||||
void keepAlive() {
|
void keepAlive() {
|
||||||
_aliveTimer?.cancel();
|
_aliveTimer?.cancel();
|
||||||
// FIXME: Unnamed constant.
|
// FIXME: Unnamed constant.
|
||||||
_aliveTimer = Timer.periodic(Duration(seconds: 30), (_) async => ping());
|
_aliveTimer = Timer.periodic(Duration(seconds: 2), (_) async => ping());
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> ping() => call(method: 'server.ping');
|
Future<void> ping() async {
|
||||||
|
try {
|
||||||
|
// await callWithTimeout(method: 'server.ping');
|
||||||
|
_setIsConnected(true);
|
||||||
|
} on RequestFailedTimeoutException catch (_) {
|
||||||
|
_setIsConnected(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<List<String>> version() =>
|
Future<List<String>> version() =>
|
||||||
call(method: 'server.version').then((dynamic result) {
|
call(method: 'server.version').then((dynamic result) {
|
||||||
|
@ -93,18 +119,18 @@ class ElectrumClient {
|
||||||
return [];
|
return [];
|
||||||
});
|
});
|
||||||
|
|
||||||
Future<Map<String, Object>> getBalance({String address}) =>
|
Future<Map<String, Object>> getBalance(String scriptHash) =>
|
||||||
call(method: 'blockchain.address.get_balance', params: [address])
|
call(method: 'blockchain.scripthash.get_balance', params: [scriptHash])
|
||||||
.then((dynamic result) {
|
.then((dynamic result) {
|
||||||
if (result is Map<String, Object>) {
|
if (result is Map<String, Object>) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Map<String, Object>();
|
return <String, Object>{};
|
||||||
});
|
});
|
||||||
|
|
||||||
Future<List<Map<String, dynamic>>> getHistory({String address}) =>
|
Future<List<Map<String, dynamic>>> getHistory(String scriptHash) =>
|
||||||
call(method: 'blockchain.address.get_history', params: [address])
|
call(method: 'blockchain.scripthash.get_history', params: [scriptHash])
|
||||||
.then((dynamic result) {
|
.then((dynamic result) {
|
||||||
if (result is List) {
|
if (result is List) {
|
||||||
return result.map((dynamic val) {
|
return result.map((dynamic val) {
|
||||||
|
@ -112,24 +138,91 @@ class ElectrumClient {
|
||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Map<String, Object>();
|
return <String, Object>{};
|
||||||
}).toList();
|
}).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
return [];
|
return [];
|
||||||
});
|
});
|
||||||
|
|
||||||
Future<String> getTransactionRaw({@required String hash}) async =>
|
Future<List<Map<String, dynamic>>> getListUnspentWithAddress(
|
||||||
call(method: 'blockchain.transaction.get', params: [hash])
|
String address) =>
|
||||||
|
call(
|
||||||
|
method: 'blockchain.scripthash.listunspent',
|
||||||
|
params: [scriptHash(address)]).then((dynamic result) {
|
||||||
|
if (result is List) {
|
||||||
|
return result.map((dynamic val) {
|
||||||
|
if (val is Map<String, Object>) {
|
||||||
|
val['address'] = address;
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <String, Object>{};
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<List<Map<String, dynamic>>> getListUnspent(String scriptHash) =>
|
||||||
|
call(method: 'blockchain.scripthash.listunspent', params: [scriptHash])
|
||||||
.then((dynamic result) {
|
.then((dynamic result) {
|
||||||
if (result is String) {
|
if (result is List) {
|
||||||
|
return result.map((dynamic val) {
|
||||||
|
if (val is Map<String, Object>) {
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <String, Object>{};
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<List<Map<String, dynamic>>> getMempool(String scriptHash) =>
|
||||||
|
call(method: 'blockchain.scripthash.get_mempool', params: [scriptHash])
|
||||||
|
.then((dynamic result) {
|
||||||
|
if (result is List) {
|
||||||
|
return result.map((dynamic val) {
|
||||||
|
if (val is Map<String, Object>) {
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <String, Object>{};
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<Map<String, Object>> getTransactionRaw(
|
||||||
|
{@required String hash}) async =>
|
||||||
|
call(method: 'blockchain.transaction.get', params: [hash, true])
|
||||||
|
.then((dynamic result) {
|
||||||
|
if (result is Map<String, Object>) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
return '';
|
return <String, Object>{};
|
||||||
});
|
});
|
||||||
|
|
||||||
Future<String> broadcastTransaction({@required String transactionRaw}) async =>
|
Future<Map<String, Object>> getTransactionExpanded(
|
||||||
|
{@required String hash}) async {
|
||||||
|
final originalTx = await getTransactionRaw(hash: hash);
|
||||||
|
final vins = originalTx['vin'] as List<Object>;
|
||||||
|
|
||||||
|
for (dynamic vin in vins) {
|
||||||
|
if (vin is Map<String, Object>) {
|
||||||
|
vin['tx'] = await getTransactionRaw(hash: vin['txid'] as String);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return originalTx;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> broadcastTransaction(
|
||||||
|
{@required String transactionRaw}) async =>
|
||||||
call(method: 'blockchain.transaction.broadcast', params: [transactionRaw])
|
call(method: 'blockchain.transaction.broadcast', params: [transactionRaw])
|
||||||
.then((dynamic result) {
|
.then((dynamic result) {
|
||||||
if (result is String) {
|
if (result is String) {
|
||||||
|
@ -163,11 +256,11 @@ class ElectrumClient {
|
||||||
return 0;
|
return 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
BehaviorSubject<Object> addressUpdate({@required String address}) =>
|
BehaviorSubject<Object> scripthashUpdate(String scripthash) =>
|
||||||
subscribe<Object>(
|
subscribe<Object>(
|
||||||
id: 'blockchain.address.subscribe:$address',
|
id: 'blockchain.scripthash.subscribe:$scripthash',
|
||||||
method: 'blockchain.address.subscribe',
|
method: 'blockchain.scripthash.subscribe',
|
||||||
params: [address]);
|
params: [scripthash]);
|
||||||
|
|
||||||
BehaviorSubject<T> subscribe<T>(
|
BehaviorSubject<T> subscribe<T>(
|
||||||
{@required String id,
|
{@required String id,
|
||||||
|
@ -190,6 +283,25 @@ class ElectrumClient {
|
||||||
return completer.future;
|
return completer.future;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<dynamic> callWithTimeout(
|
||||||
|
{String method,
|
||||||
|
List<Object> params = const [],
|
||||||
|
int timeout = 2000}) async {
|
||||||
|
final completer = Completer<dynamic>();
|
||||||
|
_id += 1;
|
||||||
|
final id = _id;
|
||||||
|
_regisryTask(id, completer);
|
||||||
|
socket.write(jsonrpc(method: method, id: _id, params: params));
|
||||||
|
|
||||||
|
Timer(Duration(milliseconds: timeout), () {
|
||||||
|
if (!completer.isCompleted) {
|
||||||
|
completer.completeError(RequestFailedTimeoutException(method, _id));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return completer.future;
|
||||||
|
}
|
||||||
|
|
||||||
void request({String method, List<Object> params = const []}) {
|
void request({String method, List<Object> params = const []}) {
|
||||||
_id += 1;
|
_id += 1;
|
||||||
socket.write(jsonrpc(method: method, id: _id, params: params));
|
socket.write(jsonrpc(method: method, id: _id, params: params));
|
||||||
|
@ -206,7 +318,9 @@ class ElectrumClient {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!(_tasks[id]?.completer?.isCompleted ?? false)) {
|
||||||
_tasks[id]?.completer?.complete(data);
|
_tasks[id]?.completer?.complete(data);
|
||||||
|
}
|
||||||
|
|
||||||
if (!(_tasks[id]?.isSubscription ?? false)) {
|
if (!(_tasks[id]?.isSubscription ?? false)) {
|
||||||
_tasks[id] = null;
|
_tasks[id] = null;
|
||||||
|
@ -218,18 +332,30 @@ class ElectrumClient {
|
||||||
void _methodHandler(
|
void _methodHandler(
|
||||||
{@required String method, @required Map<String, Object> request}) {
|
{@required String method, @required Map<String, Object> request}) {
|
||||||
switch (method) {
|
switch (method) {
|
||||||
case 'blockchain.address.subscribe':
|
case 'blockchain.scripthash.subscribe':
|
||||||
final params = request['params'] as List<dynamic>;
|
final params = request['params'] as List<dynamic>;
|
||||||
final address = params.first as String;
|
final scripthash = params.first as String;
|
||||||
final id = 'blockchain.address.subscribe:$address';
|
final id = 'blockchain.scripthash.subscribe:$scripthash';
|
||||||
|
|
||||||
if (_tasks[id] != null) {
|
|
||||||
_tasks[id].subject.add(params.last);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
_tasks[id]?.subject?.add(params.last);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _setIsConnected(bool isConnected) {
|
||||||
|
if (_isConnected != isConnected) {
|
||||||
|
onConnectionStatusChange?.call(isConnected);
|
||||||
|
}
|
||||||
|
|
||||||
|
_isConnected = isConnected;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RequestFailedTimeoutException implements Exception {
|
||||||
|
RequestFailedTimeoutException(this.method, this.id);
|
||||||
|
|
||||||
|
final String method;
|
||||||
|
final int id;
|
||||||
}
|
}
|
||||||
|
|
47
lib/bitcoin/pending_bitcoin_transaction.dart
Normal file
47
lib/bitcoin/pending_bitcoin_transaction.dart
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart';
|
||||||
|
import 'package:cake_wallet/bitcoin/bitcoin_transaction_info.dart';
|
||||||
|
import 'package:cake_wallet/src/domain/common/transaction_direction.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
||||||
|
import 'package:cake_wallet/core/pending_transaction.dart';
|
||||||
|
import 'package:cake_wallet/bitcoin/electrum.dart';
|
||||||
|
|
||||||
|
class PendingBitcoinTransaction with PendingTransaction {
|
||||||
|
PendingBitcoinTransaction(this._tx,
|
||||||
|
{@required this.eclient, @required this.amount, @required this.fee})
|
||||||
|
: _listeners = <void Function(BitcoinTransactionInfo transaction)>[];
|
||||||
|
|
||||||
|
final bitcoin.Transaction _tx;
|
||||||
|
final ElectrumClient eclient;
|
||||||
|
final int amount;
|
||||||
|
final int fee;
|
||||||
|
|
||||||
|
String get id => _tx.getId();
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get amountFormatted => bitcoinAmountToString(amount: amount);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get feeFormatted => bitcoinAmountToString(amount: fee);
|
||||||
|
|
||||||
|
final List<void Function(BitcoinTransactionInfo transaction)> _listeners;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> commit() async {
|
||||||
|
await eclient.broadcastTransaction(transactionRaw: _tx.toHex());
|
||||||
|
_listeners?.forEach((listener) => listener(transactionInfo()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void addListener(
|
||||||
|
void Function(BitcoinTransactionInfo transaction) listener) =>
|
||||||
|
_listeners.add(listener);
|
||||||
|
|
||||||
|
BitcoinTransactionInfo transactionInfo() => BitcoinTransactionInfo(
|
||||||
|
id: id,
|
||||||
|
height: 0,
|
||||||
|
amount: amount,
|
||||||
|
direction: TransactionDirection.outgoing,
|
||||||
|
date: DateTime.now(),
|
||||||
|
isPending: true,
|
||||||
|
confirmations: 0);
|
||||||
|
}
|
18
lib/bitcoin/script_hash.dart
Normal file
18
lib/bitcoin/script_hash.dart
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
||||||
|
import 'package:crypto/crypto.dart';
|
||||||
|
|
||||||
|
String scriptHash(String address) {
|
||||||
|
final outputScript = bitcoin.Address.addressToOutputScript(address);
|
||||||
|
final splitted = sha256.convert(outputScript).toString().split('');
|
||||||
|
var res = '';
|
||||||
|
|
||||||
|
for (var i = splitted.length - 1; i >= 0; i--) {
|
||||||
|
final char = splitted[i];
|
||||||
|
i--;
|
||||||
|
final nextChar = splitted[i];
|
||||||
|
res += nextChar;
|
||||||
|
res += char;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
26
lib/bitcoin/utils.dart
Normal file
26
lib/bitcoin/utils.dart
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
import 'dart:typed_data';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
||||||
|
import 'package:bitcoin_flutter/src/payments/index.dart' show PaymentData;
|
||||||
|
import 'package:hex/hex.dart';
|
||||||
|
|
||||||
|
bitcoin.PaymentData generatePaymentData(
|
||||||
|
{@required bitcoin.HDWallet hd, @required int index}) =>
|
||||||
|
PaymentData(
|
||||||
|
pubkey: Uint8List.fromList(HEX.decode(hd.derive(index).pubKey)));
|
||||||
|
|
||||||
|
bitcoin.ECPair generateKeyPair(
|
||||||
|
{@required bitcoin.HDWallet hd,
|
||||||
|
@required int index,
|
||||||
|
bitcoin.NetworkType network}) =>
|
||||||
|
bitcoin.ECPair.fromWIF(hd.derive(index).wif,
|
||||||
|
network: network ?? bitcoin.bitcoin);
|
||||||
|
|
||||||
|
String generateAddress({@required bitcoin.HDWallet hd, @required int index}) =>
|
||||||
|
bitcoin
|
||||||
|
.P2WPKH(
|
||||||
|
data: PaymentData(
|
||||||
|
pubkey:
|
||||||
|
Uint8List.fromList(HEX.decode(hd.derive(index).pubKey))))
|
||||||
|
.data
|
||||||
|
.address;
|
|
@ -14,10 +14,10 @@ class AmountValidator extends TextValidator {
|
||||||
static String _pattern(WalletType type) {
|
static String _pattern(WalletType type) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case WalletType.monero:
|
case WalletType.monero:
|
||||||
return '^([0-9]+([.][0-9]{0,12})?|[.][0-9]{1,12}|ALL)\$';
|
return '^([0-9]+([.\,][0-9]{0,12})?|[.\,][0-9]{1,12})\$';
|
||||||
case WalletType.bitcoin:
|
case WalletType.bitcoin:
|
||||||
// FIXME: Incorrect pattern for bitcoin
|
// FIXME: Incorrect pattern for bitcoin
|
||||||
return '^([0-9]+([.][0-9]{0,12})?|[.][0-9]{1,12}|ALL)\$';
|
return '^([0-9]+([.\,][0-9]{0,12})?|[.\,][0-9]{1,12})\$';
|
||||||
default:
|
default:
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ class ContactService {
|
||||||
|
|
||||||
Future add(Contact contact) async {
|
Future add(Contact contact) async {
|
||||||
await contactSource.add(contact);
|
await contactSource.add(contact);
|
||||||
contactListStore.contacts.add(contact);
|
// contactListStore.contacts.add(contact);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future update(Contact contact) async {
|
Future update(Contact contact) async {
|
||||||
|
|
6
lib/core/pending_transaction.dart
Normal file
6
lib/core/pending_transaction.dart
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
mixin PendingTransaction {
|
||||||
|
String get amountFormatted;
|
||||||
|
String get feeFormatted;
|
||||||
|
|
||||||
|
Future<void> commit();
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
import 'package:cake_wallet/src/domain/common/transaction_info.dart';
|
import 'package:cake_wallet/src/domain/common/transaction_info.dart';
|
||||||
|
|
||||||
|
@ -5,7 +6,7 @@ abstract class TransactionHistoryBase<TransactionType extends TransactionInfo> {
|
||||||
TransactionHistoryBase() : _isUpdating = false;
|
TransactionHistoryBase() : _isUpdating = false;
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
ObservableList<TransactionType> transactions;
|
ObservableMap<String, TransactionType> transactions;
|
||||||
|
|
||||||
bool _isUpdating;
|
bool _isUpdating;
|
||||||
|
|
||||||
|
@ -24,5 +25,15 @@ abstract class TransactionHistoryBase<TransactionType extends TransactionInfo> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<TransactionType>> fetchTransactions();
|
void updateAsync({void Function() onFinished}) {
|
||||||
|
fetchTransactionsAsync(
|
||||||
|
(transaction) => transactions[transaction.id] = transaction,
|
||||||
|
onFinished: onFinished);
|
||||||
|
}
|
||||||
|
|
||||||
|
void fetchTransactionsAsync(
|
||||||
|
void Function(TransactionType transaction) onTransactionLoaded,
|
||||||
|
{void Function() onFinished});
|
||||||
|
|
||||||
|
Future<Map<String, TransactionType>> fetchTransactions();
|
||||||
}
|
}
|
|
@ -1,5 +1,7 @@
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:cake_wallet/core/pending_transaction.dart';
|
||||||
import 'package:cake_wallet/core/transaction_history.dart';
|
import 'package:cake_wallet/core/transaction_history.dart';
|
||||||
|
import 'package:cake_wallet/src/domain/common/transaction_priority.dart';
|
||||||
import 'package:cake_wallet/src/domain/common/crypto_currency.dart';
|
import 'package:cake_wallet/src/domain/common/crypto_currency.dart';
|
||||||
import 'package:cake_wallet/src/domain/common/sync_status.dart';
|
import 'package:cake_wallet/src/domain/common/sync_status.dart';
|
||||||
import 'package:cake_wallet/src/domain/common/node.dart';
|
import 'package:cake_wallet/src/domain/common/node.dart';
|
||||||
|
@ -30,7 +32,9 @@ abstract class WalletBase<BalaceType> {
|
||||||
|
|
||||||
Future<void> startSync();
|
Future<void> startSync();
|
||||||
|
|
||||||
Future<void> createTransaction(Object credentials);
|
Future<PendingTransaction> createTransaction(Object credentials);
|
||||||
|
|
||||||
|
double calculateEstimatedFee(TransactionPriority priority);
|
||||||
|
|
||||||
Future<void> save();
|
Future<void> save();
|
||||||
}
|
}
|
||||||
|
|
111
lib/di.dart
111
lib/di.dart
|
@ -31,6 +31,7 @@ import 'package:cake_wallet/src/screens/subaddress/address_edit_or_create_page.d
|
||||||
import 'package:cake_wallet/src/screens/wallet_list/wallet_list_page.dart';
|
import 'package:cake_wallet/src/screens/wallet_list/wallet_list_page.dart';
|
||||||
import 'package:cake_wallet/store/theme_changer_store.dart';
|
import 'package:cake_wallet/store/theme_changer_store.dart';
|
||||||
import 'package:cake_wallet/store/wallet_list_store.dart';
|
import 'package:cake_wallet/store/wallet_list_store.dart';
|
||||||
|
import 'package:cake_wallet/utils/mobx.dart';
|
||||||
import 'package:cake_wallet/theme_changer.dart';
|
import 'package:cake_wallet/theme_changer.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/view_model/contact_list/contact_view_model.dart';
|
import 'package:cake_wallet/view_model/contact_list/contact_view_model.dart';
|
||||||
|
@ -44,7 +45,7 @@ import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart';
|
||||||
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart';
|
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart';
|
||||||
import 'package:cake_wallet/view_model/monero_account_list/monero_account_edit_or_create_view_model.dart';
|
import 'package:cake_wallet/view_model/monero_account_list/monero_account_edit_or_create_view_model.dart';
|
||||||
import 'package:cake_wallet/view_model/monero_account_list/monero_account_list_view_model.dart';
|
import 'package:cake_wallet/view_model/monero_account_list/monero_account_list_view_model.dart';
|
||||||
import 'package:cake_wallet/view_model/send_view_model.dart';
|
import 'package:cake_wallet/view_model/send/send_view_model.dart';
|
||||||
import 'package:cake_wallet/view_model/settings/settings_view_model.dart';
|
import 'package:cake_wallet/view_model/settings/settings_view_model.dart';
|
||||||
import 'package:cake_wallet/view_model/wallet_keys_view_model.dart';
|
import 'package:cake_wallet/view_model/wallet_keys_view_model.dart';
|
||||||
import 'package:cake_wallet/view_model/wallet_list/wallet_list_view_model.dart';
|
import 'package:cake_wallet/view_model/wallet_list/wallet_list_view_model.dart';
|
||||||
|
@ -87,8 +88,20 @@ NodeListStore setupNodeListStore(Box<Node> nodeSource) {
|
||||||
_nodeListStore = NodeListStore();
|
_nodeListStore = NodeListStore();
|
||||||
_nodeListStore.replaceValues(nodeSource.values);
|
_nodeListStore.replaceValues(nodeSource.values);
|
||||||
_onNodesSourceChange = nodeSource.watch();
|
_onNodesSourceChange = nodeSource.watch();
|
||||||
_onNodesSourceChange
|
_onNodesSourceChange.listen((event) {
|
||||||
.listen((_) => _nodeListStore.replaceValues(nodeSource.values));
|
// print(event);
|
||||||
|
|
||||||
|
if (event.deleted) {
|
||||||
|
_nodeListStore.nodes.removeWhere((n) {
|
||||||
|
return n.key != null ? n.key == event.key : true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.value is Node) {
|
||||||
|
final val = event.value as Node;
|
||||||
|
_nodeListStore.nodes.add(val);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return _nodeListStore;
|
return _nodeListStore;
|
||||||
}
|
}
|
||||||
|
@ -120,8 +133,7 @@ Future setup(
|
||||||
getIt.registerSingleton<ContactService>(
|
getIt.registerSingleton<ContactService>(
|
||||||
ContactService(contactSource, getIt.get<AppStore>().contactListStore));
|
ContactService(contactSource, getIt.get<AppStore>().contactListStore));
|
||||||
getIt.registerSingleton<TradesStore>(TradesStore(
|
getIt.registerSingleton<TradesStore>(TradesStore(
|
||||||
tradesSource: tradesSource,
|
tradesSource: tradesSource, settingsStore: getIt.get<SettingsStore>()));
|
||||||
settingsStore: getIt.get<SettingsStore>()));
|
|
||||||
getIt.registerSingleton<TradeFilterStore>(TradeFilterStore());
|
getIt.registerSingleton<TradeFilterStore>(TradeFilterStore());
|
||||||
getIt.registerSingleton<TransactionFilterStore>(TransactionFilterStore());
|
getIt.registerSingleton<TransactionFilterStore>(TransactionFilterStore());
|
||||||
getIt.registerSingleton<FiatConvertationStore>(FiatConvertationStore());
|
getIt.registerSingleton<FiatConvertationStore>(FiatConvertationStore());
|
||||||
|
@ -170,20 +182,17 @@ Future setup(
|
||||||
getIt.registerFactory<WalletAddressListViewModel>(
|
getIt.registerFactory<WalletAddressListViewModel>(
|
||||||
() => WalletAddressListViewModel(wallet: getIt.get<AppStore>().wallet));
|
() => WalletAddressListViewModel(wallet: getIt.get<AppStore>().wallet));
|
||||||
|
|
||||||
getIt.registerFactory(
|
getIt.registerFactory(() => BalanceViewModel(
|
||||||
() => BalanceViewModel(
|
|
||||||
wallet: getIt.get<AppStore>().wallet,
|
wallet: getIt.get<AppStore>().wallet,
|
||||||
settingsStore: getIt.get<SettingsStore>(),
|
settingsStore: getIt.get<SettingsStore>(),
|
||||||
fiatConvertationStore: getIt.get<FiatConvertationStore>()));
|
fiatConvertationStore: getIt.get<FiatConvertationStore>()));
|
||||||
|
|
||||||
getIt.registerFactory(
|
getIt.registerFactory(() => DashboardViewModel(
|
||||||
() => DashboardViewModel(
|
|
||||||
balanceViewModel: getIt.get<BalanceViewModel>(),
|
balanceViewModel: getIt.get<BalanceViewModel>(),
|
||||||
appStore: getIt.get<AppStore>(),
|
appStore: getIt.get<AppStore>(),
|
||||||
tradesStore: getIt.get<TradesStore>(),
|
tradesStore: getIt.get<TradesStore>(),
|
||||||
tradeFilterStore: getIt.get<TradeFilterStore>(),
|
tradeFilterStore: getIt.get<TradeFilterStore>(),
|
||||||
transactionFilterStore: getIt.get<TransactionFilterStore>()
|
transactionFilterStore: getIt.get<TransactionFilterStore>()));
|
||||||
));
|
|
||||||
|
|
||||||
getIt.registerFactory<AuthService>(() => AuthService(
|
getIt.registerFactory<AuthService>(() => AuthService(
|
||||||
secureStorage: getIt.get<FlutterSecureStorage>(),
|
secureStorage: getIt.get<FlutterSecureStorage>(),
|
||||||
|
@ -195,6 +204,10 @@ Future setup(
|
||||||
|
|
||||||
getIt.registerFactory<AuthPage>(
|
getIt.registerFactory<AuthPage>(
|
||||||
() => AuthPage(
|
() => AuthPage(
|
||||||
|
allowBiometricalAuthentication: getIt
|
||||||
|
.get<AppStore>()
|
||||||
|
.settingsStore
|
||||||
|
.allowBiometricalAuthentication,
|
||||||
authViewModel: getIt.get<AuthViewModel>(),
|
authViewModel: getIt.get<AuthViewModel>(),
|
||||||
onAuthenticationFinished: (isAuthenticated, __) {
|
onAuthenticationFinished: (isAuthenticated, __) {
|
||||||
if (isAuthenticated) {
|
if (isAuthenticated) {
|
||||||
|
@ -206,13 +219,20 @@ Future setup(
|
||||||
|
|
||||||
getIt
|
getIt
|
||||||
.registerFactoryParam<AuthPage, void Function(bool, AuthPageState), void>(
|
.registerFactoryParam<AuthPage, void Function(bool, AuthPageState), void>(
|
||||||
(onAuthFinished, _) => AuthPage(
|
(onAuthFinished, _) {
|
||||||
|
final allowBiometricalAuthentication =
|
||||||
|
getIt.get<AppStore>().settingsStore.allowBiometricalAuthentication;
|
||||||
|
|
||||||
|
print('allowBiometricalAuthentication $allowBiometricalAuthentication');
|
||||||
|
|
||||||
|
return AuthPage(
|
||||||
|
allowBiometricalAuthentication: allowBiometricalAuthentication,
|
||||||
authViewModel: getIt.get<AuthViewModel>(),
|
authViewModel: getIt.get<AuthViewModel>(),
|
||||||
onAuthenticationFinished: onAuthFinished,
|
onAuthenticationFinished: onAuthFinished,
|
||||||
closable: false));
|
closable: false);
|
||||||
|
});
|
||||||
|
|
||||||
getIt.registerFactory<DashboardPage>(
|
getIt.registerFactory<DashboardPage>(() => DashboardPage(
|
||||||
() => DashboardPage(
|
|
||||||
walletViewModel: getIt.get<DashboardViewModel>(),
|
walletViewModel: getIt.get<DashboardViewModel>(),
|
||||||
addressListViewModel: getIt.get<WalletAddressListViewModel>()));
|
addressListViewModel: getIt.get<WalletAddressListViewModel>()));
|
||||||
|
|
||||||
|
@ -228,17 +248,17 @@ Future setup(
|
||||||
addressEditOrCreateViewModel:
|
addressEditOrCreateViewModel:
|
||||||
getIt.get<WalletAddressEditOrCreateViewModel>(param1: item)));
|
getIt.get<WalletAddressEditOrCreateViewModel>(param1: item)));
|
||||||
|
|
||||||
|
// getIt.get<SendTemplateStore>()
|
||||||
getIt.registerFactory<SendViewModel>(() => SendViewModel(
|
getIt.registerFactory<SendViewModel>(() => SendViewModel(
|
||||||
getIt.get<AppStore>().wallet,
|
getIt.get<AppStore>().wallet,
|
||||||
getIt.get<AppStore>().settingsStore,
|
getIt.get<AppStore>().settingsStore,
|
||||||
getIt.get<FiatConvertationStore>(),
|
getIt.get<FiatConvertationStore>()));
|
||||||
getIt.get<SendTemplateStore>()));
|
|
||||||
|
|
||||||
getIt.registerFactory(
|
getIt.registerFactory(
|
||||||
() => SendPage(sendViewModel: getIt.get<SendViewModel>()));
|
() => SendPage(sendViewModel: getIt.get<SendViewModel>()));
|
||||||
|
|
||||||
getIt.registerFactory(
|
// getIt.registerFactory(
|
||||||
() => SendTemplatePage(sendViewModel: getIt.get<SendViewModel>()));
|
// () => SendTemplatePage(sendViewModel: getIt.get<SendViewModel>()));
|
||||||
|
|
||||||
getIt.registerFactory(() => WalletListViewModel(
|
getIt.registerFactory(() => WalletListViewModel(
|
||||||
walletInfoSource, getIt.get<AppStore>(), getIt.get<KeyService>()));
|
walletInfoSource, getIt.get<AppStore>(), getIt.get<KeyService>()));
|
||||||
|
@ -275,8 +295,10 @@ Future setup(
|
||||||
moneroAccountCreationViewModel:
|
moneroAccountCreationViewModel:
|
||||||
getIt.get<MoneroAccountEditOrCreateViewModel>()));
|
getIt.get<MoneroAccountEditOrCreateViewModel>()));
|
||||||
|
|
||||||
getIt.registerFactory(
|
getIt.registerFactory(() {
|
||||||
() => SettingsViewModel(getIt.get<AppStore>().settingsStore));
|
final appStore = getIt.get<AppStore>();
|
||||||
|
return SettingsViewModel(appStore.settingsStore, appStore.wallet);
|
||||||
|
});
|
||||||
|
|
||||||
getIt.registerFactory(() => SettingsPage(getIt.get<SettingsViewModel>()));
|
getIt.registerFactory(() => SettingsPage(getIt.get<SettingsViewModel>()));
|
||||||
|
|
||||||
|
@ -295,22 +317,26 @@ Future setup(
|
||||||
|
|
||||||
getIt.registerFactoryParam<ContactViewModel, Contact, void>(
|
getIt.registerFactoryParam<ContactViewModel, Contact, void>(
|
||||||
(Contact contact, _) => ContactViewModel(
|
(Contact contact, _) => ContactViewModel(
|
||||||
getIt.get<ContactService>(), getIt.get<AppStore>().wallet,
|
contactSource, getIt.get<AppStore>().wallet,
|
||||||
contact: contact));
|
contact: contact));
|
||||||
|
|
||||||
getIt.registerFactory(() => ContactListViewModel(
|
getIt.registerFactory(() => ContactListViewModel(
|
||||||
getIt.get<AppStore>().contactListStore, getIt.get<ContactService>()));
|
getIt.get<AppStore>().contactListStore,
|
||||||
|
getIt.get<ContactService>(),
|
||||||
|
contactSource));
|
||||||
|
|
||||||
getIt.registerFactoryParam<ContactListPage, bool, void>((bool isEditable, _) =>
|
getIt.registerFactoryParam<ContactListPage, bool, void>(
|
||||||
ContactListPage(getIt.get<ContactListViewModel>(), isEditable: isEditable));
|
(bool isEditable, _) => ContactListPage(getIt.get<ContactListViewModel>(),
|
||||||
|
isEditable: isEditable));
|
||||||
|
|
||||||
getIt.registerFactoryParam<ContactPage, Contact, void>((Contact contact, _) =>
|
getIt.registerFactoryParam<ContactPage, Contact, void>((Contact contact, _) =>
|
||||||
ContactPage(getIt.get<ContactViewModel>(param1: contact)));
|
ContactPage(getIt.get<ContactViewModel>(param1: contact)));
|
||||||
|
|
||||||
getIt.registerFactory(() => NodeListViewModel(
|
getIt.registerFactory(() {
|
||||||
getIt.get<AppStore>().nodeListStore,
|
final appStore = getIt.get<AppStore>();
|
||||||
nodeSource,
|
return NodeListViewModel(appStore.nodeListStore, nodeSource,
|
||||||
getIt.get<AppStore>().wallet));
|
appStore.wallet, appStore.settingsStore);
|
||||||
|
});
|
||||||
|
|
||||||
getIt.registerFactory(() => NodeListPage(getIt.get<NodeListViewModel>()));
|
getIt.registerFactory(() => NodeListPage(getIt.get<NodeListViewModel>()));
|
||||||
|
|
||||||
|
@ -320,32 +346,27 @@ Future setup(
|
||||||
getIt.registerFactory(
|
getIt.registerFactory(
|
||||||
() => NodeCreateOrEditPage(getIt.get<NodeCreateOrEditViewModel>()));
|
() => NodeCreateOrEditPage(getIt.get<NodeCreateOrEditViewModel>()));
|
||||||
|
|
||||||
getIt.registerFactory(() =>
|
getIt.registerFactory(() => ExchangeViewModel(
|
||||||
ExchangeViewModel(
|
|
||||||
wallet: getIt.get<AppStore>().wallet,
|
wallet: getIt.get<AppStore>().wallet,
|
||||||
exchangeTemplateStore: getIt.get<ExchangeTemplateStore>(),
|
exchangeTemplateStore: getIt.get<ExchangeTemplateStore>(),
|
||||||
trades: tradesSource,
|
trades: tradesSource,
|
||||||
tradesStore: getIt.get<TradesStore>()
|
tradesStore: getIt.get<TradesStore>()));
|
||||||
));
|
|
||||||
|
|
||||||
getIt.registerFactory(() =>
|
getIt.registerFactory(() => ExchangeTradeViewModel(
|
||||||
ExchangeTradeViewModel(
|
|
||||||
wallet: getIt.get<AppStore>().wallet,
|
wallet: getIt.get<AppStore>().wallet,
|
||||||
trades: tradesSource,
|
trades: tradesSource,
|
||||||
tradesStore: getIt.get<TradesStore>()
|
tradesStore: getIt.get<TradesStore>()));
|
||||||
));
|
|
||||||
|
|
||||||
getIt.registerFactory(() =>
|
getIt.registerFactory(() => ExchangePage(getIt.get<ExchangeViewModel>()));
|
||||||
ExchangePage(getIt.get<ExchangeViewModel>()));
|
|
||||||
|
|
||||||
getIt.registerFactory(() =>
|
getIt.registerFactory(
|
||||||
ExchangeConfirmPage(tradesStore: getIt.get<TradesStore>()));
|
() => ExchangeConfirmPage(tradesStore: getIt.get<TradesStore>()));
|
||||||
|
|
||||||
getIt.registerFactory(() =>
|
getIt.registerFactory(() => ExchangeTradePage(
|
||||||
ExchangeTradePage(exchangeTradeViewModel: getIt.get<ExchangeTradeViewModel>()));
|
exchangeTradeViewModel: getIt.get<ExchangeTradeViewModel>()));
|
||||||
|
|
||||||
getIt.registerFactory(() =>
|
getIt.registerFactory(
|
||||||
ExchangeTemplatePage(getIt.get<ExchangeViewModel>()));
|
() => ExchangeTemplatePage(getIt.get<ExchangeViewModel>()));
|
||||||
}
|
}
|
||||||
|
|
||||||
void setupThemeChangerStore(ThemeChanger themeChanger) {
|
void setupThemeChangerStore(ThemeChanger themeChanger) {
|
||||||
|
|
|
@ -132,7 +132,7 @@ void main() async {
|
||||||
fiatConvertationService: fiatConvertationService,
|
fiatConvertationService: fiatConvertationService,
|
||||||
templates: templates,
|
templates: templates,
|
||||||
exchangeTemplates: exchangeTemplates,
|
exchangeTemplates: exchangeTemplates,
|
||||||
initialMigrationVersion: 3);
|
initialMigrationVersion: 4);
|
||||||
|
|
||||||
setReactions(
|
setReactions(
|
||||||
settingsStore: settingsStore,
|
settingsStore: settingsStore,
|
||||||
|
@ -176,7 +176,7 @@ Future<void> initialSetup(
|
||||||
@required FiatConvertationService fiatConvertationService,
|
@required FiatConvertationService fiatConvertationService,
|
||||||
@required Box<Template> templates,
|
@required Box<Template> templates,
|
||||||
@required Box<ExchangeTemplate> exchangeTemplates,
|
@required Box<ExchangeTemplate> exchangeTemplates,
|
||||||
int initialMigrationVersion = 3}) async {
|
int initialMigrationVersion = 4}) async {
|
||||||
await defaultSettingsMigration(
|
await defaultSettingsMigration(
|
||||||
version: initialMigrationVersion,
|
version: initialMigrationVersion,
|
||||||
sharedPreferences: sharedPreferences,
|
sharedPreferences: sharedPreferences,
|
||||||
|
|
|
@ -20,12 +20,29 @@ class MoneroTransactionHistory = MoneroTransactionHistoryBase
|
||||||
abstract class MoneroTransactionHistoryBase
|
abstract class MoneroTransactionHistoryBase
|
||||||
extends TransactionHistoryBase<MoneroTransactionInfo> with Store {
|
extends TransactionHistoryBase<MoneroTransactionInfo> with Store {
|
||||||
MoneroTransactionHistoryBase() {
|
MoneroTransactionHistoryBase() {
|
||||||
transactions = ObservableList<MoneroTransactionInfo>();
|
transactions = ObservableMap<String, MoneroTransactionInfo>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<MoneroTransactionInfo>> fetchTransactions() async {
|
Future<Map<String, MoneroTransactionInfo>> fetchTransactions() async {
|
||||||
monero_transaction_history.refreshTransactions();
|
monero_transaction_history.refreshTransactions();
|
||||||
return _getAllTransactions(null);
|
return _getAllTransactions(null).fold<Map<String, MoneroTransactionInfo>>(
|
||||||
|
<String, MoneroTransactionInfo>{},
|
||||||
|
(Map<String, MoneroTransactionInfo> acc, MoneroTransactionInfo tx) {
|
||||||
|
acc[tx.id] = tx;
|
||||||
|
return acc;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void updateAsync({void Function() onFinished}) {
|
||||||
|
fetchTransactionsAsync(
|
||||||
|
(transaction) => transactions[transaction.id] = transaction,
|
||||||
|
onFinished: onFinished);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void fetchTransactionsAsync(
|
||||||
|
void Function(MoneroTransactionInfo transaction) onTransactionLoaded,
|
||||||
|
{void Function() onFinished}) {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:cake_wallet/src/domain/monero/monero_transaction_creation_credentials.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
import 'package:cw_monero/wallet.dart';
|
import 'package:cw_monero/wallet.dart';
|
||||||
|
@ -16,6 +17,10 @@ import 'package:cake_wallet/src/domain/monero/account.dart';
|
||||||
import 'package:cake_wallet/src/domain/monero/account_list.dart';
|
import 'package:cake_wallet/src/domain/monero/account_list.dart';
|
||||||
import 'package:cake_wallet/src/domain/monero/subaddress.dart';
|
import 'package:cake_wallet/src/domain/monero/subaddress.dart';
|
||||||
import 'package:cake_wallet/src/domain/common/node.dart';
|
import 'package:cake_wallet/src/domain/common/node.dart';
|
||||||
|
import 'package:cake_wallet/core/pending_transaction.dart';
|
||||||
|
import 'package:cake_wallet/src/domain/common/transaction_priority.dart';
|
||||||
|
import 'package:cake_wallet/src/domain/common/calculate_fiat_amount.dart'
|
||||||
|
as cfa;
|
||||||
|
|
||||||
part 'monero_wallet.g.dart';
|
part 'monero_wallet.g.dart';
|
||||||
|
|
||||||
|
@ -133,17 +138,44 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance> with Store {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> createTransaction(Object credentials) async {
|
Future<PendingTransaction> createTransaction(Object credentials) async {
|
||||||
// final _credentials = credentials as MoneroTransactionCreationCredentials;
|
final _credentials = credentials as MoneroTransactionCreationCredentials;
|
||||||
// final transactionDescription = await transaction_history.createTransaction(
|
// final transactionDescription = await transaction_history.createTransaction(
|
||||||
// address: _credentials.address,
|
// address: _credentials.address,
|
||||||
// paymentId: _credentials.paymentId,
|
// paymentId: _credentials.paymentId,
|
||||||
// amount: _credentials.amount,
|
// amount: _credentials.amount,
|
||||||
// priorityRaw: _credentials.priority.serialize(),
|
// priorityRaw: _credentials.priority.serialize(),
|
||||||
// accountIndex: _account.value.id);
|
// accountIndex: _account.value.id);
|
||||||
//
|
|
||||||
// return PendingTransaction.fromTransactionDescription(
|
// return PendingTransaction.fromTransactionDescription(
|
||||||
// transactionDescription);
|
// transactionDescription);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
double calculateEstimatedFee(TransactionPriority priority) {
|
||||||
|
// FIXME: hardcoded value;
|
||||||
|
|
||||||
|
if (priority == TransactionPriority.slow) {
|
||||||
|
return 0.00002459;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (priority == TransactionPriority.regular) {
|
||||||
|
return 0.00012305;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (priority == TransactionPriority.medium) {
|
||||||
|
return 0.00024503;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (priority == TransactionPriority.fast) {
|
||||||
|
return 0.00061453;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (priority == TransactionPriority.fastest) {
|
||||||
|
return 0.0260216;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:cake_wallet/monero/monero_wallet.dart';
|
import 'package:cake_wallet/monero/monero_wallet.dart';
|
||||||
import 'package:cake_wallet/core/wallet_credentials.dart';
|
import 'package:cake_wallet/core/wallet_credentials.dart';
|
||||||
import 'package:cake_wallet/core/wallet_service.dart';
|
import 'package:cake_wallet/core/wallet_service.dart';
|
||||||
|
@ -60,7 +62,7 @@ class MoneroWalletService extends WalletService<
|
||||||
|
|
||||||
return wallet;
|
return wallet;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// TODO: Implement Exception fop wallet list service.
|
// TODO: Implement Exception for wallet list service.
|
||||||
print('MoneroWalletsManager Error: $e');
|
print('MoneroWalletsManager Error: $e');
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
|
@ -72,7 +74,7 @@ class MoneroWalletService extends WalletService<
|
||||||
final path = await pathForWallet(name: name, type: WalletType.monero);
|
final path = await pathForWallet(name: name, type: WalletType.monero);
|
||||||
return monero_wallet_manager.isWalletExist(path: path);
|
return monero_wallet_manager.isWalletExist(path: path);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// TODO: Implement Exception fop wallet list service.
|
// TODO: Implement Exception for wallet list service.
|
||||||
print('MoneroWalletsManager Error: $e');
|
print('MoneroWalletsManager Error: $e');
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
|
@ -83,26 +85,21 @@ class MoneroWalletService extends WalletService<
|
||||||
try {
|
try {
|
||||||
final path = await pathForWallet(name: name, type: WalletType.monero);
|
final path = await pathForWallet(name: name, type: WalletType.monero);
|
||||||
monero_wallet_manager.openWallet(path: path, password: password);
|
monero_wallet_manager.openWallet(path: path, password: password);
|
||||||
|
|
||||||
// final id = walletTypeToString(WalletType.monero).toLowerCase() + '_' + name;
|
|
||||||
// final walletInfo = walletInfoSource.values
|
|
||||||
// .firstWhere((info) => info.id == id, orElse: () => null);
|
|
||||||
|
|
||||||
final wallet = MoneroWallet(filename: monero_wallet.getFilename());
|
final wallet = MoneroWallet(filename: monero_wallet.getFilename());
|
||||||
await wallet.init();
|
await wallet.init();
|
||||||
|
|
||||||
return wallet;
|
return wallet;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// TODO: Implement Exception fop wallet list service.
|
// TODO: Implement Exception for wallet list service.
|
||||||
print('MoneroWalletsManager Error: $e');
|
print('MoneroWalletsManager Error: $e');
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> remove(String wallet) async {
|
@override
|
||||||
// TODO: implement remove
|
Future<void> remove(String wallet) async =>
|
||||||
throw UnimplementedError();
|
File(await pathForWalletDir(name: wallet, type: WalletType.bitcoin))
|
||||||
}
|
.delete(recursive: true);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<MoneroWallet> restoreFromKeys(
|
Future<MoneroWallet> restoreFromKeys(
|
||||||
|
@ -125,7 +122,7 @@ class MoneroWalletService extends WalletService<
|
||||||
|
|
||||||
return wallet;
|
return wallet;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// TODO: Implement Exception fop wallet list service.
|
// TODO: Implement Exception for wallet list service.
|
||||||
print('MoneroWalletsManager Error: $e');
|
print('MoneroWalletsManager Error: $e');
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
|
@ -149,7 +146,7 @@ class MoneroWalletService extends WalletService<
|
||||||
|
|
||||||
return wallet;
|
return wallet;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// TODO: Implement Exception fop wallet list service.
|
// TODO: Implement Exception for wallet list service.
|
||||||
print('MoneroWalletsManager Error: $e');
|
print('MoneroWalletsManager Error: $e');
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:cake_wallet/core/key_service.dart';
|
import 'package:cake_wallet/core/key_service.dart';
|
||||||
import 'package:cake_wallet/src/domain/common/sync_status.dart';
|
import 'package:cake_wallet/src/domain/common/sync_status.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
|
@ -22,7 +24,8 @@ import 'package:cake_wallet/store/dashboard/fiat_convertation_store.dart';
|
||||||
// FIXME: move me
|
// FIXME: move me
|
||||||
Future<void> loadCurrentWallet() async {
|
Future<void> loadCurrentWallet() async {
|
||||||
final appStore = getIt.get<AppStore>();
|
final appStore = getIt.get<AppStore>();
|
||||||
final name = getIt.get<SharedPreferences>().getString('current_wallet_name');
|
final name = 'test';
|
||||||
|
getIt.get<SharedPreferences>().getString('current_wallet_name');
|
||||||
final typeRaw =
|
final typeRaw =
|
||||||
getIt.get<SharedPreferences>().getInt('current_wallet_type') ?? 0;
|
getIt.get<SharedPreferences>().getInt('current_wallet_type') ?? 0;
|
||||||
final type = deserializeFromInt(typeRaw);
|
final type = deserializeFromInt(typeRaw);
|
||||||
|
@ -49,8 +52,10 @@ ReactionDisposer _initialAuthReaction;
|
||||||
ReactionDisposer _onCurrentWalletChangeReaction;
|
ReactionDisposer _onCurrentWalletChangeReaction;
|
||||||
ReactionDisposer _onWalletSyncStatusChangeReaction;
|
ReactionDisposer _onWalletSyncStatusChangeReaction;
|
||||||
ReactionDisposer _onCurrentFiatCurrencyChangeDisposer;
|
ReactionDisposer _onCurrentFiatCurrencyChangeDisposer;
|
||||||
|
Timer _reconnectionTimer;
|
||||||
|
|
||||||
Future<void> bootstrap({FiatConvertationService fiatConvertationService}) async {
|
Future<void> bootstrap(
|
||||||
|
{FiatConvertationService fiatConvertationService}) async {
|
||||||
final authenticationStore = getIt.get<AuthenticationStore>();
|
final authenticationStore = getIt.get<AuthenticationStore>();
|
||||||
final settingsStore = getIt.get<SettingsStore>();
|
final settingsStore = getIt.get<SettingsStore>();
|
||||||
final fiatConvertationStore = getIt.get<FiatConvertationStore>();
|
final fiatConvertationStore = getIt.get<FiatConvertationStore>();
|
||||||
|
@ -72,12 +77,21 @@ Future<void> bootstrap({FiatConvertationService fiatConvertationService}) async
|
||||||
|
|
||||||
_onCurrentWalletChangeReaction ??=
|
_onCurrentWalletChangeReaction ??=
|
||||||
reaction((_) => getIt.get<AppStore>().wallet, (WalletBase wallet) async {
|
reaction((_) => getIt.get<AppStore>().wallet, (WalletBase wallet) async {
|
||||||
print('Wallet name ${wallet.name}');
|
|
||||||
|
|
||||||
_onWalletSyncStatusChangeReaction?.reaction?.dispose();
|
_onWalletSyncStatusChangeReaction?.reaction?.dispose();
|
||||||
_onWalletSyncStatusChangeReaction = when(
|
_reconnectionTimer?.cancel();
|
||||||
|
_onWalletSyncStatusChangeReaction = reaction(
|
||||||
(_) => wallet.syncStatus is ConnectedSyncStatus,
|
(_) => wallet.syncStatus is ConnectedSyncStatus,
|
||||||
() async => await wallet.startSync());
|
(Object _) async => await wallet.startSync());
|
||||||
|
|
||||||
|
_reconnectionTimer = Timer.periodic(Duration(seconds: 5), (_) async {
|
||||||
|
if (wallet.syncStatus is LostConnectionSyncStatus ||
|
||||||
|
wallet.syncStatus is FailedSyncStatus) {
|
||||||
|
try {
|
||||||
|
await wallet.connectToNode(
|
||||||
|
node: settingsStore.getCurrentNode(wallet.type));
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
await getIt
|
await getIt
|
||||||
.get<SharedPreferences>()
|
.get<SharedPreferences>()
|
||||||
|
@ -87,30 +101,24 @@ Future<void> bootstrap({FiatConvertationService fiatConvertationService}) async
|
||||||
.get<SharedPreferences>()
|
.get<SharedPreferences>()
|
||||||
.setInt('current_wallet_type', serializeToInt(wallet.type));
|
.setInt('current_wallet_type', serializeToInt(wallet.type));
|
||||||
|
|
||||||
await wallet.connectToNode(node: null);
|
final node = settingsStore.getCurrentNode(wallet.type);
|
||||||
|
|
||||||
final cryptoCurrency = wallet.currency;
|
final cryptoCurrency = wallet.currency;
|
||||||
final fiatCurrency = settingsStore.fiatCurrency;
|
final fiatCurrency = settingsStore.fiatCurrency;
|
||||||
|
|
||||||
|
await wallet.connectToNode(node: node);
|
||||||
|
|
||||||
final price = await fiatConvertationService.getPrice(
|
final price = await fiatConvertationService.getPrice(
|
||||||
crypto: cryptoCurrency,
|
crypto: cryptoCurrency, fiat: fiatCurrency);
|
||||||
fiat: fiatCurrency
|
|
||||||
);
|
|
||||||
|
|
||||||
fiatConvertationStore.setPrice(price);
|
fiatConvertationStore.setPrice(price);
|
||||||
});
|
});
|
||||||
|
|
||||||
//
|
_onCurrentFiatCurrencyChangeDisposer ??= reaction(
|
||||||
|
(_) => settingsStore.fiatCurrency, (FiatCurrency fiatCurrency) async {
|
||||||
_onCurrentFiatCurrencyChangeDisposer ??=
|
|
||||||
reaction((_) => settingsStore.fiatCurrency,
|
|
||||||
(FiatCurrency fiatCurrency) async {
|
|
||||||
final cryptoCurrency = getIt.get<AppStore>().wallet.currency;
|
final cryptoCurrency = getIt.get<AppStore>().wallet.currency;
|
||||||
|
|
||||||
final price = await fiatConvertationService.getPrice(
|
final price = await fiatConvertationService.getPrice(
|
||||||
crypto: cryptoCurrency,
|
crypto: cryptoCurrency, fiat: fiatCurrency);
|
||||||
fiat: fiatCurrency
|
|
||||||
);
|
|
||||||
|
|
||||||
fiatConvertationStore.setPrice(price);
|
fiatConvertationStore.setPrice(price);
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,22 +3,28 @@ import 'package:flutter/services.dart';
|
||||||
import 'package:cake_wallet/generated/i18n.dart';
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
|
|
||||||
class BiometricAuth {
|
class BiometricAuth {
|
||||||
|
final _localAuth = LocalAuthentication();
|
||||||
|
|
||||||
Future<bool> isAuthenticated() async {
|
Future<bool> isAuthenticated() async {
|
||||||
final LocalAuthentication _localAuth = LocalAuthentication();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return await _localAuth.authenticateWithBiometrics(
|
return await _localAuth.authenticateWithBiometrics(
|
||||||
localizedReason: S.current.biometric_auth_reason,
|
localizedReason: S.current.biometric_auth_reason,
|
||||||
useErrorDialogs: true,
|
useErrorDialogs: true,
|
||||||
stickyAuth: false
|
stickyAuth: false);
|
||||||
);
|
} on PlatformException catch (e) {
|
||||||
} on PlatformException
|
|
||||||
catch(e) {
|
|
||||||
print(e);
|
print(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<bool> canCheckBiometrics() async {
|
||||||
|
try {
|
||||||
|
return await _localAuth.canCheckBiometrics;
|
||||||
|
} on PlatformException catch (e) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,11 +1,12 @@
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:cake_wallet/src/domain/common/crypto_currency.dart';
|
import 'package:cake_wallet/src/domain/common/crypto_currency.dart';
|
||||||
|
import 'package:cake_wallet/utils/mobx.dart';
|
||||||
|
|
||||||
part 'contact.g.dart';
|
part 'contact.g.dart';
|
||||||
|
|
||||||
@HiveType(typeId: 0)
|
@HiveType(typeId: 0)
|
||||||
class Contact extends HiveObject {
|
class Contact extends HiveObject with Keyable {
|
||||||
Contact({@required this.name, @required this.address, CryptoCurrency type})
|
Contact({@required this.name, @required this.address, CryptoCurrency type})
|
||||||
: raw = type?.raw;
|
: raw = type?.raw;
|
||||||
|
|
||||||
|
@ -22,6 +23,9 @@ class Contact extends HiveObject {
|
||||||
|
|
||||||
CryptoCurrency get type => CryptoCurrency.deserialize(raw: raw);
|
CryptoCurrency get type => CryptoCurrency.deserialize(raw: raw);
|
||||||
|
|
||||||
|
@override
|
||||||
|
dynamic get keyIndex => key;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object o) => o is Contact && o.key == key;
|
bool operator ==(Object o) => o is Contact && o.key == key;
|
||||||
|
|
||||||
|
|
38
lib/src/domain/common/contact_model.dart
Normal file
38
lib/src/domain/common/contact_model.dart
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
// import 'package:hive/hive.dart';
|
||||||
|
// import 'package:mobx/mobx.dart';
|
||||||
|
// import 'package:cake_wallet/src/domain/common/contact.dart';
|
||||||
|
// import 'package:cake_wallet/src/domain/common/crypto_currency.dart';
|
||||||
|
|
||||||
|
// part 'contact_model.g.dart';
|
||||||
|
|
||||||
|
// class ContactModel = ContactModelBase with _$ContactModel;
|
||||||
|
|
||||||
|
// abstract class ContactModelBase with Store {
|
||||||
|
// ContactModelBase(this._contacts, {Contact contact}) : _contact = contact {
|
||||||
|
// name = _contact?.name;
|
||||||
|
// address = _contact?.address;
|
||||||
|
// currency = _contact?.type;
|
||||||
|
|
||||||
|
// _contacts.watch(key: contact.key).listen((event) {
|
||||||
|
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// @observable
|
||||||
|
// String name;
|
||||||
|
|
||||||
|
// @observable
|
||||||
|
// String address;
|
||||||
|
|
||||||
|
// @observable
|
||||||
|
// CryptoCurrency currency;
|
||||||
|
|
||||||
|
// // @computed
|
||||||
|
// // bool get isReady =>
|
||||||
|
// // (name?.isNotEmpty ?? false) &&
|
||||||
|
// // (currency?.toString()?.isNotEmpty ?? false) &&
|
||||||
|
// // (address?.isNotEmpty ?? false);
|
||||||
|
|
||||||
|
// final Box<ContactBase> _contacts;
|
||||||
|
// final Contact _contact;
|
||||||
|
// }
|
|
@ -29,15 +29,19 @@ Future defaultSettingsMigration(
|
||||||
switch (version) {
|
switch (version) {
|
||||||
case 1:
|
case 1:
|
||||||
await sharedPreferences.setString(
|
await sharedPreferences.setString(
|
||||||
SettingsStoreBase.currentFiatCurrencyKey, FiatCurrency.usd.toString());
|
SettingsStoreBase.currentFiatCurrencyKey,
|
||||||
|
FiatCurrency.usd.toString());
|
||||||
await sharedPreferences.setInt(
|
await sharedPreferences.setInt(
|
||||||
SettingsStoreBase.currentTransactionPriorityKey, TransactionPriority.standart.raw);
|
SettingsStoreBase.currentTransactionPriorityKey,
|
||||||
|
TransactionPriority.standart.raw);
|
||||||
await sharedPreferences.setInt(
|
await sharedPreferences.setInt(
|
||||||
SettingsStoreBase.currentBalanceDisplayModeKey,
|
SettingsStoreBase.currentBalanceDisplayModeKey,
|
||||||
BalanceDisplayMode.availableBalance.raw);
|
BalanceDisplayMode.availableBalance.raw);
|
||||||
await sharedPreferences.setBool('save_recipient_address', true);
|
await sharedPreferences.setBool('save_recipient_address', true);
|
||||||
await resetToDefault(nodes);
|
await resetToDefault(nodes);
|
||||||
await changeCurrentNodeToDefault(
|
await changeMoneroCurrentNodeToDefault(
|
||||||
|
sharedPreferences: sharedPreferences, nodes: nodes);
|
||||||
|
await changeBitcoinCurrentElectrumServerToDefault(
|
||||||
sharedPreferences: sharedPreferences, nodes: nodes);
|
sharedPreferences: sharedPreferences, nodes: nodes);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
@ -50,6 +54,11 @@ Future defaultSettingsMigration(
|
||||||
case 3:
|
case 3:
|
||||||
await updateNodeTypes(nodes: nodes);
|
await updateNodeTypes(nodes: nodes);
|
||||||
await addBitcoinElectrumServerList(nodes: nodes);
|
await addBitcoinElectrumServerList(nodes: nodes);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
await changeBitcoinCurrentElectrumServerToDefault(
|
||||||
|
sharedPreferences: sharedPreferences, nodes: nodes);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
@ -69,10 +78,11 @@ Future defaultSettingsMigration(
|
||||||
Future<void> replaceNodesMigration({@required Box<Node> nodes}) async {
|
Future<void> replaceNodesMigration({@required Box<Node> nodes}) async {
|
||||||
final replaceNodes = <String, Node>{
|
final replaceNodes = <String, Node>{
|
||||||
'eu-node.cakewallet.io:18081':
|
'eu-node.cakewallet.io:18081':
|
||||||
Node(uri: 'xmr-node-eu.cakewallet.com:18081'),
|
Node(uri: 'xmr-node-eu.cakewallet.com:18081', type: WalletType.monero),
|
||||||
'node.cakewallet.io:18081':
|
'node.cakewallet.io:18081': Node(
|
||||||
Node(uri: 'xmr-node-usa-east.cakewallet.com:18081'),
|
uri: 'xmr-node-usa-east.cakewallet.com:18081', type: WalletType.monero),
|
||||||
'node.xmr.ru:13666': Node(uri: 'node.monero.net:18081')
|
'node.xmr.ru:13666':
|
||||||
|
Node(uri: 'node.monero.net:18081', type: WalletType.monero)
|
||||||
};
|
};
|
||||||
|
|
||||||
nodes.values.forEach((Node node) async {
|
nodes.values.forEach((Node node) async {
|
||||||
|
@ -87,11 +97,27 @@ Future<void> replaceNodesMigration({@required Box<Node> nodes}) async {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> changeCurrentNodeToDefault(
|
Future<void> changeMoneroCurrentNodeToDefault(
|
||||||
{@required SharedPreferences sharedPreferences,
|
{@required SharedPreferences sharedPreferences,
|
||||||
@required Box<Node> nodes}) async {
|
@required Box<Node> nodes}) async {
|
||||||
|
final node = getMoneroDefaultNode(nodes: nodes);
|
||||||
|
final nodeId = node?.key as int ?? 0; // 0 - England
|
||||||
|
|
||||||
|
await sharedPreferences.setInt('current_node_id', nodeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
Node getBitcoinDefaultElectrumServer({@required Box<Node> nodes}) {
|
||||||
|
final uri = 'electrumx.cakewallet.com:50002';
|
||||||
|
|
||||||
|
return nodes.values
|
||||||
|
.firstWhere((Node node) => node.uri == uri, orElse: () => null) ??
|
||||||
|
nodes.values.firstWhere((node) => node.type == WalletType.bitcoin,
|
||||||
|
orElse: () => null);
|
||||||
|
}
|
||||||
|
|
||||||
|
Node getMoneroDefaultNode({@required Box<Node> nodes}) {
|
||||||
final timeZone = DateTime.now().timeZoneOffset.inHours;
|
final timeZone = DateTime.now().timeZoneOffset.inHours;
|
||||||
String nodeUri = '';
|
var nodeUri = '';
|
||||||
|
|
||||||
if (timeZone >= 1) {
|
if (timeZone >= 1) {
|
||||||
// Eurasia
|
// Eurasia
|
||||||
|
@ -101,11 +127,18 @@ Future<void> changeCurrentNodeToDefault(
|
||||||
nodeUri = 'xmr-node-usa-east.cakewallet.com:18081';
|
nodeUri = 'xmr-node-usa-east.cakewallet.com:18081';
|
||||||
}
|
}
|
||||||
|
|
||||||
final node = nodes.values.firstWhere((Node node) => node.uri == nodeUri) ??
|
return nodes.values
|
||||||
|
.firstWhere((Node node) => node.uri == nodeUri, orElse: () => null) ??
|
||||||
nodes.values.first;
|
nodes.values.first;
|
||||||
final nodeId = node != null ? node.key as int : 0; // 0 - England
|
}
|
||||||
|
|
||||||
await sharedPreferences.setInt('current_node_id', nodeId);
|
Future<void> changeBitcoinCurrentElectrumServerToDefault(
|
||||||
|
{@required SharedPreferences sharedPreferences,
|
||||||
|
@required Box<Node> nodes}) async {
|
||||||
|
final server = getBitcoinDefaultElectrumServer(nodes: nodes);
|
||||||
|
final serverId = server?.key as int ?? 0;
|
||||||
|
|
||||||
|
await sharedPreferences.setInt('current_node_id_btc', serverId);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> replaceDefaultNode(
|
Future<void> replaceDefaultNode(
|
||||||
|
@ -126,7 +159,7 @@ Future<void> replaceDefaultNode(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await changeCurrentNodeToDefault(
|
await changeMoneroCurrentNodeToDefault(
|
||||||
sharedPreferences: sharedPreferences, nodes: nodes);
|
sharedPreferences: sharedPreferences, nodes: nodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:cake_wallet/utils/mobx.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
|
@ -8,13 +9,17 @@ import 'package:cake_wallet/src/domain/common/digest_request.dart';
|
||||||
part 'node.g.dart';
|
part 'node.g.dart';
|
||||||
|
|
||||||
@HiveType(typeId: 1)
|
@HiveType(typeId: 1)
|
||||||
class Node extends HiveObject {
|
class Node extends HiveObject with Keyable {
|
||||||
Node({@required this.uri, @required WalletType type, this.login, this.password}) {
|
Node(
|
||||||
|
{@required this.uri,
|
||||||
|
@required WalletType type,
|
||||||
|
this.login,
|
||||||
|
this.password}) {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
Node.fromMap(Map map)
|
Node.fromMap(Map map)
|
||||||
: uri = (map['uri'] ?? '') as String,
|
: uri = map['uri'] as String ?? '',
|
||||||
login = map['login'] as String,
|
login = map['login'] as String,
|
||||||
password = map['password'] as String,
|
password = map['password'] as String,
|
||||||
typeRaw = map['typeRaw'] as int;
|
typeRaw = map['typeRaw'] as int;
|
||||||
|
|
|
@ -10,7 +10,10 @@ Future<List<Node>> loadDefaultNodes() async {
|
||||||
|
|
||||||
return nodes.map((dynamic raw) {
|
return nodes.map((dynamic raw) {
|
||||||
if (raw is Map) {
|
if (raw is Map) {
|
||||||
return Node.fromMap(raw);
|
final node = Node.fromMap(raw);
|
||||||
|
node?.type = WalletType.monero;
|
||||||
|
|
||||||
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
@ -38,13 +41,7 @@ Future resetToDefault(Box<Node> nodeSource) async {
|
||||||
final moneroNodes = await loadDefaultNodes();
|
final moneroNodes = await loadDefaultNodes();
|
||||||
final bitcoinElectrumServerList = await loadElectrumServerList();
|
final bitcoinElectrumServerList = await loadElectrumServerList();
|
||||||
final nodes = moneroNodes + bitcoinElectrumServerList;
|
final nodes = moneroNodes + bitcoinElectrumServerList;
|
||||||
final entities = <int, Node>{};
|
|
||||||
|
|
||||||
await nodeSource.clear();
|
await nodeSource.clear();
|
||||||
|
await nodeSource.addAll(nodes);
|
||||||
for (var i = 0; i < nodes.length; i++) {
|
|
||||||
entities[i] = nodes[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
await nodeSource.putAll(entities);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,3 +73,11 @@ class ConnectedSyncStatus extends SyncStatus {
|
||||||
@override
|
@override
|
||||||
String title() => S.current.sync_status_connected;
|
String title() => S.current.sync_status_connected;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class LostConnectionSyncStatus extends SyncStatus {
|
||||||
|
@override
|
||||||
|
double progress() => 1.0;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String title() => S.current.sync_status_failed_connect;
|
||||||
|
}
|
|
@ -1,11 +1,13 @@
|
||||||
import 'package:cake_wallet/src/domain/common/transaction_direction.dart';
|
import 'package:cake_wallet/src/domain/common/transaction_direction.dart';
|
||||||
|
|
||||||
abstract class TransactionInfo extends Object {
|
abstract class TransactionInfo extends Object {
|
||||||
|
String id;
|
||||||
int amount;
|
int amount;
|
||||||
TransactionDirection direction;
|
TransactionDirection direction;
|
||||||
bool isPending;
|
bool isPending;
|
||||||
DateTime date;
|
DateTime date;
|
||||||
int height;
|
int height;
|
||||||
|
int confirmations;
|
||||||
String amountFormatted();
|
String amountFormatted();
|
||||||
String fiatAmount();
|
String fiatAmount();
|
||||||
void changeFiatAmount(String amount);
|
void changeFiatAmount(String amount);
|
||||||
|
|
|
@ -5,19 +5,20 @@ import 'package:cake_wallet/generated/i18n.dart';
|
||||||
import 'package:cake_wallet/view_model/auth_state.dart';
|
import 'package:cake_wallet/view_model/auth_state.dart';
|
||||||
import 'package:cake_wallet/view_model/auth_view_model.dart';
|
import 'package:cake_wallet/view_model/auth_view_model.dart';
|
||||||
import 'package:cake_wallet/src/screens/pin_code/pin_code.dart';
|
import 'package:cake_wallet/src/screens/pin_code/pin_code.dart';
|
||||||
import 'package:cake_wallet/src/stores/settings/settings_store.dart';
|
|
||||||
import 'package:cake_wallet/src/domain/common/biometric_auth.dart';
|
import 'package:cake_wallet/src/domain/common/biometric_auth.dart';
|
||||||
|
|
||||||
typedef OnAuthenticationFinished = void Function(bool, AuthPageState);
|
typedef OnAuthenticationFinished = void Function(bool, AuthPageState);
|
||||||
|
|
||||||
class AuthPage extends StatefulWidget {
|
class AuthPage extends StatefulWidget {
|
||||||
AuthPage(
|
AuthPage(
|
||||||
{this.onAuthenticationFinished,
|
{@required this.allowBiometricalAuthentication,
|
||||||
|
this.onAuthenticationFinished,
|
||||||
this.authViewModel,
|
this.authViewModel,
|
||||||
this.closable = true});
|
this.closable = true});
|
||||||
|
|
||||||
final AuthViewModel authViewModel;
|
final AuthViewModel authViewModel;
|
||||||
final OnAuthenticationFinished onAuthenticationFinished;
|
final OnAuthenticationFinished onAuthenticationFinished;
|
||||||
|
final bool allowBiometricalAuthentication;
|
||||||
final bool closable;
|
final bool closable;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -95,6 +96,27 @@ class AuthPageState extends State<AuthPage> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (widget.allowBiometricalAuthentication) {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||||
|
print('post');
|
||||||
|
await Future<void>.delayed(Duration(milliseconds: 100));
|
||||||
|
print('after timeout');
|
||||||
|
final biometricAuth = BiometricAuth();
|
||||||
|
final isAuth = await biometricAuth.isAuthenticated();
|
||||||
|
|
||||||
|
if (isAuth) {
|
||||||
|
widget.authViewModel.biometricAuth();
|
||||||
|
_key.currentState.showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text(S.of(context).authenticated),
|
||||||
|
backgroundColor: Colors.green,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,27 +133,7 @@ class AuthPageState extends State<AuthPage> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
// final authStore = Provider.of<AuthStore>(context);
|
print('start');
|
||||||
// final settingsStore = Provider.of<SettingsStore>(context);
|
|
||||||
|
|
||||||
// if (settingsStore.allowBiometricalAuthentication) {
|
|
||||||
// WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
||||||
// final biometricAuth = BiometricAuth();
|
|
||||||
// biometricAuth.isAuthenticated().then(
|
|
||||||
// (isAuth) {
|
|
||||||
// if (isAuth) {
|
|
||||||
// authStore.biometricAuth();
|
|
||||||
// _key.currentState.showSnackBar(
|
|
||||||
// SnackBar(
|
|
||||||
// content: Text(S.of(context).authenticated),
|
|
||||||
// backgroundColor: Colors.green,
|
|
||||||
// ),
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// );
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
key: _key,
|
key: _key,
|
||||||
|
|
|
@ -33,19 +33,14 @@ class DashboardPage extends BasePage {
|
||||||
Theme.of(context).accentColor,
|
Theme.of(context).accentColor,
|
||||||
Theme.of(context).scaffoldBackgroundColor,
|
Theme.of(context).scaffoldBackgroundColor,
|
||||||
Theme.of(context).primaryColor,
|
Theme.of(context).primaryColor,
|
||||||
],
|
], begin: Alignment.topRight, end: Alignment.bottomLeft)),
|
||||||
begin: Alignment.topRight,
|
|
||||||
end: Alignment.bottomLeft)),
|
|
||||||
child: scaffold);
|
child: scaffold);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool get resizeToAvoidBottomPadding => false;
|
bool get resizeToAvoidBottomPadding => false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget get endDrawer => MenuWidget(
|
Widget get endDrawer => MenuWidget(walletViewModel);
|
||||||
name: walletViewModel.name,
|
|
||||||
subname: walletViewModel.subname,
|
|
||||||
type: walletViewModel.type);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget middle(BuildContext context) {
|
Widget middle(BuildContext context) {
|
||||||
|
@ -54,8 +49,8 @@ class DashboardPage extends BasePage {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget trailing(BuildContext context) {
|
Widget trailing(BuildContext context) {
|
||||||
final menuButton = Image.asset('assets/images/menu.png',
|
final menuButton =
|
||||||
color: Colors.white);
|
Image.asset('assets/images/menu.png', color: Colors.white);
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
alignment: Alignment.centerRight,
|
alignment: Alignment.centerRight,
|
||||||
|
@ -65,9 +60,7 @@ class DashboardPage extends BasePage {
|
||||||
splashColor: Colors.transparent,
|
splashColor: Colors.transparent,
|
||||||
padding: EdgeInsets.all(0),
|
padding: EdgeInsets.all(0),
|
||||||
onPressed: () => onOpenEndDrawer(),
|
onPressed: () => onOpenEndDrawer(),
|
||||||
child: menuButton
|
child: menuButton));
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final DashboardViewModel walletViewModel;
|
final DashboardViewModel walletViewModel;
|
||||||
|
@ -85,7 +78,6 @@ class DashboardPage extends BasePage {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget body(BuildContext context) {
|
Widget body(BuildContext context) {
|
||||||
|
|
||||||
_setEffects();
|
_setEffects();
|
||||||
|
|
||||||
return SafeArea(
|
return SafeArea(
|
||||||
|
@ -96,15 +88,9 @@ class DashboardPage extends BasePage {
|
||||||
child: PageView.builder(
|
child: PageView.builder(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
itemCount: pages.length,
|
itemCount: pages.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) => pages[index])),
|
||||||
return pages[index];
|
|
||||||
}
|
|
||||||
)
|
|
||||||
),
|
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.only(
|
padding: EdgeInsets.only(bottom: 24),
|
||||||
bottom: 24
|
|
||||||
),
|
|
||||||
child: SmoothPageIndicator(
|
child: SmoothPageIndicator(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
count: pages.length,
|
count: pages.length,
|
||||||
|
@ -114,18 +100,13 @@ class DashboardPage extends BasePage {
|
||||||
dotWidth: 6.0,
|
dotWidth: 6.0,
|
||||||
dotHeight: 6.0,
|
dotHeight: 6.0,
|
||||||
dotColor: Theme.of(context).indicatorColor,
|
dotColor: Theme.of(context).indicatorColor,
|
||||||
activeDotColor: Colors.white
|
activeDotColor: Colors.white),
|
||||||
),
|
)),
|
||||||
)
|
|
||||||
),
|
|
||||||
Container(
|
Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
padding: EdgeInsets.only(
|
padding: EdgeInsets.only(left: 44, right: 0, bottom: 24),
|
||||||
left: 45,
|
|
||||||
right: 45,
|
|
||||||
bottom: 24
|
|
||||||
),
|
|
||||||
child: Row(
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Flexible(
|
Flexible(
|
||||||
child: ActionButton(
|
child: ActionButton(
|
||||||
|
@ -139,23 +120,13 @@ class DashboardPage extends BasePage {
|
||||||
child: ActionButton(
|
child: ActionButton(
|
||||||
image: exchangeImage,
|
image: exchangeImage,
|
||||||
title: S.of(context).exchange,
|
title: S.of(context).exchange,
|
||||||
route: Routes.exchange
|
route: Routes.exchange),
|
||||||
),
|
|
||||||
),
|
|
||||||
Flexible(
|
|
||||||
child: ActionButton(
|
|
||||||
image: receiveImage,
|
|
||||||
title: S.of(context).receive,
|
|
||||||
route: Routes.receive,
|
|
||||||
alignment: Alignment.centerRight,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
)
|
));
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _setEffects() {
|
void _setEffects() {
|
||||||
|
|
|
@ -6,8 +6,10 @@ import 'package:cake_wallet/src/stores/wallet/wallet_store.dart';
|
||||||
import 'package:cake_wallet/src/screens/auth/auth_page.dart';
|
import 'package:cake_wallet/src/screens/auth/auth_page.dart';
|
||||||
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
|
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
|
||||||
|
|
||||||
|
// FIXME: terrible design
|
||||||
|
|
||||||
class WalletMenu {
|
class WalletMenu {
|
||||||
WalletMenu(this.context);
|
WalletMenu(this.context, this.reconnect);
|
||||||
|
|
||||||
final List<String> items = [
|
final List<String> items = [
|
||||||
S.current.reconnect,
|
S.current.reconnect,
|
||||||
|
@ -30,6 +32,7 @@ class WalletMenu {
|
||||||
];
|
];
|
||||||
|
|
||||||
final BuildContext context;
|
final BuildContext context;
|
||||||
|
final Future<void> Function() reconnect;
|
||||||
|
|
||||||
void action(int index) {
|
void action(int index) {
|
||||||
switch (index) {
|
switch (index) {
|
||||||
|
@ -70,22 +73,19 @@ class WalletMenu {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _presentReconnectAlert(BuildContext context) async {
|
Future<void> _presentReconnectAlert(BuildContext context) async {
|
||||||
final walletStore = Provider.of<WalletStore>(context);
|
|
||||||
|
|
||||||
await showDialog<void>(
|
await showDialog<void>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return AlertWithTwoActions(
|
return AlertWithTwoActions(
|
||||||
alertTitle: S.of(context).reconnection,
|
alertTitle: S.of(context).reconnection,
|
||||||
alertContent: S.of(context).reconnect_alert_text,
|
alertContent: S.of(context).reconnect_alert_text,
|
||||||
rightButtonText: S.of(context).ok,
|
leftButtonText: S.of(context).ok,
|
||||||
leftButtonText: S.of(context).cancel,
|
rightButtonText: S.of(context).cancel,
|
||||||
actionRightButton: () {
|
actionLeftButton: () async {
|
||||||
walletStore.reconnect();
|
await reconnect?.call();
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
actionLeftButton: () => Navigator.of(context).pop()
|
actionRightButton: () => Navigator.of(context).pop());
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class ActionButton extends StatelessWidget{
|
class ActionButton extends StatelessWidget {
|
||||||
ActionButton({
|
ActionButton(
|
||||||
@required this.image,
|
{@required this.image,
|
||||||
@required this.title,
|
@required this.title,
|
||||||
@required this.route,
|
@required this.route,
|
||||||
this.alignment = Alignment.center
|
this.alignment = Alignment.center});
|
||||||
});
|
|
||||||
|
|
||||||
final Image image;
|
final Image image;
|
||||||
final String title;
|
final String title;
|
||||||
|
@ -35,17 +34,13 @@ class ActionButton extends StatelessWidget{
|
||||||
width: 60,
|
width: 60,
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Theme.of(context).buttonColor,
|
color: Theme.of(context).buttonColor, shape: BoxShape.circle),
|
||||||
shape: BoxShape.circle),
|
|
||||||
child: image,
|
child: image,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
title,
|
title,
|
||||||
style: TextStyle(
|
style: TextStyle(fontSize: 14, color: Colors.white),
|
||||||
fontSize: 14,
|
|
||||||
color: Colors.white
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,16 +1,17 @@
|
||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
import 'package:cake_wallet/palette.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:cake_wallet/palette.dart';
|
||||||
|
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||||
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
|
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
|
||||||
import 'package:cake_wallet/src/screens/dashboard/wallet_menu.dart';
|
import 'package:cake_wallet/src/screens/dashboard/wallet_menu.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
|
|
||||||
class MenuWidget extends StatefulWidget {
|
// FIXME: terrible design.
|
||||||
MenuWidget({this.type, this.name, this.subname});
|
|
||||||
|
|
||||||
final WalletType type;
|
class MenuWidget extends StatefulWidget {
|
||||||
final String name;
|
MenuWidget(this.dashboardViewModel);
|
||||||
final String subname;
|
|
||||||
|
final DashboardViewModel dashboardViewModel;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
MenuWidgetState createState() => MenuWidgetState();
|
MenuWidgetState createState() => MenuWidgetState();
|
||||||
|
@ -64,17 +65,14 @@ class MenuWidgetState extends State<MenuWidget> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final walletMenu = WalletMenu(context);
|
final walletMenu =
|
||||||
|
WalletMenu(context, () async => widget.dashboardViewModel.reconnect());
|
||||||
final itemCount = walletMenu.items.length;
|
final itemCount = walletMenu.items.length;
|
||||||
|
|
||||||
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.overline.decorationColor);
|
||||||
.accentTextTheme
|
|
||||||
.overline.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.overline.decorationColor);
|
||||||
.accentTextTheme
|
|
||||||
.overline.decorationColor);
|
|
||||||
|
|
||||||
return Row(
|
return Row(
|
||||||
mainAxisSize: MainAxisSize.max,
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
@ -100,14 +98,20 @@ class MenuWidgetState extends State<MenuWidget> {
|
||||||
child: ListView.separated(
|
child: ListView.separated(
|
||||||
padding: EdgeInsets.only(top: 0),
|
padding: EdgeInsets.only(top: 0),
|
||||||
itemBuilder: (_, index) {
|
itemBuilder: (_, index) {
|
||||||
|
|
||||||
if (index == 0) {
|
if (index == 0) {
|
||||||
return Container(
|
return Container(
|
||||||
height: headerHeight,
|
height: headerHeight,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
gradient: LinearGradient(colors: [
|
gradient: LinearGradient(
|
||||||
Theme.of(context).accentTextTheme.display1.color,
|
colors: [
|
||||||
Theme.of(context).accentTextTheme.display1.decorationColor,
|
Theme.of(context)
|
||||||
|
.accentTextTheme
|
||||||
|
.display1
|
||||||
|
.color,
|
||||||
|
Theme.of(context)
|
||||||
|
.accentTextTheme
|
||||||
|
.display1
|
||||||
|
.decorationColor,
|
||||||
],
|
],
|
||||||
begin: Alignment.topLeft,
|
begin: Alignment.topLeft,
|
||||||
end: Alignment.bottomRight),
|
end: Alignment.bottomRight),
|
||||||
|
@ -116,36 +120,40 @@ class MenuWidgetState extends State<MenuWidget> {
|
||||||
left: 24,
|
left: 24,
|
||||||
top: fromTopEdge,
|
top: fromTopEdge,
|
||||||
right: 24,
|
right: 24,
|
||||||
bottom: fromBottomEdge
|
bottom: fromBottomEdge),
|
||||||
),
|
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
_iconFor(type: widget.type),
|
_iconFor(type: widget.dashboardViewModel.type),
|
||||||
SizedBox(width: 12),
|
SizedBox(width: 12),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Container(
|
child: Container(
|
||||||
height: 42,
|
height: 42,
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment:
|
||||||
mainAxisAlignment: widget.subname != null
|
CrossAxisAlignment.start,
|
||||||
|
mainAxisAlignment:
|
||||||
|
widget.dashboardViewModel.subname !=
|
||||||
|
null
|
||||||
? MainAxisAlignment.spaceBetween
|
? MainAxisAlignment.spaceBetween
|
||||||
: MainAxisAlignment.center,
|
: MainAxisAlignment.center,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Text(
|
Text(
|
||||||
widget.name,
|
widget.dashboardViewModel.name,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontWeight: FontWeight.bold),
|
fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
if (widget.subname != null)
|
if (widget.dashboardViewModel.subname !=
|
||||||
|
null)
|
||||||
Text(
|
Text(
|
||||||
widget.subname,
|
widget.dashboardViewModel.subname,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Theme.of(context)
|
color: Theme.of(context)
|
||||||
.accentTextTheme
|
.accentTextTheme
|
||||||
.overline.decorationColor,
|
.overline
|
||||||
|
.decorationColor,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
fontSize: 12),
|
fontSize: 12),
|
||||||
)
|
)
|
||||||
|
@ -169,10 +177,11 @@ class MenuWidgetState extends State<MenuWidget> {
|
||||||
walletMenu.action(index);
|
walletMenu.action(index);
|
||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
color: Theme.of(context).textTheme.body2.decorationColor,
|
color: Theme.of(context)
|
||||||
height: isLastTile
|
.textTheme
|
||||||
? headerHeight
|
.body2
|
||||||
: tileHeight,
|
.decorationColor,
|
||||||
|
height: isLastTile ? headerHeight : tileHeight,
|
||||||
padding: isLastTile
|
padding: isLastTile
|
||||||
? EdgeInsets.only(
|
? EdgeInsets.only(
|
||||||
left: 24,
|
left: 24,
|
||||||
|
@ -192,24 +201,26 @@ class MenuWidgetState extends State<MenuWidget> {
|
||||||
child: Text(
|
child: Text(
|
||||||
item,
|
item,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Theme.of(context).textTheme
|
color: Theme.of(context)
|
||||||
.display2.color,
|
.textTheme
|
||||||
|
.display2
|
||||||
|
.color,
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontWeight: FontWeight.bold),
|
fontWeight: FontWeight.bold),
|
||||||
))
|
))
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
)
|
));
|
||||||
);
|
|
||||||
},
|
},
|
||||||
separatorBuilder: (_, index) => Container(
|
separatorBuilder: (_, index) => Container(
|
||||||
height: 1,
|
height: 1,
|
||||||
color: Theme.of(context).primaryTextTheme.caption.decorationColor,
|
color: Theme.of(context)
|
||||||
|
.primaryTextTheme
|
||||||
|
.caption
|
||||||
|
.decorationColor,
|
||||||
),
|
),
|
||||||
itemCount: itemCount + 1),
|
itemCount: itemCount + 1),
|
||||||
)
|
)))
|
||||||
)
|
|
||||||
)
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ import 'package:cake_wallet/core/amount_validator.dart';
|
||||||
|
|
||||||
class BaseExchangeWidget extends StatefulWidget {
|
class BaseExchangeWidget extends StatefulWidget {
|
||||||
BaseExchangeWidget({
|
BaseExchangeWidget({
|
||||||
@ required this.exchangeViewModel,
|
@required this.exchangeViewModel,
|
||||||
this.leading,
|
this.leading,
|
||||||
this.middle,
|
this.middle,
|
||||||
this.trailing,
|
this.trailing,
|
||||||
|
@ -50,11 +50,11 @@ class BaseExchangeWidget extends StatefulWidget {
|
||||||
|
|
||||||
class BaseExchangeWidgetState extends State<BaseExchangeWidget> {
|
class BaseExchangeWidgetState extends State<BaseExchangeWidget> {
|
||||||
BaseExchangeWidgetState({
|
BaseExchangeWidgetState({
|
||||||
@ required this.exchangeViewModel,
|
@required this.exchangeViewModel,
|
||||||
@ required this.leading,
|
@required this.leading,
|
||||||
@ required this.middle,
|
@required this.middle,
|
||||||
@ required this.trailing,
|
@required this.trailing,
|
||||||
@ required this.isTemplate,
|
@required this.isTemplate,
|
||||||
});
|
});
|
||||||
|
|
||||||
final ExchangeViewModel exchangeViewModel;
|
final ExchangeViewModel exchangeViewModel;
|
||||||
|
@ -134,54 +134,70 @@ class BaseExchangeWidgetState extends State<BaseExchangeWidget> {
|
||||||
builder: (_) => ExchangeCard(
|
builder: (_) => ExchangeCard(
|
||||||
key: depositKey,
|
key: depositKey,
|
||||||
title: S.of(context).you_will_send,
|
title: S.of(context).you_will_send,
|
||||||
initialCurrency: exchangeViewModel.depositCurrency,
|
initialCurrency:
|
||||||
|
exchangeViewModel.depositCurrency,
|
||||||
initialWalletName: depositWalletName,
|
initialWalletName: depositWalletName,
|
||||||
initialAddress:
|
initialAddress: exchangeViewModel
|
||||||
exchangeViewModel.depositCurrency == exchangeViewModel.wallet.currency
|
.depositCurrency ==
|
||||||
|
exchangeViewModel.wallet.currency
|
||||||
? exchangeViewModel.wallet.address
|
? exchangeViewModel.wallet.address
|
||||||
: exchangeViewModel.depositAddress,
|
: exchangeViewModel.depositAddress,
|
||||||
initialIsAmountEditable: true,
|
initialIsAmountEditable: true,
|
||||||
initialIsAddressEditable: exchangeViewModel.isDepositAddressEnabled,
|
initialIsAddressEditable: exchangeViewModel
|
||||||
|
.isDepositAddressEnabled,
|
||||||
isAmountEstimated: false,
|
isAmountEstimated: false,
|
||||||
currencies: CryptoCurrency.all,
|
currencies: CryptoCurrency.all,
|
||||||
onCurrencySelected: (currency) =>
|
onCurrencySelected: (currency) =>
|
||||||
exchangeViewModel.changeDepositCurrency(currency: currency),
|
exchangeViewModel.changeDepositCurrency(
|
||||||
|
currency: currency),
|
||||||
imageArrow: arrowBottomPurple,
|
imageArrow: arrowBottomPurple,
|
||||||
currencyButtonColor: Colors.transparent,
|
currencyButtonColor: Colors.transparent,
|
||||||
addressButtonsColor: Theme.of(context).focusColor,
|
addressButtonsColor:
|
||||||
borderColor: Theme.of(context).primaryTextTheme.body2.color,
|
Theme.of(context).focusColor,
|
||||||
|
borderColor: Theme.of(context)
|
||||||
|
.primaryTextTheme
|
||||||
|
.body2
|
||||||
|
.color,
|
||||||
currencyValueValidator: AmountValidator(
|
currencyValueValidator: AmountValidator(
|
||||||
type: exchangeViewModel.wallet.type),
|
type: exchangeViewModel.wallet.type),
|
||||||
addressTextFieldValidator: AddressValidator(
|
addressTextFieldValidator: AddressValidator(
|
||||||
type: exchangeViewModel.depositCurrency),
|
type:
|
||||||
|
exchangeViewModel.depositCurrency),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
)
|
)),
|
||||||
),
|
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.only(top: 29, left: 24, right: 24),
|
padding: EdgeInsets.only(top: 29, left: 24, right: 24),
|
||||||
child: Observer(
|
child: Observer(
|
||||||
builder: (_) => ExchangeCard(
|
builder: (_) => ExchangeCard(
|
||||||
key: receiveKey,
|
key: receiveKey,
|
||||||
title: S.of(context).you_will_get,
|
title: S.of(context).you_will_get,
|
||||||
initialCurrency: exchangeViewModel.receiveCurrency,
|
initialCurrency:
|
||||||
|
exchangeViewModel.receiveCurrency,
|
||||||
initialWalletName: receiveWalletName,
|
initialWalletName: receiveWalletName,
|
||||||
initialAddress:
|
initialAddress:
|
||||||
exchangeViewModel.receiveCurrency == exchangeViewModel.wallet.currency
|
exchangeViewModel.receiveCurrency ==
|
||||||
|
exchangeViewModel.wallet.currency
|
||||||
? exchangeViewModel.wallet.address
|
? exchangeViewModel.wallet.address
|
||||||
: exchangeViewModel.receiveAddress,
|
: exchangeViewModel.receiveAddress,
|
||||||
initialIsAmountEditable: false,
|
initialIsAmountEditable: false,
|
||||||
initialIsAddressEditable: exchangeViewModel.isReceiveAddressEnabled,
|
initialIsAddressEditable:
|
||||||
|
exchangeViewModel.isReceiveAddressEnabled,
|
||||||
isAmountEstimated: true,
|
isAmountEstimated: true,
|
||||||
currencies: CryptoCurrency.all,
|
currencies: CryptoCurrency.all,
|
||||||
onCurrencySelected: (currency) => exchangeViewModel
|
onCurrencySelected: (currency) =>
|
||||||
.changeReceiveCurrency(currency: currency),
|
exchangeViewModel.changeReceiveCurrency(
|
||||||
|
currency: currency),
|
||||||
imageArrow: arrowBottomCakeGreen,
|
imageArrow: arrowBottomCakeGreen,
|
||||||
currencyButtonColor: Colors.transparent,
|
currencyButtonColor: Colors.transparent,
|
||||||
addressButtonsColor: Theme.of(context).focusColor,
|
addressButtonsColor:
|
||||||
borderColor: Theme.of(context).primaryTextTheme.body2.decorationColor,
|
Theme.of(context).focusColor,
|
||||||
|
borderColor: Theme.of(context)
|
||||||
|
.primaryTextTheme
|
||||||
|
.body2
|
||||||
|
.decorationColor,
|
||||||
currencyValueValidator: AmountValidator(
|
currencyValueValidator: AmountValidator(
|
||||||
type: exchangeViewModel.wallet.type),
|
type: exchangeViewModel.wallet.type),
|
||||||
addressTextFieldValidator: AddressValidator(
|
addressTextFieldValidator: AddressValidator(
|
||||||
|
@ -189,16 +205,11 @@ class BaseExchangeWidgetState extends State<BaseExchangeWidget> {
|
||||||
)),
|
)),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
)
|
)),
|
||||||
),
|
|
||||||
isTemplate
|
isTemplate
|
||||||
? Offstage()
|
? Offstage()
|
||||||
: Padding(
|
: Padding(
|
||||||
padding: EdgeInsets.only(
|
padding: EdgeInsets.only(top: 30, left: 24, bottom: 24),
|
||||||
top: 30,
|
|
||||||
left: 24,
|
|
||||||
bottom: 24
|
|
||||||
),
|
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
|
@ -207,8 +218,10 @@ class BaseExchangeWidgetState extends State<BaseExchangeWidget> {
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
color: Theme.of(context).primaryTextTheme.display4.color
|
color: Theme.of(context)
|
||||||
),
|
.primaryTextTheme
|
||||||
|
.display4
|
||||||
|
.color),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -231,16 +244,21 @@ class BaseExchangeWidgetState extends State<BaseExchangeWidget> {
|
||||||
child: DottedBorder(
|
child: DottedBorder(
|
||||||
borderType: BorderType.RRect,
|
borderType: BorderType.RRect,
|
||||||
dashPattern: [6, 4],
|
dashPattern: [6, 4],
|
||||||
color: Theme.of(context).primaryTextTheme.display2.decorationColor,
|
color: Theme.of(context)
|
||||||
|
.primaryTextTheme
|
||||||
|
.display2
|
||||||
|
.decorationColor,
|
||||||
strokeWidth: 2,
|
strokeWidth: 2,
|
||||||
radius: Radius.circular(20),
|
radius: Radius.circular(20),
|
||||||
child: Container(
|
child: Container(
|
||||||
height: 34,
|
height: 34,
|
||||||
width: 75,
|
width: 75,
|
||||||
padding: EdgeInsets.only(left: 10, right: 10),
|
padding: EdgeInsets.only(
|
||||||
|
left: 10, right: 10),
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.all(Radius.circular(20)),
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(20)),
|
||||||
color: Colors.transparent,
|
color: Colors.transparent,
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
|
@ -248,17 +266,18 @@ class BaseExchangeWidgetState extends State<BaseExchangeWidget> {
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
color: Theme.of(context).primaryTextTheme.display3.color
|
color: Theme.of(context)
|
||||||
|
.primaryTextTheme
|
||||||
|
.display3
|
||||||
|
.color),
|
||||||
|
),
|
||||||
|
)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
Observer(builder: (_) {
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Observer(
|
|
||||||
builder: (_) {
|
|
||||||
final templates = exchangeViewModel.templates;
|
final templates = exchangeViewModel.templates;
|
||||||
final itemCount = exchangeViewModel.templates.length;
|
final itemCount =
|
||||||
|
exchangeViewModel.templates.length;
|
||||||
|
|
||||||
return ListView.builder(
|
return ListView.builder(
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
|
@ -274,7 +293,8 @@ class BaseExchangeWidgetState extends State<BaseExchangeWidget> {
|
||||||
from: template.depositCurrency,
|
from: template.depositCurrency,
|
||||||
to: template.receiveCurrency,
|
to: template.receiveCurrency,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
applyTemplate(exchangeViewModel, template);
|
applyTemplate(
|
||||||
|
exchangeViewModel, template);
|
||||||
},
|
},
|
||||||
onRemove: () {
|
onRemove: () {
|
||||||
showDialog<void>(
|
showDialog<void>(
|
||||||
|
@ -296,17 +316,14 @@ class BaseExchangeWidgetState extends State<BaseExchangeWidget> {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
);
|
}),
|
||||||
}
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
)
|
)))
|
||||||
)
|
|
||||||
)
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
|
bottomSectionPadding:
|
||||||
|
EdgeInsets.only(left: 24, right: 24, bottom: 24),
|
||||||
bottomSection: Column(children: <Widget>[
|
bottomSection: Column(children: <Widget>[
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.only(bottom: 15),
|
padding: EdgeInsets.only(bottom: 15),
|
||||||
|
@ -319,10 +336,12 @@ class BaseExchangeWidgetState extends State<BaseExchangeWidget> {
|
||||||
child: Text(
|
child: Text(
|
||||||
description,
|
description,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Theme.of(context).primaryTextTheme.display4.decorationColor,
|
color: Theme.of(context)
|
||||||
|
.primaryTextTheme
|
||||||
|
.display4
|
||||||
|
.decorationColor,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
fontSize: 12
|
fontSize: 12),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
|
@ -333,20 +352,20 @@ class BaseExchangeWidgetState extends State<BaseExchangeWidget> {
|
||||||
if (_formKey.currentState.validate()) {
|
if (_formKey.currentState.validate()) {
|
||||||
exchangeViewModel.exchangeTemplateStore.addTemplate(
|
exchangeViewModel.exchangeTemplateStore.addTemplate(
|
||||||
amount: exchangeViewModel.depositAmount,
|
amount: exchangeViewModel.depositAmount,
|
||||||
depositCurrency: exchangeViewModel.depositCurrency.toString(),
|
depositCurrency:
|
||||||
receiveCurrency: exchangeViewModel.receiveCurrency.toString(),
|
exchangeViewModel.depositCurrency.toString(),
|
||||||
|
receiveCurrency:
|
||||||
|
exchangeViewModel.receiveCurrency.toString(),
|
||||||
provider: exchangeViewModel.provider.toString(),
|
provider: exchangeViewModel.provider.toString(),
|
||||||
depositAddress: exchangeViewModel.depositAddress,
|
depositAddress: exchangeViewModel.depositAddress,
|
||||||
receiveAddress: exchangeViewModel.receiveAddress
|
receiveAddress: exchangeViewModel.receiveAddress);
|
||||||
);
|
|
||||||
exchangeViewModel.exchangeTemplateStore.update();
|
exchangeViewModel.exchangeTemplateStore.update();
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
text: S.of(context).save,
|
text: S.of(context).save,
|
||||||
color: Colors.green,
|
color: Colors.green,
|
||||||
textColor: Colors.white
|
textColor: Colors.white)
|
||||||
)
|
|
||||||
: Observer(
|
: Observer(
|
||||||
builder: (_) => LoadingPrimaryButton(
|
builder: (_) => LoadingPrimaryButton(
|
||||||
text: S.of(context).exchange,
|
text: S.of(context).exchange,
|
||||||
|
@ -413,8 +432,10 @@ class BaseExchangeWidgetState extends State<BaseExchangeWidget> {
|
||||||
key.currentState.changeLimits(min: min, max: max);
|
key.currentState.changeLimits(min: min, max: max);
|
||||||
}
|
}
|
||||||
|
|
||||||
_onCurrencyChange(exchangeViewModel.receiveCurrency, exchangeViewModel, receiveKey);
|
_onCurrencyChange(
|
||||||
_onCurrencyChange(exchangeViewModel.depositCurrency, exchangeViewModel, depositKey);
|
exchangeViewModel.receiveCurrency, exchangeViewModel, receiveKey);
|
||||||
|
_onCurrencyChange(
|
||||||
|
exchangeViewModel.depositCurrency, exchangeViewModel, depositKey);
|
||||||
|
|
||||||
reaction(
|
reaction(
|
||||||
(_) => exchangeViewModel.wallet.name,
|
(_) => exchangeViewModel.wallet.name,
|
||||||
|
@ -448,7 +469,8 @@ class BaseExchangeWidgetState extends State<BaseExchangeWidget> {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
reaction((_) => exchangeViewModel.isDepositAddressEnabled, (bool isEnabled) {
|
reaction((_) => exchangeViewModel.isDepositAddressEnabled,
|
||||||
|
(bool isEnabled) {
|
||||||
depositKey.currentState.isAddressEditable(isEditable: isEnabled);
|
depositKey.currentState.isAddressEditable(isEditable: isEnabled);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -464,7 +486,8 @@ class BaseExchangeWidgetState extends State<BaseExchangeWidget> {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
reaction((_) => exchangeViewModel.isReceiveAddressEnabled, (bool isEnabled) {
|
reaction((_) => exchangeViewModel.isReceiveAddressEnabled,
|
||||||
|
(bool isEnabled) {
|
||||||
receiveKey.currentState.isAddressEditable(isEditable: isEnabled);
|
receiveKey.currentState.isAddressEditable(isEditable: isEnabled);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -478,14 +501,12 @@ class BaseExchangeWidgetState extends State<BaseExchangeWidget> {
|
||||||
alertTitle: S.of(context).error,
|
alertTitle: S.of(context).error,
|
||||||
alertContent: state.error,
|
alertContent: state.error,
|
||||||
buttonText: S.of(context).ok,
|
buttonText: S.of(context).ok,
|
||||||
buttonAction: () => Navigator.of(context).pop()
|
buttonAction: () => Navigator.of(context).pop());
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (state is TradeIsCreatedSuccessfully) {
|
if (state is TradeIsCreatedSuccessfully) {
|
||||||
Navigator.of(context)
|
Navigator.of(context).pushNamed(Routes.exchangeConfirm);
|
||||||
.pushNamed(Routes.exchangeConfirm);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -517,7 +538,8 @@ class BaseExchangeWidgetState extends State<BaseExchangeWidget> {
|
||||||
|
|
||||||
depositAmountController.addListener(() {
|
depositAmountController.addListener(() {
|
||||||
if (depositAmountController.text != exchangeViewModel.depositAmount) {
|
if (depositAmountController.text != exchangeViewModel.depositAmount) {
|
||||||
exchangeViewModel.changeDepositAmount(amount: depositAmountController.text);
|
exchangeViewModel.changeDepositAmount(
|
||||||
|
amount: depositAmountController.text);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -526,7 +548,8 @@ class BaseExchangeWidgetState extends State<BaseExchangeWidget> {
|
||||||
|
|
||||||
receiveAmountController.addListener(() {
|
receiveAmountController.addListener(() {
|
||||||
if (receiveAmountController.text != exchangeViewModel.receiveAmount) {
|
if (receiveAmountController.text != exchangeViewModel.receiveAmount) {
|
||||||
exchangeViewModel.changeReceiveAmount(amount: receiveAmountController.text);
|
exchangeViewModel.changeReceiveAmount(
|
||||||
|
amount: receiveAmountController.text);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -544,30 +567,27 @@ class BaseExchangeWidgetState extends State<BaseExchangeWidget> {
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onCurrencyChange(CryptoCurrency currency,
|
void _onCurrencyChange(CryptoCurrency currency,
|
||||||
ExchangeViewModel exchangeViewModel,
|
ExchangeViewModel exchangeViewModel, GlobalKey<ExchangeCardState> key) {
|
||||||
GlobalKey<ExchangeCardState> key) {
|
|
||||||
final isCurrentTypeWallet = currency == exchangeViewModel.wallet.currency;
|
final isCurrentTypeWallet = currency == exchangeViewModel.wallet.currency;
|
||||||
|
|
||||||
key.currentState.changeSelectedCurrency(currency);
|
key.currentState.changeSelectedCurrency(currency);
|
||||||
key.currentState
|
key.currentState.changeWalletName(
|
||||||
.changeWalletName(isCurrentTypeWallet
|
isCurrentTypeWallet ? exchangeViewModel.wallet.name : null);
|
||||||
? exchangeViewModel.wallet.name : null);
|
|
||||||
|
|
||||||
key.currentState
|
key.currentState.changeAddress(
|
||||||
.changeAddress(address: isCurrentTypeWallet
|
address: isCurrentTypeWallet ? exchangeViewModel.wallet.address : '');
|
||||||
? exchangeViewModel.wallet.address : '');
|
|
||||||
|
|
||||||
key.currentState.changeAmount(amount: '');
|
key.currentState.changeAmount(amount: '');
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onWalletNameChange(ExchangeViewModel exchangeViewModel,
|
void _onWalletNameChange(ExchangeViewModel exchangeViewModel,
|
||||||
CryptoCurrency currency,
|
CryptoCurrency currency, GlobalKey<ExchangeCardState> key) {
|
||||||
GlobalKey<ExchangeCardState> key) {
|
|
||||||
final isCurrentTypeWallet = currency == exchangeViewModel.wallet.currency;
|
final isCurrentTypeWallet = currency == exchangeViewModel.wallet.currency;
|
||||||
|
|
||||||
if (isCurrentTypeWallet) {
|
if (isCurrentTypeWallet) {
|
||||||
key.currentState.changeWalletName(exchangeViewModel.wallet.name);
|
key.currentState.changeWalletName(exchangeViewModel.wallet.name);
|
||||||
key.currentState.addressController.text = exchangeViewModel.wallet.address;
|
key.currentState.addressController.text =
|
||||||
|
exchangeViewModel.wallet.address;
|
||||||
} else if (key.currentState.addressController.text ==
|
} else if (key.currentState.addressController.text ==
|
||||||
exchangeViewModel.wallet.address) {
|
exchangeViewModel.wallet.address) {
|
||||||
key.currentState.changeWalletName(null);
|
key.currentState.changeWalletName(null);
|
||||||
|
|
|
@ -74,15 +74,41 @@ class NodeListPage extends BasePage {
|
||||||
}
|
}
|
||||||
|
|
||||||
final node = nodeListViewModel.nodes[index];
|
final node = nodeListViewModel.nodes[index];
|
||||||
final isSelected = index == 1; // FIXME: hardcoded value.
|
|
||||||
final nodeListRow = NodeListRow(
|
final nodeListRow = NodeListRow(
|
||||||
title: node.uri,
|
title: node.value.uri,
|
||||||
isSelected: isSelected,
|
isSelected: node.isSelected,
|
||||||
isAlive: node.requestNode(),
|
isAlive: node.value.requestNode(),
|
||||||
onTap: (_) {});
|
onTap: (_) async {
|
||||||
|
if (node.isSelected) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await showDialog<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
content: Text(
|
||||||
|
S.of(context).change_current_node(node.value.uri),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
actions: <Widget>[
|
||||||
|
FlatButton(
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
child: Text(S.of(context).cancel)),
|
||||||
|
FlatButton(
|
||||||
|
onPressed: () async {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
await nodeListViewModel
|
||||||
|
.setAsCurrent(node.value);
|
||||||
|
},
|
||||||
|
child: Text(S.of(context).change)),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
final dismissibleRow = Dismissible(
|
final dismissibleRow = Dismissible(
|
||||||
key: Key('${node.key}'),
|
key: Key('${node.keyIndex}'),
|
||||||
confirmDismiss: (direction) async {
|
confirmDismiss: (direction) async {
|
||||||
return await showDialog(
|
return await showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
|
@ -99,7 +125,7 @@ class NodeListPage extends BasePage {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onDismissed: (direction) async =>
|
onDismissed: (direction) async =>
|
||||||
nodeListViewModel.delete(node),
|
nodeListViewModel.delete(node.value),
|
||||||
direction: DismissDirection.endToStart,
|
direction: DismissDirection.endToStart,
|
||||||
background: Container(
|
background: Container(
|
||||||
padding: EdgeInsets.only(right: 10.0),
|
padding: EdgeInsets.only(right: 10.0),
|
||||||
|
@ -120,7 +146,7 @@ class NodeListPage extends BasePage {
|
||||||
)),
|
)),
|
||||||
child: nodeListRow);
|
child: nodeListRow);
|
||||||
|
|
||||||
return isSelected ? nodeListRow : dismissibleRow;
|
return node.isSelected ? nodeListRow : dismissibleRow;
|
||||||
},
|
},
|
||||||
itemCounter: (int sectionIndex) {
|
itemCounter: (int sectionIndex) {
|
||||||
if (sectionIndex == 0) {
|
if (sectionIndex == 0) {
|
||||||
|
|
|
@ -126,7 +126,11 @@ class PinCodeState<T extends PinCodeWidget> extends State<T> {
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
color: isFilled
|
color: isFilled
|
||||||
? Theme.of(context).primaryTextTheme.title.color
|
? Theme.of(context).primaryTextTheme.title.color
|
||||||
: Theme.of(context).accentTextTheme.body1.color,
|
: Theme.of(context)
|
||||||
|
.accentTextTheme
|
||||||
|
.body1
|
||||||
|
.color
|
||||||
|
.withOpacity(0.25),
|
||||||
));
|
));
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
@ -144,7 +148,10 @@ class PinCodeState<T extends PinCodeWidget> extends State<T> {
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 14.0,
|
fontSize: 14.0,
|
||||||
fontWeight: FontWeight.normal,
|
fontWeight: FontWeight.normal,
|
||||||
color: Theme.of(context).accentTextTheme.body1.decorationColor),
|
color: Theme.of(context)
|
||||||
|
.accentTextTheme
|
||||||
|
.body1
|
||||||
|
.decorationColor),
|
||||||
))
|
))
|
||||||
],
|
],
|
||||||
Spacer(flex: 1),
|
Spacer(flex: 1),
|
||||||
|
|
|
@ -1,16 +1,40 @@
|
||||||
import 'package:cake_wallet/view_model/send_view_model.dart';
|
import 'dart:ui';
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||||
|
import 'package:mobx/mobx.dart';
|
||||||
|
import 'package:cake_wallet/palette.dart';
|
||||||
|
import 'package:cake_wallet/routes.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/auth/auth_page.dart';
|
||||||
|
import 'package:cake_wallet/src/widgets/address_text_field.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/send/send_view_model.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||||
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
|
import 'package:cake_wallet/src/widgets/top_panel.dart';
|
||||||
|
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
||||||
|
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/send/widgets/confirm_sending_alert.dart';
|
||||||
|
import 'package:cake_wallet/view_model/send/send_view_model_state.dart';
|
||||||
|
import 'package:cake_wallet/src/widgets/trail_button.dart';
|
||||||
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/send/widgets/base_send_widget.dart';
|
import 'package:cake_wallet/src/screens/send/widgets/base_send_widget.dart';
|
||||||
|
|
||||||
|
// FIXME: Refactor this screen.
|
||||||
|
|
||||||
class SendPage extends BasePage {
|
class SendPage extends BasePage {
|
||||||
SendPage({@required this.sendViewModel});
|
SendPage({@required this.sendViewModel});
|
||||||
|
|
||||||
final SendViewModel sendViewModel;
|
final SendViewModel sendViewModel;
|
||||||
|
|
||||||
|
// ???
|
||||||
@override
|
@override
|
||||||
String get title => sendViewModel.pageTitle;
|
String get title => 'SEND';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Color get titleColor => Colors.white;
|
Color get titleColor => Colors.white;
|
||||||
|
@ -18,15 +42,66 @@ class SendPage extends BasePage {
|
||||||
@override
|
@override
|
||||||
Color get backgroundLightColor => Colors.transparent;
|
Color get backgroundLightColor => Colors.transparent;
|
||||||
|
|
||||||
@override
|
|
||||||
Color get backgroundDarkColor => Colors.transparent;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool get resizeToAvoidBottomPadding => false;
|
bool get resizeToAvoidBottomPadding => false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget body(BuildContext context) =>
|
Widget trailing(context) => TrailButton(
|
||||||
BaseSendWidget(
|
caption: S.of(context).clear, onPressed: () => sendViewModel.reset());
|
||||||
|
Color get backgroundDarkColor => Colors.transparent;
|
||||||
|
|
||||||
|
// @override
|
||||||
|
// State<StatefulWidget> createState() => SendFormState();
|
||||||
|
|
||||||
|
final _addressController = TextEditingController();
|
||||||
|
final _cryptoAmountController = TextEditingController();
|
||||||
|
final _fiatAmountController = TextEditingController();
|
||||||
|
|
||||||
|
final _focusNode = FocusNode();
|
||||||
|
|
||||||
|
bool _effectsInstalled = false;
|
||||||
|
|
||||||
|
final _formKey = GlobalKey<FormState>();
|
||||||
|
|
||||||
|
// @override
|
||||||
|
// void initState() {
|
||||||
|
// _focusNode.addListener(() {
|
||||||
|
// if (!_focusNode.hasFocus && _addressController.text.isNotEmpty) {
|
||||||
|
// getOpenaliasRecord(context);
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
// super.initState();
|
||||||
|
// }
|
||||||
|
|
||||||
|
Future<void> getOpenaliasRecord(BuildContext context) async {
|
||||||
|
// final sendStore = Provider.of<SendStore>(context);
|
||||||
|
// final isOpenalias =
|
||||||
|
// await sendStore.isOpenaliasRecord(_addressController.text);
|
||||||
|
//
|
||||||
|
// if (isOpenalias) {
|
||||||
|
// _addressController.text = sendStore.recordAddress;
|
||||||
|
//
|
||||||
|
// await showDialog<void>(
|
||||||
|
// context: context,
|
||||||
|
// builder: (BuildContext context) {
|
||||||
|
// return AlertWithOneAction(
|
||||||
|
// alertTitle: S.of(context).openalias_alert_title,
|
||||||
|
// alertContent:
|
||||||
|
// S.of(context).openalias_alert_content(sendStore.recordName),
|
||||||
|
// buttonText: S.of(context).ok,
|
||||||
|
// buttonAction: () => Navigator.of(context).pop());
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
// @override
|
||||||
|
// Widget body(BuildContext context) {
|
||||||
|
// return super.build(context);
|
||||||
|
// }
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget body(BuildContext context) => BaseSendWidget(
|
||||||
sendViewModel: sendViewModel,
|
sendViewModel: sendViewModel,
|
||||||
leading: leading(context),
|
leading: leading(context),
|
||||||
middle: middle(context),
|
middle: middle(context),
|
||||||
|
@ -37,9 +112,532 @@ class SendPage extends BasePage {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
resizeToAvoidBottomPadding: resizeToAvoidBottomPadding,
|
resizeToAvoidBottomPadding: resizeToAvoidBottomPadding,
|
||||||
body: Container(
|
body: Container(
|
||||||
color: Theme.of(context).backgroundColor,
|
color: Theme.of(context).backgroundColor, child: body(context)));
|
||||||
child: body(context)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @override
|
||||||
|
// Widget build(BuildContext context) {
|
||||||
|
// _setEffects(context);
|
||||||
|
|
||||||
|
// return Container(
|
||||||
|
// color: Theme.of(context).backgroundColor,
|
||||||
|
// child: ScrollableWithBottomSection(
|
||||||
|
// contentPadding: EdgeInsets.only(bottom: 24),
|
||||||
|
// content: Column(
|
||||||
|
// children: <Widget>[
|
||||||
|
// TopPanel(
|
||||||
|
// color: Theme.of(context).accentTextTheme.title.backgroundColor,
|
||||||
|
// widget: Form(
|
||||||
|
// key: _formKey,
|
||||||
|
// child: Column(children: <Widget>[
|
||||||
|
// AddressTextField(
|
||||||
|
// controller: _addressController,
|
||||||
|
// placeholder: 'Address',
|
||||||
|
// //S.of(context).send_monero_address, FIXME: placeholder for btc and xmr address text field.
|
||||||
|
// focusNode: _focusNode,
|
||||||
|
// onURIScanned: (uri) {
|
||||||
|
// var address = '';
|
||||||
|
// var amount = '';
|
||||||
|
|
||||||
|
// if (uri != null) {
|
||||||
|
// address = uri.path;
|
||||||
|
// amount = uri.queryParameters['tx_amount'];
|
||||||
|
// } else {
|
||||||
|
// address = uri.toString();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// _addressController.text = address;
|
||||||
|
// _cryptoAmountController.text = amount;
|
||||||
|
// },
|
||||||
|
// options: [
|
||||||
|
// AddressTextFieldOption.qrCode,
|
||||||
|
// AddressTextFieldOption.addressBook
|
||||||
|
// ],
|
||||||
|
// buttonColor: Theme.of(context).accentTextTheme.title.color,
|
||||||
|
// validator: widget.sendViewModel.addressValidator,
|
||||||
|
// ),
|
||||||
|
// Padding(
|
||||||
|
// padding: const EdgeInsets.only(top: 20),
|
||||||
|
// child: TextFormField(
|
||||||
|
// onChanged: (value) =>
|
||||||
|
// widget.sendViewModel.setCryptoAmount(value),
|
||||||
|
// style: TextStyle(
|
||||||
|
// fontSize: 16.0,
|
||||||
|
// color:
|
||||||
|
// Theme.of(context).primaryTextTheme.title.color),
|
||||||
|
// controller: _cryptoAmountController,
|
||||||
|
// keyboardType: TextInputType.numberWithOptions(
|
||||||
|
// signed: false, decimal: true),
|
||||||
|
// // inputFormatters: [
|
||||||
|
// // BlacklistingTextInputFormatter(
|
||||||
|
// // RegExp('[\\-|\\ |\\,]'))
|
||||||
|
// // ],
|
||||||
|
// decoration: InputDecoration(
|
||||||
|
// prefixIcon: Padding(
|
||||||
|
// padding: EdgeInsets.only(top: 12),
|
||||||
|
// child: Text(
|
||||||
|
// '${widget.sendViewModel.currency.toString()}:',
|
||||||
|
// style: TextStyle(
|
||||||
|
// fontSize: 16,
|
||||||
|
// fontWeight: FontWeight.w500,
|
||||||
|
// color: Theme.of(context)
|
||||||
|
// .primaryTextTheme
|
||||||
|
// .title
|
||||||
|
// .color,
|
||||||
|
// )),
|
||||||
|
// ),
|
||||||
|
// suffixIcon: Padding(
|
||||||
|
// padding: EdgeInsets.only(bottom: 5),
|
||||||
|
// child: Container(
|
||||||
|
// height: 32,
|
||||||
|
// width: 32,
|
||||||
|
// margin: EdgeInsets.only(
|
||||||
|
// left: 12, bottom: 7, top: 4),
|
||||||
|
// decoration: BoxDecoration(
|
||||||
|
// color: Theme.of(context)
|
||||||
|
// .accentTextTheme
|
||||||
|
// .title
|
||||||
|
// .color,
|
||||||
|
// borderRadius:
|
||||||
|
// BorderRadius.all(Radius.circular(6))),
|
||||||
|
// child: InkWell(
|
||||||
|
// onTap: () => widget.sendViewModel.setAll(),
|
||||||
|
// child: Center(
|
||||||
|
// child: Text(S.of(context).all,
|
||||||
|
// textAlign: TextAlign.center,
|
||||||
|
// style: TextStyle(
|
||||||
|
// fontSize: 9,
|
||||||
|
// fontWeight: FontWeight.bold,
|
||||||
|
// color: Theme.of(context)
|
||||||
|
// .primaryTextTheme
|
||||||
|
// .caption
|
||||||
|
// .color)),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// )),
|
||||||
|
// hintStyle: TextStyle(
|
||||||
|
// fontSize: 16.0,
|
||||||
|
// color: Theme.of(context)
|
||||||
|
// .primaryTextTheme
|
||||||
|
// .title
|
||||||
|
// .color),
|
||||||
|
// hintText: '0.0000',
|
||||||
|
// focusedBorder: UnderlineInputBorder(
|
||||||
|
// borderSide: BorderSide(
|
||||||
|
// color: Theme.of(context).dividerColor,
|
||||||
|
// width: 1.0)),
|
||||||
|
// enabledBorder: UnderlineInputBorder(
|
||||||
|
// borderSide: BorderSide(
|
||||||
|
// color: Theme.of(context).dividerColor,
|
||||||
|
// width: 1.0))),
|
||||||
|
// validator: (String value) {
|
||||||
|
// if (widget.sendViewModel.all) {
|
||||||
|
// return null;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return widget.sendViewModel.amountValidator
|
||||||
|
// .call(value);
|
||||||
|
// }),
|
||||||
|
// ),
|
||||||
|
// Padding(
|
||||||
|
// padding: const EdgeInsets.only(top: 20),
|
||||||
|
// child: TextFormField(
|
||||||
|
// onChanged: (value) =>
|
||||||
|
// widget.sendViewModel.setFiatAmount(value),
|
||||||
|
// style: TextStyle(
|
||||||
|
// fontSize: 16.0,
|
||||||
|
// color:
|
||||||
|
// Theme.of(context).primaryTextTheme.title.color),
|
||||||
|
// controller: _fiatAmountController,
|
||||||
|
// keyboardType: TextInputType.numberWithOptions(
|
||||||
|
// signed: false, decimal: true),
|
||||||
|
// // inputFormatters: [
|
||||||
|
// // BlacklistingTextInputFormatter(
|
||||||
|
// // RegExp('[\\-|\\ |\\,]'))
|
||||||
|
// // ],
|
||||||
|
// decoration: InputDecoration(
|
||||||
|
// prefixIcon: Padding(
|
||||||
|
// padding: EdgeInsets.only(top: 12),
|
||||||
|
// child: Text(
|
||||||
|
// '${widget.sendViewModel.fiat.toString()}:',
|
||||||
|
// style: TextStyle(
|
||||||
|
// fontSize: 16,
|
||||||
|
// fontWeight: FontWeight.w500,
|
||||||
|
// color: Theme.of(context)
|
||||||
|
// .primaryTextTheme
|
||||||
|
// .title
|
||||||
|
// .color,
|
||||||
|
// )),
|
||||||
|
// ),
|
||||||
|
// hintStyle: TextStyle(
|
||||||
|
// fontSize: 16.0,
|
||||||
|
// color: Theme.of(context)
|
||||||
|
// .primaryTextTheme
|
||||||
|
// .caption
|
||||||
|
// .color),
|
||||||
|
// hintText: '0.00',
|
||||||
|
// focusedBorder: UnderlineInputBorder(
|
||||||
|
// borderSide: BorderSide(
|
||||||
|
// color: Theme.of(context).dividerColor,
|
||||||
|
// width: 1.0)),
|
||||||
|
// enabledBorder: UnderlineInputBorder(
|
||||||
|
// borderSide: BorderSide(
|
||||||
|
// color: Theme.of(context).dividerColor,
|
||||||
|
// width: 1.0)))),
|
||||||
|
// ),
|
||||||
|
// Padding(
|
||||||
|
// padding: const EdgeInsets.only(top: 20),
|
||||||
|
// child: Row(
|
||||||
|
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
// children: <Widget>[
|
||||||
|
// Text(S.of(context).send_estimated_fee,
|
||||||
|
// style: TextStyle(
|
||||||
|
// fontSize: 12,
|
||||||
|
// fontWeight: FontWeight.w600,
|
||||||
|
// color: Theme.of(context)
|
||||||
|
// .primaryTextTheme
|
||||||
|
// .title
|
||||||
|
// .color,
|
||||||
|
// )),
|
||||||
|
// Text(
|
||||||
|
// '${widget.sendViewModel.estimatedFee} ${widget.sendViewModel.currency.toString()}',
|
||||||
|
// style: TextStyle(
|
||||||
|
// fontSize: 12,
|
||||||
|
// fontWeight: FontWeight.w600,
|
||||||
|
// color: Theme.of(context)
|
||||||
|
// .primaryTextTheme
|
||||||
|
// .title
|
||||||
|
// .color,
|
||||||
|
// ))
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
// )
|
||||||
|
// ]),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// // Padding(
|
||||||
|
// // padding: EdgeInsets.only(top: 32, left: 24, bottom: 24),
|
||||||
|
// // child: Row(
|
||||||
|
// // mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
// // children: <Widget>[
|
||||||
|
// // Text(
|
||||||
|
// // S.of(context).send_templates,
|
||||||
|
// // style: TextStyle(
|
||||||
|
// // fontSize: 18,
|
||||||
|
// // fontWeight: FontWeight.w600,
|
||||||
|
// // color:
|
||||||
|
// // Theme.of(context).primaryTextTheme.caption.color),
|
||||||
|
// // )
|
||||||
|
// // ],
|
||||||
|
// // ),
|
||||||
|
// // ),
|
||||||
|
// // Container(
|
||||||
|
// // height: 40,
|
||||||
|
// // width: double.infinity,
|
||||||
|
// // padding: EdgeInsets.only(left: 24),
|
||||||
|
// // child: Observer(builder: (_) {
|
||||||
|
// // final itemCount = sendTemplateStore.templates.length + 1;
|
||||||
|
// //
|
||||||
|
// // return ListView.builder(
|
||||||
|
// // scrollDirection: Axis.horizontal,
|
||||||
|
// // itemCount: itemCount,
|
||||||
|
// // itemBuilder: (context, index) {
|
||||||
|
// // if (index == 0) {
|
||||||
|
// // return GestureDetector(
|
||||||
|
// // onTap: () => Navigator.of(context)
|
||||||
|
// // .pushNamed(Routes.sendTemplate),
|
||||||
|
// // child: Container(
|
||||||
|
// // padding: EdgeInsets.only(right: 10),
|
||||||
|
// // child: DottedBorder(
|
||||||
|
// // borderType: BorderType.RRect,
|
||||||
|
// // dashPattern: [8, 4],
|
||||||
|
// // color: Theme.of(context)
|
||||||
|
// // .accentTextTheme
|
||||||
|
// // .title
|
||||||
|
// // .backgroundColor,
|
||||||
|
// // strokeWidth: 2,
|
||||||
|
// // radius: Radius.circular(20),
|
||||||
|
// // child: Container(
|
||||||
|
// // height: 40,
|
||||||
|
// // width: 75,
|
||||||
|
// // padding: EdgeInsets.only(left: 10, right: 10),
|
||||||
|
// // alignment: Alignment.center,
|
||||||
|
// // decoration: BoxDecoration(
|
||||||
|
// // borderRadius:
|
||||||
|
// // BorderRadius.all(Radius.circular(20)),
|
||||||
|
// // color: Colors.transparent,
|
||||||
|
// // ),
|
||||||
|
// // child: Text(
|
||||||
|
// // S.of(context).send_new,
|
||||||
|
// // style: TextStyle(
|
||||||
|
// // fontSize: 14,
|
||||||
|
// // fontWeight: FontWeight.w600,
|
||||||
|
// // color: Theme.of(context)
|
||||||
|
// // .primaryTextTheme
|
||||||
|
// // .caption
|
||||||
|
// // .color),
|
||||||
|
// // ),
|
||||||
|
// // )),
|
||||||
|
// // ),
|
||||||
|
// // );
|
||||||
|
// // }
|
||||||
|
// //
|
||||||
|
// // index -= 1;
|
||||||
|
// //
|
||||||
|
// // final template = sendTemplateStore.templates[index];
|
||||||
|
// //
|
||||||
|
// // return TemplateTile(
|
||||||
|
// // to: template.name,
|
||||||
|
// // amount: template.amount,
|
||||||
|
// // from: template.cryptoCurrency,
|
||||||
|
// // onTap: () {
|
||||||
|
// // _addressController.text = template.address;
|
||||||
|
// // _cryptoAmountController.text = template.amount;
|
||||||
|
// // getOpenaliasRecord(context);
|
||||||
|
// // });
|
||||||
|
// // });
|
||||||
|
// // }),
|
||||||
|
// // )
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
// bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
|
||||||
|
// bottomSection: Observer(builder: (_) {
|
||||||
|
// return LoadingPrimaryButton(
|
||||||
|
// onPressed: () async {
|
||||||
|
// FocusScope.of(context).requestFocus(FocusNode());
|
||||||
|
|
||||||
|
// if (!_formKey.currentState.validate()) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// await showDialog<void>(
|
||||||
|
// context: context,
|
||||||
|
// builder: (dialogContext) {
|
||||||
|
// return AlertWithTwoActions(
|
||||||
|
// alertTitle: S.of(context).send_creating_transaction,
|
||||||
|
// alertContent: S.of(context).confirm_sending,
|
||||||
|
// leftButtonText: S.of(context).send,
|
||||||
|
// rightButtonText: S.of(context).cancel,
|
||||||
|
// actionLeftButton: () async {
|
||||||
|
// await Navigator.of(dialogContext)
|
||||||
|
// .popAndPushNamed(Routes.auth, arguments:
|
||||||
|
// (bool isAuthenticatedSuccessfully,
|
||||||
|
// AuthPageState auth) {
|
||||||
|
// if (!isAuthenticatedSuccessfully) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Navigator.of(auth.context).pop();
|
||||||
|
// widget.sendViewModel.createTransaction();
|
||||||
|
// });
|
||||||
|
// },
|
||||||
|
// actionRightButton: () => Navigator.of(context).pop());
|
||||||
|
// });
|
||||||
|
// },
|
||||||
|
// text: S.of(context).send,
|
||||||
|
// color: Colors.blue,
|
||||||
|
// textColor: Colors.white,
|
||||||
|
// isLoading: widget.sendViewModel.state is TransactionIsCreating ||
|
||||||
|
// widget.sendViewModel.state is TransactionCommitting,
|
||||||
|
// isDisabled: !widget.sendViewModel.isReadyForSend);
|
||||||
|
// }),
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
|
// void _setEffects(BuildContext context) {
|
||||||
|
// if (_effectsInstalled) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// reaction((_) => widget.sendViewModel.all, (bool all) {
|
||||||
|
// if (all) {
|
||||||
|
// _cryptoAmountController.text = S.current.all;
|
||||||
|
// _fiatAmountController.text = null;
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
// reaction((_) => widget.sendViewModel.fiatAmount, (String amount) {
|
||||||
|
// if (amount != _fiatAmountController.text) {
|
||||||
|
// _fiatAmountController.text = amount;
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
// reaction((_) => widget.sendViewModel.cryptoAmount, (String amount) {
|
||||||
|
// if (widget.sendViewModel.all && amount != S.current.all) {
|
||||||
|
// widget.sendViewModel.all = false;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (amount != _cryptoAmountController.text) {
|
||||||
|
// _cryptoAmountController.text = amount;
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
// reaction((_) => widget.sendViewModel.address, (String address) {
|
||||||
|
// if (address != _addressController.text) {
|
||||||
|
// _addressController.text = address;
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
// _addressController.addListener(() {
|
||||||
|
// final address = _addressController.text;
|
||||||
|
|
||||||
|
// if (widget.sendViewModel.address != address) {
|
||||||
|
// widget.sendViewModel.address = address;
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
// reaction((_) => widget.sendViewModel.state, (SendViewModelState state) {
|
||||||
|
// if (state is SendingFailed) {
|
||||||
|
// WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
// showDialog<void>(
|
||||||
|
// context: context,
|
||||||
|
// builder: (BuildContext context) {
|
||||||
|
// return AlertWithOneAction(
|
||||||
|
// alertTitle: S.of(context).error,
|
||||||
|
// alertContent: state.error,
|
||||||
|
// buttonText: S.of(context).ok,
|
||||||
|
// buttonAction: () => Navigator.of(context).pop());
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (state is TransactionCreatedSuccessfully) {
|
||||||
|
// WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
// showDialog<void>(
|
||||||
|
// context: context,
|
||||||
|
// builder: (BuildContext context) {
|
||||||
|
// return ConfirmSendingAlert(
|
||||||
|
// alertTitle: S.of(context).confirm_sending,
|
||||||
|
// amount: S.of(context).send_amount,
|
||||||
|
// amountValue:
|
||||||
|
// widget.sendViewModel.pendingTransaction.amountFormatted,
|
||||||
|
// fee: S.of(context).send_fee,
|
||||||
|
// feeValue:
|
||||||
|
// widget.sendViewModel.pendingTransaction.feeFormatted,
|
||||||
|
// leftButtonText: S.of(context).ok,
|
||||||
|
// rightButtonText: S.of(context).cancel,
|
||||||
|
// actionLeftButton: () {
|
||||||
|
// Navigator.of(context).pop();
|
||||||
|
// widget.sendViewModel.commitTransaction();
|
||||||
|
// showDialog<void>(
|
||||||
|
// context: context,
|
||||||
|
// builder: (BuildContext context) {
|
||||||
|
// return Observer(builder: (_) {
|
||||||
|
// final state = widget.sendViewModel.state;
|
||||||
|
|
||||||
|
// if (state is TransactionCommitted) {
|
||||||
|
// return Stack(
|
||||||
|
// children: <Widget>[
|
||||||
|
// Container(
|
||||||
|
// color: Theme.of(context).backgroundColor,
|
||||||
|
// child: Center(
|
||||||
|
// child: Image.asset(
|
||||||
|
// 'assets/images/birthday_cake.png'),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// Center(
|
||||||
|
// child: Padding(
|
||||||
|
// padding: EdgeInsets.only(
|
||||||
|
// top: 220, left: 24, right: 24),
|
||||||
|
// child: Text(
|
||||||
|
// S.of(context).send_success,
|
||||||
|
// textAlign: TextAlign.center,
|
||||||
|
// style: TextStyle(
|
||||||
|
// fontSize: 22,
|
||||||
|
// fontWeight: FontWeight.bold,
|
||||||
|
// color: Theme.of(context)
|
||||||
|
// .primaryTextTheme
|
||||||
|
// .title
|
||||||
|
// .color,
|
||||||
|
// decoration: TextDecoration.none,
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// Positioned(
|
||||||
|
// left: 24,
|
||||||
|
// right: 24,
|
||||||
|
// bottom: 24,
|
||||||
|
// child: PrimaryButton(
|
||||||
|
// onPressed: () =>
|
||||||
|
// Navigator.of(context).pop(),
|
||||||
|
// text: S.of(context).send_got_it,
|
||||||
|
// color: Colors.blue,
|
||||||
|
// textColor: Colors.white))
|
||||||
|
// ],
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return Stack(
|
||||||
|
// children: <Widget>[
|
||||||
|
// Container(
|
||||||
|
// color: Theme.of(context).backgroundColor,
|
||||||
|
// child: Center(
|
||||||
|
// child: Image.asset(
|
||||||
|
// 'assets/images/birthday_cake.png'),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// BackdropFilter(
|
||||||
|
// filter: ImageFilter.blur(
|
||||||
|
// sigmaX: 3.0, sigmaY: 3.0),
|
||||||
|
// child: Container(
|
||||||
|
// decoration: BoxDecoration(
|
||||||
|
// color: Theme.of(context)
|
||||||
|
// .backgroundColor
|
||||||
|
// .withOpacity(0.25)),
|
||||||
|
// child: Center(
|
||||||
|
// child: Padding(
|
||||||
|
// padding: EdgeInsets.only(top: 220),
|
||||||
|
// child: Text(
|
||||||
|
// S.of(context).send_sending,
|
||||||
|
// textAlign: TextAlign.center,
|
||||||
|
// style: TextStyle(
|
||||||
|
// fontSize: 22,
|
||||||
|
// fontWeight: FontWeight.bold,
|
||||||
|
// color: Theme.of(context)
|
||||||
|
// .primaryTextTheme
|
||||||
|
// .title
|
||||||
|
// .color,
|
||||||
|
// decoration: TextDecoration.none,
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// )
|
||||||
|
// ],
|
||||||
|
// );
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
// },
|
||||||
|
// actionRightButton: () => Navigator.of(context).pop());
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (state is TransactionCommitted) {
|
||||||
|
// WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
// _addressController.text = '';
|
||||||
|
// _cryptoAmountController.text = '';
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
// _effectsInstalled = true;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Widget body(BuildContext context) => BaseSendWidget(
|
||||||
|
// sendViewModel: sendViewModel,
|
||||||
|
// leading: leading(context),
|
||||||
|
// middle: middle(context),
|
||||||
|
// );
|
||||||
|
|
||||||
|
// @override
|
||||||
|
// Widget build(BuildContext context) {
|
||||||
|
// return Scaffold(
|
||||||
|
// resizeToAvoidBottomPadding: resizeToAvoidBottomPadding,
|
||||||
|
// body: Container(
|
||||||
|
// color: Theme.of(context).backgroundColor, child: body(context)));
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||||
import 'package:cake_wallet/generated/i18n.dart';
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
import 'package:cake_wallet/view_model/send_view_model.dart';
|
import 'package:cake_wallet/view_model/send/send_view_model.dart';
|
||||||
import 'package:cake_wallet/src/screens/send/widgets/base_send_widget.dart';
|
import 'package:cake_wallet/src/screens/send/widgets/base_send_widget.dart';
|
||||||
|
|
||||||
class SendTemplatePage extends BasePage {
|
class SendTemplatePage extends BasePage {
|
||||||
|
@ -26,22 +26,17 @@ class SendTemplatePage extends BasePage {
|
||||||
bool get resizeToAvoidBottomPadding => false;
|
bool get resizeToAvoidBottomPadding => false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget body(BuildContext context) =>
|
Widget body(BuildContext context) => BaseSendWidget(
|
||||||
BaseSendWidget(
|
|
||||||
sendViewModel: sendViewModel,
|
sendViewModel: sendViewModel,
|
||||||
leading: leading(context),
|
leading: leading(context),
|
||||||
middle: middle(context),
|
middle: middle(context),
|
||||||
isTemplate: true
|
isTemplate: true);
|
||||||
);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
resizeToAvoidBottomPadding: resizeToAvoidBottomPadding,
|
resizeToAvoidBottomPadding: resizeToAvoidBottomPadding,
|
||||||
body: Container(
|
body: Container(
|
||||||
color: Theme.of(context).backgroundColor,
|
color: Theme.of(context).backgroundColor, child: body(context)));
|
||||||
child: body(context)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,8 +1,12 @@
|
||||||
|
import 'dart:ui';
|
||||||
|
import 'package:cake_wallet/src/domain/common/transaction_priority.dart';
|
||||||
|
import 'package:cake_wallet/src/widgets/picker.dart';
|
||||||
import 'package:cake_wallet/src/widgets/primary_button.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/src/widgets/scollable_with_bottom_section.dart';
|
||||||
|
import 'package:cake_wallet/view_model/send/send_view_model_state.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:cake_wallet/view_model/send_view_model.dart';
|
import 'package:cake_wallet/view_model/send/send_view_model.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
|
@ -19,12 +23,11 @@ import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
|
||||||
import 'package:cake_wallet/routes.dart';
|
import 'package:cake_wallet/routes.dart';
|
||||||
|
|
||||||
class BaseSendWidget extends StatelessWidget {
|
class BaseSendWidget extends StatelessWidget {
|
||||||
BaseSendWidget({
|
BaseSendWidget(
|
||||||
@required this.sendViewModel,
|
{@required this.sendViewModel,
|
||||||
@required this.leading,
|
@required this.leading,
|
||||||
@required this.middle,
|
@required this.middle,
|
||||||
this.isTemplate = false
|
this.isTemplate = false});
|
||||||
});
|
|
||||||
|
|
||||||
final SendViewModel sendViewModel;
|
final SendViewModel sendViewModel;
|
||||||
final bool isTemplate;
|
final bool isTemplate;
|
||||||
|
@ -42,7 +45,6 @@ class BaseSendWidget extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|
||||||
_setEffects(context);
|
_setEffects(context);
|
||||||
|
|
||||||
return ScrollableWithBottomSection(
|
return ScrollableWithBottomSection(
|
||||||
|
@ -54,9 +56,7 @@ class BaseSendWidget extends StatelessWidget {
|
||||||
gradient: LinearGradient(colors: [
|
gradient: LinearGradient(colors: [
|
||||||
Theme.of(context).primaryTextTheme.subhead.color,
|
Theme.of(context).primaryTextTheme.subhead.color,
|
||||||
Theme.of(context).primaryTextTheme.subhead.decorationColor,
|
Theme.of(context).primaryTextTheme.subhead.decorationColor,
|
||||||
],
|
], begin: Alignment.topLeft, end: Alignment.bottomRight),
|
||||||
begin: Alignment.topLeft,
|
|
||||||
end: Alignment.bottomRight),
|
|
||||||
widget: Form(
|
widget: Form(
|
||||||
key: _formKey,
|
key: _formKey,
|
||||||
child: Column(children: <Widget>[
|
child: Column(children: <Widget>[
|
||||||
|
@ -74,14 +74,19 @@ class BaseSendWidget extends StatelessWidget {
|
||||||
? BaseTextFormField(
|
? BaseTextFormField(
|
||||||
controller: _nameController,
|
controller: _nameController,
|
||||||
hintText: S.of(context).send_name,
|
hintText: S.of(context).send_name,
|
||||||
borderColor: Theme.of(context).primaryTextTheme.headline.color,
|
borderColor: Theme.of(context)
|
||||||
|
.primaryTextTheme
|
||||||
|
.headline
|
||||||
|
.color,
|
||||||
textStyle: TextStyle(
|
textStyle: TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
color: Colors.white
|
color: Colors.white),
|
||||||
),
|
|
||||||
placeholderTextStyle: TextStyle(
|
placeholderTextStyle: TextStyle(
|
||||||
color: Theme.of(context).primaryTextTheme.headline.decorationColor,
|
color: Theme.of(context)
|
||||||
|
.primaryTextTheme
|
||||||
|
.headline
|
||||||
|
.decorationColor,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
fontSize: 14),
|
fontSize: 14),
|
||||||
validator: sendViewModel.templateValidator,
|
validator: sendViewModel.templateValidator,
|
||||||
|
@ -113,23 +118,25 @@ class BaseSendWidget extends StatelessWidget {
|
||||||
AddressTextFieldOption.qrCode,
|
AddressTextFieldOption.qrCode,
|
||||||
AddressTextFieldOption.addressBook
|
AddressTextFieldOption.addressBook
|
||||||
],
|
],
|
||||||
buttonColor: Theme.of(context).primaryTextTheme.display1.color,
|
buttonColor:
|
||||||
borderColor: Theme.of(context).primaryTextTheme.headline.color,
|
Theme.of(context).primaryTextTheme.display1.color,
|
||||||
|
borderColor:
|
||||||
|
Theme.of(context).primaryTextTheme.headline.color,
|
||||||
textStyle: TextStyle(
|
textStyle: TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
color: Colors.white
|
color: Colors.white),
|
||||||
),
|
|
||||||
hintStyle: TextStyle(
|
hintStyle: TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
color: Theme.of(context).primaryTextTheme.headline.decorationColor
|
color: Theme.of(context)
|
||||||
),
|
.primaryTextTheme
|
||||||
|
.headline
|
||||||
|
.decorationColor),
|
||||||
validator: sendViewModel.addressValidator,
|
validator: sendViewModel.addressValidator,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Observer(
|
Observer(builder: (_) {
|
||||||
builder: (_) {
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(top: 20),
|
padding: const EdgeInsets.only(top: 20),
|
||||||
child: BaseTextFormField(
|
child: BaseTextFormField(
|
||||||
|
@ -142,7 +149,8 @@ class BaseSendWidget extends StatelessWidget {
|
||||||
],
|
],
|
||||||
prefixIcon: Padding(
|
prefixIcon: Padding(
|
||||||
padding: EdgeInsets.only(top: 9),
|
padding: EdgeInsets.only(top: 9),
|
||||||
child: Text(sendViewModel.currency.title + ':',
|
child:
|
||||||
|
Text(sendViewModel.currency.title + ':',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
|
@ -151,66 +159,86 @@ class BaseSendWidget extends StatelessWidget {
|
||||||
),
|
),
|
||||||
suffixIcon: isTemplate
|
suffixIcon: isTemplate
|
||||||
? Offstage()
|
? Offstage()
|
||||||
: Padding(
|
: Container(
|
||||||
padding: EdgeInsets.only(bottom: 2),
|
height: 32,
|
||||||
child: Row(
|
width: 32,
|
||||||
mainAxisSize: MainAxisSize.min,
|
margin: EdgeInsets.only(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
left: 14, top: 4, bottom: 10),
|
||||||
children: <Widget>[
|
|
||||||
Container(
|
|
||||||
width: MediaQuery.of(context).size.width/2,
|
|
||||||
alignment: Alignment.centerLeft,
|
|
||||||
child: Text(
|
|
||||||
' / ' + sendViewModel.balance,
|
|
||||||
maxLines: 1,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 14,
|
|
||||||
color: Theme.of(context).primaryTextTheme.headline.decorationColor
|
|
||||||
)
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
height: 34,
|
|
||||||
width: 34,
|
|
||||||
margin: EdgeInsets.only(left: 12, bottom: 8),
|
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Theme.of(context).primaryTextTheme.display1.color,
|
color: Theme.of(context)
|
||||||
borderRadius: BorderRadius.all(Radius.circular(6))
|
.primaryTextTheme
|
||||||
),
|
.display1
|
||||||
|
.color,
|
||||||
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(6))),
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () => sendViewModel.setSendAll(),
|
onTap: () =>
|
||||||
|
sendViewModel.setSendAll(),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Text(S.of(context).all,
|
child: Text(S.of(context).all,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: Theme.of(context).primaryTextTheme.display1.decorationColor
|
color: Theme.of(context)
|
||||||
)
|
.primaryTextTheme
|
||||||
|
.display1
|
||||||
|
.decorationColor)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
hintText: '0.0000',
|
hintText: '0.0000',
|
||||||
borderColor: Theme.of(context).primaryTextTheme.headline.color,
|
borderColor: Theme.of(context)
|
||||||
|
.primaryTextTheme
|
||||||
|
.headline
|
||||||
|
.color,
|
||||||
textStyle: TextStyle(
|
textStyle: TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
color: Colors.white
|
color: Colors.white),
|
||||||
),
|
|
||||||
placeholderTextStyle: TextStyle(
|
placeholderTextStyle: TextStyle(
|
||||||
color: Theme.of(context).primaryTextTheme.headline.decorationColor,
|
color: Theme.of(context)
|
||||||
|
.primaryTextTheme
|
||||||
|
.headline
|
||||||
|
.decorationColor,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
fontSize: 14),
|
fontSize: 14),
|
||||||
validator: sendViewModel.amountValidator
|
validator: sendViewModel.amountValidator));
|
||||||
|
}),
|
||||||
|
isTemplate
|
||||||
|
? Offstage()
|
||||||
|
: Observer(
|
||||||
|
builder: (_) => Padding(
|
||||||
|
padding: EdgeInsets.only(top: 10),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
mainAxisAlignment:
|
||||||
|
MainAxisAlignment.spaceBetween,
|
||||||
|
children: <Widget>[
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
S.of(context).available_balance + ':',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.primaryTextTheme
|
||||||
|
.headline
|
||||||
|
.decorationColor),
|
||||||
|
)),
|
||||||
|
Text(
|
||||||
|
sendViewModel.balance,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.primaryTextTheme
|
||||||
|
.headline
|
||||||
|
.decorationColor),
|
||||||
)
|
)
|
||||||
);
|
],
|
||||||
}
|
|
||||||
),
|
),
|
||||||
|
)),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(top: 20),
|
padding: const EdgeInsets.only(top: 20),
|
||||||
child: BaseTextFormField(
|
child: BaseTextFormField(
|
||||||
|
@ -223,8 +251,7 @@ class BaseSendWidget extends StatelessWidget {
|
||||||
],
|
],
|
||||||
prefixIcon: Padding(
|
prefixIcon: Padding(
|
||||||
padding: EdgeInsets.only(top: 9),
|
padding: EdgeInsets.only(top: 9),
|
||||||
child: Text(
|
child: Text(sendViewModel.fiat.title + ':',
|
||||||
sendViewModel.fiat.title + ':',
|
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
|
@ -232,52 +259,63 @@ class BaseSendWidget extends StatelessWidget {
|
||||||
)),
|
)),
|
||||||
),
|
),
|
||||||
hintText: '0.00',
|
hintText: '0.00',
|
||||||
borderColor: Theme.of(context).primaryTextTheme.headline.color,
|
borderColor: Theme.of(context)
|
||||||
|
.primaryTextTheme
|
||||||
|
.headline
|
||||||
|
.color,
|
||||||
textStyle: TextStyle(
|
textStyle: TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
color: Colors.white
|
color: Colors.white),
|
||||||
),
|
|
||||||
placeholderTextStyle: TextStyle(
|
placeholderTextStyle: TextStyle(
|
||||||
color: Theme.of(context).primaryTextTheme.headline.decorationColor,
|
color: Theme.of(context)
|
||||||
|
.primaryTextTheme
|
||||||
|
.headline
|
||||||
|
.decorationColor,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
fontSize: 14),
|
fontSize: 14),
|
||||||
)
|
)),
|
||||||
),
|
|
||||||
isTemplate
|
isTemplate
|
||||||
? Offstage()
|
? Offstage()
|
||||||
: GestureDetector(
|
: Observer(
|
||||||
onTap: () {},
|
builder: (_) => GestureDetector(
|
||||||
|
onTap: () =>
|
||||||
|
_setTransactionPriority(context),
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: EdgeInsets.only(top: 24),
|
padding: EdgeInsets.only(top: 24),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment:
|
||||||
|
MainAxisAlignment.spaceBetween,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Text(S.of(context).send_estimated_fee,
|
Text(S.of(context).send_estimated_fee,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
//color: Theme.of(context).primaryTextTheme.display2.color,
|
//color: Theme.of(context).primaryTextTheme.display2.color,
|
||||||
color: Colors.white
|
color: Colors.white)),
|
||||||
)),
|
|
||||||
Container(
|
Container(
|
||||||
child: Row(
|
child: Row(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Text(
|
Text(
|
||||||
sendViewModel.estimatedFee.toString() + ' '
|
sendViewModel.estimatedFee
|
||||||
+ sendViewModel.currency.title,
|
.toString() +
|
||||||
|
' ' +
|
||||||
|
sendViewModel
|
||||||
|
.currency.title,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight:
|
||||||
|
FontWeight.w600,
|
||||||
//color: Theme.of(context).primaryTextTheme.display2.color,
|
//color: Theme.of(context).primaryTextTheme.display2.color,
|
||||||
color: Colors.white
|
color: Colors.white)),
|
||||||
)),
|
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.only(left: 5),
|
padding:
|
||||||
|
EdgeInsets.only(left: 5),
|
||||||
child: Icon(
|
child: Icon(
|
||||||
Icons.arrow_forward_ios,
|
Icons.arrow_forward_ios,
|
||||||
size: 12,
|
size: 12,
|
||||||
color: Colors.white,),
|
color: Colors.white,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -285,7 +323,7 @@ class BaseSendWidget extends StatelessWidget {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
))
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -295,11 +333,7 @@ class BaseSendWidget extends StatelessWidget {
|
||||||
isTemplate
|
isTemplate
|
||||||
? Offstage()
|
? Offstage()
|
||||||
: Padding(
|
: Padding(
|
||||||
padding: EdgeInsets.only(
|
padding: EdgeInsets.only(top: 30, left: 24, bottom: 24),
|
||||||
top: 30,
|
|
||||||
left: 24,
|
|
||||||
bottom: 24
|
|
||||||
),
|
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
|
@ -308,8 +342,10 @@ class BaseSendWidget extends StatelessWidget {
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
color: Theme.of(context).primaryTextTheme.display4.color
|
color: Theme.of(context)
|
||||||
),
|
.primaryTextTheme
|
||||||
|
.display4
|
||||||
|
.color),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -332,7 +368,10 @@ class BaseSendWidget extends StatelessWidget {
|
||||||
child: DottedBorder(
|
child: DottedBorder(
|
||||||
borderType: BorderType.RRect,
|
borderType: BorderType.RRect,
|
||||||
dashPattern: [6, 4],
|
dashPattern: [6, 4],
|
||||||
color: Theme.of(context).primaryTextTheme.display2.decorationColor,
|
color: Theme.of(context)
|
||||||
|
.primaryTextTheme
|
||||||
|
.display2
|
||||||
|
.decorationColor,
|
||||||
strokeWidth: 2,
|
strokeWidth: 2,
|
||||||
radius: Radius.circular(20),
|
radius: Radius.circular(20),
|
||||||
child: Container(
|
child: Container(
|
||||||
|
@ -341,7 +380,8 @@ class BaseSendWidget extends StatelessWidget {
|
||||||
padding: EdgeInsets.only(left: 10, right: 10),
|
padding: EdgeInsets.only(left: 10, right: 10),
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.all(Radius.circular(20)),
|
borderRadius:
|
||||||
|
BorderRadius.all(Radius.circular(20)),
|
||||||
color: Colors.transparent,
|
color: Colors.transparent,
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
|
@ -349,25 +389,26 @@ class BaseSendWidget extends StatelessWidget {
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
color: Theme.of(context).primaryTextTheme.display3.color
|
color: Theme.of(context)
|
||||||
|
.primaryTextTheme
|
||||||
|
.display3
|
||||||
|
.color),
|
||||||
|
),
|
||||||
|
)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
// Observer(
|
||||||
),
|
// builder: (_) {
|
||||||
),
|
// final templates = sendViewModel.templates;
|
||||||
),
|
// final itemCount = templates.length;
|
||||||
Observer(
|
|
||||||
builder: (_) {
|
|
||||||
final templates = sendViewModel.templates;
|
|
||||||
final itemCount = templates.length;
|
|
||||||
|
|
||||||
return ListView.builder(
|
// return 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(),
|
||||||
|
@ -414,13 +455,12 @@ class BaseSendWidget extends StatelessWidget {
|
||||||
? PrimaryButton(
|
? PrimaryButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (_formKey.currentState.validate()) {
|
if (_formKey.currentState.validate()) {
|
||||||
sendViewModel.sendTemplateStore.addTemplate(
|
// sendViewModel.sendTemplateStore.addTemplate(
|
||||||
name: _nameController.text,
|
// name: _nameController.text,
|
||||||
address: _addressController.text,
|
// address: _addressController.text,
|
||||||
cryptoCurrency: sendViewModel.currency.title,
|
// cryptoCurrency: sendViewModel.currency.title,
|
||||||
amount: _cryptoAmountController.text
|
// amount: _cryptoAmountController.text);
|
||||||
);
|
// sendViewModel.sendTemplateStore.update();
|
||||||
sendViewModel.sendTemplateStore.update();
|
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -451,6 +491,13 @@ class BaseSendWidget extends StatelessWidget {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reaction((_) => sendViewModel.sendAll, (bool all) {
|
||||||
|
if (all) {
|
||||||
|
_cryptoAmountController.text = S.current.all;
|
||||||
|
_fiatAmountController.text = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
reaction((_) => sendViewModel.fiatAmount, (String amount) {
|
reaction((_) => sendViewModel.fiatAmount, (String amount) {
|
||||||
if (amount != _fiatAmountController.text) {
|
if (amount != _fiatAmountController.text) {
|
||||||
_fiatAmountController.text = amount;
|
_fiatAmountController.text = amount;
|
||||||
|
@ -458,6 +505,10 @@ class BaseSendWidget extends StatelessWidget {
|
||||||
});
|
});
|
||||||
|
|
||||||
reaction((_) => sendViewModel.cryptoAmount, (String amount) {
|
reaction((_) => sendViewModel.cryptoAmount, (String amount) {
|
||||||
|
if (sendViewModel.sendAll && amount != S.current.all) {
|
||||||
|
sendViewModel.sendAll = false;
|
||||||
|
}
|
||||||
|
|
||||||
if (amount != _cryptoAmountController.text) {
|
if (amount != _cryptoAmountController.text) {
|
||||||
_cryptoAmountController.text = amount;
|
_cryptoAmountController.text = amount;
|
||||||
}
|
}
|
||||||
|
@ -473,29 +524,7 @@ class BaseSendWidget extends StatelessWidget {
|
||||||
final address = _addressController.text;
|
final address = _addressController.text;
|
||||||
|
|
||||||
if (sendViewModel.address != address) {
|
if (sendViewModel.address != address) {
|
||||||
sendViewModel.changeAddress(address);
|
sendViewModel.address = address;
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
_fiatAmountController.addListener(() {
|
|
||||||
final fiatAmount = _fiatAmountController.text;
|
|
||||||
|
|
||||||
if (sendViewModel.fiatAmount != fiatAmount) {
|
|
||||||
sendViewModel.changeFiatAmount(fiatAmount);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
_cryptoAmountController.addListener(() {
|
|
||||||
final cryptoAmount = _cryptoAmountController.text;
|
|
||||||
|
|
||||||
if (sendViewModel.cryptoAmount != cryptoAmount) {
|
|
||||||
sendViewModel.changeCryptoAmount(cryptoAmount);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
_focusNode.addListener(() {
|
|
||||||
if (!_focusNode.hasFocus && _addressController.text.isNotEmpty) {
|
|
||||||
getOpenaliasRecord(context);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -505,44 +534,126 @@ class BaseSendWidget extends StatelessWidget {
|
||||||
showDialog<void>(
|
showDialog<void>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return AlertDialog(
|
return AlertWithOneAction(
|
||||||
title: Text(S.of(context).error),
|
alertTitle: S.of(context).error,
|
||||||
content: Text(state.error),
|
alertContent: state.error,
|
||||||
actions: <Widget>[
|
buttonText: S.of(context).ok,
|
||||||
FlatButton(
|
buttonAction: () => Navigator.of(context).pop());
|
||||||
child: Text(S.of(context).ok),
|
|
||||||
onPressed: () => Navigator.of(context).pop())
|
|
||||||
],
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state is TransactionCreatedSuccessfully) {
|
if (state is TransactionCreatedSuccessfully) {
|
||||||
// WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
// showDialog<void>(
|
showDialog<void>(
|
||||||
// context: context,
|
context: context,
|
||||||
// builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
// return ConfirmSendingAlert(
|
return ConfirmSendingAlert(
|
||||||
// alertTitle: S.of(context).confirm_sending,
|
alertTitle: S.of(context).confirm_sending,
|
||||||
// amount: S.of(context).send_amount,
|
amount: S.of(context).send_amount,
|
||||||
// amountValue: sendStore.pendingTransaction.amount,
|
amountValue:
|
||||||
// fee: S.of(context).send_fee,
|
sendViewModel.pendingTransaction.amountFormatted,
|
||||||
// feeValue: sendStore.pendingTransaction.fee,
|
fee: S.of(context).send_fee,
|
||||||
// leftButtonText: S.of(context).ok,
|
feeValue: sendViewModel.pendingTransaction.feeFormatted,
|
||||||
// rightButtonText: S.of(context).cancel,
|
leftButtonText: S.of(context).ok,
|
||||||
// actionLeftButton: () {
|
rightButtonText: S.of(context).cancel,
|
||||||
// Navigator.of(context).pop();
|
actionLeftButton: () {
|
||||||
// sendStore.commitTransaction();
|
Navigator.of(context).pop();
|
||||||
// showDialog<void>(
|
sendViewModel.commitTransaction();
|
||||||
// context: context,
|
showDialog<void>(
|
||||||
// builder: (BuildContext context) {
|
context: context,
|
||||||
// return SendingAlert(sendStore: sendStore);
|
builder: (BuildContext context) {
|
||||||
// });
|
return Observer(builder: (_) {
|
||||||
// },
|
final state = sendViewModel.state;
|
||||||
// actionRightButton: () => Navigator.of(context).pop());
|
|
||||||
// });
|
if (state is TransactionCommitted) {
|
||||||
// });
|
return Stack(
|
||||||
|
children: <Widget>[
|
||||||
|
Container(
|
||||||
|
color: Theme.of(context).backgroundColor,
|
||||||
|
child: Center(
|
||||||
|
child: Image.asset(
|
||||||
|
'assets/images/birthday_cake.png'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Center(
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
top: 220, left: 24, right: 24),
|
||||||
|
child: Text(
|
||||||
|
S.of(context).send_success,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 22,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.primaryTextTheme
|
||||||
|
.title
|
||||||
|
.color,
|
||||||
|
decoration: TextDecoration.none,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
left: 24,
|
||||||
|
right: 24,
|
||||||
|
bottom: 24,
|
||||||
|
child: PrimaryButton(
|
||||||
|
onPressed: () =>
|
||||||
|
Navigator.of(context).pop(),
|
||||||
|
text: S.of(context).send_got_it,
|
||||||
|
color: Colors.blue,
|
||||||
|
textColor: Colors.white))
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Stack(
|
||||||
|
children: <Widget>[
|
||||||
|
Container(
|
||||||
|
color: Theme.of(context).backgroundColor,
|
||||||
|
child: Center(
|
||||||
|
child: Image.asset(
|
||||||
|
'assets/images/birthday_cake.png'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
BackdropFilter(
|
||||||
|
filter: ImageFilter.blur(
|
||||||
|
sigmaX: 3.0, sigmaY: 3.0),
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.backgroundColor
|
||||||
|
.withOpacity(0.25)),
|
||||||
|
child: Center(
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.only(top: 220),
|
||||||
|
child: Text(
|
||||||
|
S.of(context).send_sending,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 22,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.primaryTextTheme
|
||||||
|
.title
|
||||||
|
.color,
|
||||||
|
decoration: TextDecoration.none,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
actionRightButton: () => Navigator.of(context).pop());
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state is TransactionCommitted) {
|
if (state is TransactionCommitted) {
|
||||||
|
@ -557,21 +668,40 @@ class BaseSendWidget extends StatelessWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> getOpenaliasRecord(BuildContext context) async {
|
Future<void> getOpenaliasRecord(BuildContext context) async {
|
||||||
final isOpenalias = await sendViewModel.isOpenaliasRecord(_addressController.text);
|
// final isOpenalias =
|
||||||
|
// await sendViewModel.isOpenaliasRecord(_addressController.text);
|
||||||
|
|
||||||
if (isOpenalias) {
|
// if (isOpenalias) {
|
||||||
_addressController.text = sendViewModel.recordAddress;
|
// _addressController.text = sendViewModel.recordAddress;
|
||||||
|
|
||||||
|
// await showDialog<void>(
|
||||||
|
// context: context,
|
||||||
|
// builder: (BuildContext context) {
|
||||||
|
// return AlertWithOneAction(
|
||||||
|
// alertTitle: S.of(context).openalias_alert_title,
|
||||||
|
// alertContent: S
|
||||||
|
// .of(context)
|
||||||
|
// .openalias_alert_content(sendViewModel.recordName),
|
||||||
|
// buttonText: S.of(context).ok,
|
||||||
|
// buttonAction: () => Navigator.of(context).pop());
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _setTransactionPriority(BuildContext context) async {
|
||||||
|
final items = TransactionPriority.all;
|
||||||
|
final selectedItem = items.indexOf(sendViewModel.transactionPriority);
|
||||||
|
|
||||||
await showDialog<void>(
|
await showDialog<void>(
|
||||||
context: context,
|
builder: (_) => Picker(
|
||||||
builder: (BuildContext context) {
|
items: items,
|
||||||
return AlertWithOneAction(
|
selectedAtIndex: selectedItem,
|
||||||
alertTitle: S.of(context).openalias_alert_title,
|
title: S.of(context).please_select,
|
||||||
alertContent: S.of(context).openalias_alert_content(sendViewModel.recordName),
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
buttonText: S.of(context).ok,
|
onItemSelected: (TransactionPriority priority) => null,
|
||||||
buttonAction: () => Navigator.of(context).pop()
|
// sendViewModel.setTransactionPriority(priority),
|
||||||
);
|
isAlwaysShowScrollThumb: true,
|
||||||
});
|
),
|
||||||
}
|
context: context);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
import 'package:cake_wallet/src/stores/send/sending_state.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:cake_wallet/src/stores/send/sending_state.dart';
|
||||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||||
import 'package:cake_wallet/src/stores/send/send_store.dart';
|
import 'package:cake_wallet/src/stores/send/send_store.dart';
|
||||||
import 'package:cake_wallet/generated/i18n.dart';
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
|
|
|
@ -43,9 +43,10 @@ class SettingsPage extends BasePage {
|
||||||
return SettingsPickerCell<dynamic>(
|
return SettingsPickerCell<dynamic>(
|
||||||
title: item.title,
|
title: item.title,
|
||||||
selectedItem: item.selectedItem(),
|
selectedItem: item.selectedItem(),
|
||||||
setItem: (dynamic value) => item.setItem(value),
|
|
||||||
isAlwaysShowScrollThumb: item.isAlwaysShowScrollThumb,
|
isAlwaysShowScrollThumb: item.isAlwaysShowScrollThumb,
|
||||||
items: item.items);
|
items: item.items,
|
||||||
|
onItemSelected: (dynamic value) => item.onItemSelected(value),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,7 +60,8 @@ class SettingsPage extends BasePage {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item is RegularListItem) {
|
if (item is RegularListItem) {
|
||||||
return SettingsCellWithArrow(title: item.title, handler: item.handler);
|
return SettingsCellWithArrow(
|
||||||
|
title: item.title, handler: item.handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item is LinkListItem) {
|
if (item is LinkListItem) {
|
||||||
|
@ -73,7 +75,8 @@ class SettingsPage extends BasePage {
|
||||||
if (item is VersionListItem) {
|
if (item is VersionListItem) {
|
||||||
return Observer(builder: (_) {
|
return Observer(builder: (_) {
|
||||||
return SettingsVersionCell(
|
return SettingsVersionCell(
|
||||||
title: S.of(context).version(settingsViewModel.currentVersion));
|
title:
|
||||||
|
S.of(context).version(settingsViewModel.currentVersion));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,12 @@ import 'package:cake_wallet/src/widgets/standard_list.dart';
|
||||||
import 'package:cake_wallet/generated/i18n.dart';
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
|
|
||||||
class SettingsPickerCell<ItemType> extends StandardListRow {
|
class SettingsPickerCell<ItemType> extends StandardListRow {
|
||||||
SettingsPickerCell({@required String title, this.selectedItem, this.items,
|
SettingsPickerCell(
|
||||||
this.setItem, this.isAlwaysShowScrollThumb})
|
{@required String title,
|
||||||
|
this.selectedItem,
|
||||||
|
this.items,
|
||||||
|
this.onItemSelected,
|
||||||
|
this.isAlwaysShowScrollThumb})
|
||||||
: super(
|
: super(
|
||||||
title: title,
|
title: title,
|
||||||
isSelected: false,
|
isSelected: false,
|
||||||
|
@ -20,12 +24,13 @@ class SettingsPickerCell<ItemType> extends StandardListRow {
|
||||||
title: S.current.please_select,
|
title: S.current.please_select,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
isAlwaysShowScrollThumb: isAlwaysShowScrollThumb,
|
isAlwaysShowScrollThumb: isAlwaysShowScrollThumb,
|
||||||
onItemSelected: (ItemType value) => setItem(value)));
|
onItemSelected: (ItemType item) =>
|
||||||
|
onItemSelected?.call(item)));
|
||||||
});
|
});
|
||||||
|
|
||||||
final ItemType selectedItem;
|
final ItemType selectedItem;
|
||||||
final List<ItemType> items;
|
final List<ItemType> items;
|
||||||
final Function(ItemType) setItem;
|
final void Function(ItemType item) onItemSelected;
|
||||||
final bool isAlwaysShowScrollThumb;
|
final bool isAlwaysShowScrollThumb;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -8,9 +8,9 @@ class SettingsSwitcherCell extends StandardListRow {
|
||||||
: super(title: title, isSelected: false);
|
: super(title: title, isSelected: false);
|
||||||
|
|
||||||
final bool value;
|
final bool value;
|
||||||
final void Function(bool value) onValueChange;
|
final void Function(BuildContext context, bool value) onValueChange;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget buildTrailing(BuildContext context) =>
|
Widget buildTrailing(BuildContext context) => StandartSwitch(
|
||||||
StandartSwitch(value: value, onTaped: () => onValueChange(!value));
|
value: value, onTaped: () => onValueChange(context, !value));
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
@ -48,6 +49,9 @@ class TransactionDetailsPage extends BasePage {
|
||||||
StandartListItem(
|
StandartListItem(
|
||||||
title: S.current.transaction_details_date,
|
title: S.current.transaction_details_date,
|
||||||
value: dateFormat.format(tx.date)),
|
value: dateFormat.format(tx.date)),
|
||||||
|
StandartListItem(
|
||||||
|
title: 'Confirmations',
|
||||||
|
value: tx.confirmations?.toString()),
|
||||||
StandartListItem(
|
StandartListItem(
|
||||||
title: S.current.transaction_details_height, value: '${tx.height}'),
|
title: S.current.transaction_details_height, value: '${tx.height}'),
|
||||||
StandartListItem(
|
StandartListItem(
|
||||||
|
|
|
@ -66,14 +66,6 @@ class WalletListBodyState extends State<WalletListBody> {
|
||||||
itemBuilder: (__, index) {
|
itemBuilder: (__, index) {
|
||||||
final wallet = widget.walletListViewModel.wallets[index];
|
final wallet = widget.walletListViewModel.wallets[index];
|
||||||
final screenWidth = MediaQuery.of(context).size.width;
|
final screenWidth = MediaQuery.of(context).size.width;
|
||||||
// String shortAddress = '';
|
|
||||||
|
|
||||||
// if (wallet.isCurrent) {
|
|
||||||
// shortAddress = wallet.address;
|
|
||||||
// shortAddress = shortAddress.replaceRange(
|
|
||||||
// 4, shortAddress.length - 4, '...');
|
|
||||||
// }
|
|
||||||
|
|
||||||
final walletMenu = WalletMenu(context, widget.walletListViewModel);
|
final walletMenu = WalletMenu(context, widget.walletListViewModel);
|
||||||
final items =
|
final items =
|
||||||
walletMenu.generateItemsForWalletMenu(wallet.isCurrent);
|
walletMenu.generateItemsForWalletMenu(wallet.isCurrent);
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
|
||||||
import 'package:cake_wallet/view_model/wallet_list/wallet_list_view_model.dart';
|
import 'package:cake_wallet/view_model/wallet_list/wallet_list_view_model.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:cake_wallet/routes.dart';
|
import 'package:cake_wallet/routes.dart';
|
||||||
|
@ -36,10 +37,14 @@ class WalletMenu {
|
||||||
];
|
];
|
||||||
|
|
||||||
final List<Image> listImages = [
|
final List<Image> listImages = [
|
||||||
Image.asset('assets/images/load.png', height: 24, width: 24, color: Colors.white),
|
Image.asset('assets/images/load.png',
|
||||||
Image.asset('assets/images/eye_action.png', height: 24, width: 24, color: Colors.white),
|
height: 24, width: 24, color: Colors.white),
|
||||||
Image.asset('assets/images/trash.png', height: 24, width: 24, color: Colors.white),
|
Image.asset('assets/images/eye_action.png',
|
||||||
Image.asset('assets/images/scanner.png', height: 24, width: 24, color: Colors.white)
|
height: 24, width: 24, color: Colors.white),
|
||||||
|
Image.asset('assets/images/trash.png',
|
||||||
|
height: 24, width: 24, color: Colors.white),
|
||||||
|
Image.asset('assets/images/scanner.png',
|
||||||
|
height: 24, width: 24, color: Colors.white)
|
||||||
];
|
];
|
||||||
|
|
||||||
List<String> generateItemsForWalletMenu(bool isCurrentWallet) {
|
List<String> generateItemsForWalletMenu(bool isCurrentWallet) {
|
||||||
|
@ -87,10 +92,11 @@ class WalletMenu {
|
||||||
return images;
|
return images;
|
||||||
}
|
}
|
||||||
|
|
||||||
void action(int index, WalletListItem wallet, bool isCurrentWallet) {
|
Future<void> action(
|
||||||
|
int index, WalletListItem wallet, bool isCurrentWallet) async {
|
||||||
switch (index) {
|
switch (index) {
|
||||||
case 0:
|
case 0:
|
||||||
Navigator.of(context).pushNamed(Routes.auth, arguments:
|
await Navigator.of(context).pushNamed(Routes.auth, arguments:
|
||||||
(bool isAuthenticatedSuccessfully, AuthPageState auth) async {
|
(bool isAuthenticatedSuccessfully, AuthPageState auth) async {
|
||||||
if (!isAuthenticatedSuccessfully) {
|
if (!isAuthenticatedSuccessfully) {
|
||||||
return;
|
return;
|
||||||
|
@ -110,7 +116,7 @@ class WalletMenu {
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
Navigator.of(context).pushNamed(Routes.auth, arguments:
|
await Navigator.of(context).pushNamed(Routes.auth, arguments:
|
||||||
(bool isAuthenticatedSuccessfully, AuthPageState auth) async {
|
(bool isAuthenticatedSuccessfully, AuthPageState auth) async {
|
||||||
if (!isAuthenticatedSuccessfully) {
|
if (!isAuthenticatedSuccessfully) {
|
||||||
return;
|
return;
|
||||||
|
@ -120,7 +126,23 @@ class WalletMenu {
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
Navigator.of(context).pushNamed(Routes.auth, arguments:
|
final isComfirmed = await showDialog<bool>(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return AlertWithTwoActions(
|
||||||
|
alertTitle: 'Remove wallet',
|
||||||
|
alertContent: S.of(context).confirm_delete_wallet,
|
||||||
|
leftButtonText: S.of(context).cancel,
|
||||||
|
rightButtonText: S.of(context).remove,
|
||||||
|
actionLeftButton: () => Navigator.of(context).pop(false),
|
||||||
|
actionRightButton: () => Navigator.of(context).pop(true));
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isComfirmed == null || !isComfirmed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await Navigator.of(context).pushNamed(Routes.auth, arguments:
|
||||||
(bool isAuthenticatedSuccessfully, AuthPageState auth) async {
|
(bool isAuthenticatedSuccessfully, AuthPageState auth) async {
|
||||||
if (!isAuthenticatedSuccessfully) {
|
if (!isAuthenticatedSuccessfully) {
|
||||||
return;
|
return;
|
||||||
|
@ -129,7 +151,7 @@ class WalletMenu {
|
||||||
try {
|
try {
|
||||||
auth.changeProcessText(
|
auth.changeProcessText(
|
||||||
S.of(context).wallet_list_removing_wallet(wallet.name));
|
S.of(context).wallet_list_removing_wallet(wallet.name));
|
||||||
// await _walletListStore.remove(wallet);
|
await walletListViewModel.remove(wallet);
|
||||||
auth.close();
|
auth.close();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
auth.changeProcessText(S
|
auth.changeProcessText(S
|
||||||
|
@ -139,7 +161,7 @@ class WalletMenu {
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
Navigator.of(context).pushNamed(Routes.rescan);
|
await Navigator.of(context).pushNamed(Routes.rescan);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -42,7 +42,7 @@ abstract class SettingsStoreBase with Store {
|
||||||
_sharedPreferences = sharedPreferences;
|
_sharedPreferences = sharedPreferences;
|
||||||
_nodes = nodes;
|
_nodes = nodes;
|
||||||
allowBiometricalAuthentication = initialAllowBiometricalAuthentication;
|
allowBiometricalAuthentication = initialAllowBiometricalAuthentication;
|
||||||
isDarkTheme = initialDarkTheme;
|
isDarkTheme = true;
|
||||||
defaultPinLength = initialPinLength;
|
defaultPinLength = initialPinLength;
|
||||||
languageCode = initialLanguageCode;
|
languageCode = initialLanguageCode;
|
||||||
currentLocale = initialCurrentLocale;
|
currentLocale = initialCurrentLocale;
|
||||||
|
@ -143,7 +143,7 @@ abstract class SettingsStoreBase with Store {
|
||||||
bool allowBiometricalAuthentication;
|
bool allowBiometricalAuthentication;
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
bool isDarkTheme;
|
bool isDarkTheme = true;
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
int defaultPinLength;
|
int defaultPinLength;
|
||||||
|
@ -285,7 +285,7 @@ abstract class SettingsStoreBase with Store {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future setCurrentNodeToDefault() async {
|
Future setCurrentNodeToDefault() async {
|
||||||
await changeCurrentNodeToDefault(sharedPreferences: _sharedPreferences, nodes: _nodes);
|
// await changeCurrentNodeToDefault(sharedPreferences: _sharedPreferences, nodes: _nodes);
|
||||||
await loadSettings();
|
await loadSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -216,10 +216,7 @@ class SeedWidgetState extends State<SeedWidget> {
|
||||||
gradient: LinearGradient(colors: [
|
gradient: LinearGradient(colors: [
|
||||||
Theme.of(context).primaryTextTheme.subhead.color,
|
Theme.of(context).primaryTextTheme.subhead.color,
|
||||||
Theme.of(context).primaryTextTheme.subhead.decorationColor,
|
Theme.of(context).primaryTextTheme.subhead.decorationColor,
|
||||||
],
|
], begin: Alignment.topLeft, end: Alignment.bottomRight)),
|
||||||
begin: Alignment.topLeft,
|
|
||||||
end: Alignment.bottomRight)
|
|
||||||
),
|
|
||||||
child: Column(
|
child: Column(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
CupertinoNavigationBar(
|
CupertinoNavigationBar(
|
||||||
|
@ -242,14 +239,17 @@ class SeedWidgetState extends State<SeedWidget> {
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
color:
|
color: Theme.of(context)
|
||||||
Theme.of(context).textTheme.overline.backgroundColor),
|
.textTheme
|
||||||
|
.overline
|
||||||
|
.backgroundColor),
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.only(top: 5),
|
padding: EdgeInsets.only(top: 5),
|
||||||
child: Wrap(
|
child: Wrap(
|
||||||
children: items.map((item) {
|
children: items.map((item) {
|
||||||
final isValid = widget.validator.isValid(item);
|
final isValid =
|
||||||
|
widget.validator.isValid(item);
|
||||||
final isSelected = selectedItem == item;
|
final isSelected = selectedItem == item;
|
||||||
|
|
||||||
return InkWell(
|
return InkWell(
|
||||||
|
@ -259,7 +259,8 @@ class SeedWidgetState extends State<SeedWidget> {
|
||||||
color: isValid
|
color: isValid
|
||||||
? Colors.transparent
|
? Colors.transparent
|
||||||
: Palette.red),
|
: Palette.red),
|
||||||
margin: EdgeInsets.only(right: 7, bottom: 8),
|
margin: EdgeInsets.only(
|
||||||
|
right: 7, bottom: 8),
|
||||||
child: Text(
|
child: Text(
|
||||||
item.toString(),
|
item.toString(),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
|
@ -280,11 +281,9 @@ class SeedWidgetState extends State<SeedWidget> {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
))
|
||||||
)
|
|
||||||
],
|
],
|
||||||
)
|
)),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
Flexible(
|
Flexible(
|
||||||
fit: FlexFit.tight,
|
fit: FlexFit.tight,
|
||||||
|
@ -372,11 +371,17 @@ class SeedWidgetState extends State<SeedWidget> {
|
||||||
errorText: _errorMessage,
|
errorText: _errorMessage,
|
||||||
focusedBorder: UnderlineInputBorder(
|
focusedBorder: UnderlineInputBorder(
|
||||||
borderSide: BorderSide(
|
borderSide: BorderSide(
|
||||||
color: Theme.of(context).accentTextTheme.subtitle.backgroundColor,
|
color: Theme.of(context)
|
||||||
|
.accentTextTheme
|
||||||
|
.subtitle
|
||||||
|
.backgroundColor,
|
||||||
width: 1.0)),
|
width: 1.0)),
|
||||||
enabledBorder: UnderlineInputBorder(
|
enabledBorder: UnderlineInputBorder(
|
||||||
borderSide: BorderSide(
|
borderSide: BorderSide(
|
||||||
color: Theme.of(context).accentTextTheme.subtitle.backgroundColor,
|
color: Theme.of(context)
|
||||||
|
.accentTextTheme
|
||||||
|
.subtitle
|
||||||
|
.backgroundColor,
|
||||||
width: 1.0))),
|
width: 1.0))),
|
||||||
enableInteractiveSelection: false,
|
enableInteractiveSelection: false,
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:cake_wallet/di.dart';
|
import 'package:cake_wallet/di.dart';
|
||||||
|
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
|
@ -29,8 +30,9 @@ abstract class SettingsStoreBase with Store {
|
||||||
@required int initialPinLength,
|
@required int initialPinLength,
|
||||||
@required String initialLanguageCode,
|
@required String initialLanguageCode,
|
||||||
@required String initialCurrentLocale,
|
@required String initialCurrentLocale,
|
||||||
@required this.node,
|
// @required this.node,
|
||||||
@required this.appVersion,
|
@required this.appVersion,
|
||||||
|
@required Map<WalletType, Node> nodes,
|
||||||
this.actionlistDisplayMode}) {
|
this.actionlistDisplayMode}) {
|
||||||
fiatCurrency = initialFiatCurrency;
|
fiatCurrency = initialFiatCurrency;
|
||||||
transactionPriority = initialTransactionPriority;
|
transactionPriority = initialTransactionPriority;
|
||||||
|
@ -42,17 +44,18 @@ abstract class SettingsStoreBase with Store {
|
||||||
languageCode = initialLanguageCode;
|
languageCode = initialLanguageCode;
|
||||||
currentLocale = initialCurrentLocale;
|
currentLocale = initialCurrentLocale;
|
||||||
itemHeaders = {};
|
itemHeaders = {};
|
||||||
|
this.nodes = ObservableMap<WalletType, Node>.of(nodes);
|
||||||
// actionlistDisplayMode.observe(
|
|
||||||
// (dynamic _) => _sharedPreferences.setInt(displayActionListModeKey,
|
|
||||||
// serializeActionlistDisplayModes(actionlistDisplayMode)),
|
|
||||||
// fireImmediately: false);
|
|
||||||
|
|
||||||
_sharedPreferences = sharedPreferences;
|
_sharedPreferences = sharedPreferences;
|
||||||
_nodeSource = nodeSource;
|
_nodeSource = nodeSource;
|
||||||
|
|
||||||
|
reaction(
|
||||||
|
(_) => allowBiometricalAuthentication,
|
||||||
|
(bool biometricalAuthentication) => sharedPreferences.setBool(
|
||||||
|
allowBiometricalAuthenticationKey, biometricalAuthentication));
|
||||||
}
|
}
|
||||||
|
|
||||||
static const currentNodeIdKey = 'current_node_id';
|
static const currentNodeIdKey = 'current_node_id';
|
||||||
|
static const currentBitcoinElectrumSererIdKey = 'current_node_id_btc';
|
||||||
static const currentFiatCurrencyKey = 'current_fiat_currency';
|
static const currentFiatCurrencyKey = 'current_fiat_currency';
|
||||||
static const currentTransactionPriorityKey = 'current_fee_priority';
|
static const currentTransactionPriorityKey = 'current_fee_priority';
|
||||||
static const currentBalanceDisplayModeKey = 'current_balance_display_mode';
|
static const currentBalanceDisplayModeKey = 'current_balance_display_mode';
|
||||||
|
@ -64,9 +67,6 @@ abstract class SettingsStoreBase with Store {
|
||||||
static const currentPinLength = 'current_pin_length';
|
static const currentPinLength = 'current_pin_length';
|
||||||
static const currentLanguageCode = 'language_code';
|
static const currentLanguageCode = 'language_code';
|
||||||
|
|
||||||
@observable
|
|
||||||
Node node;
|
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
FiatCurrency fiatCurrency;
|
FiatCurrency fiatCurrency;
|
||||||
|
|
||||||
|
@ -103,6 +103,26 @@ abstract class SettingsStoreBase with Store {
|
||||||
SharedPreferences _sharedPreferences;
|
SharedPreferences _sharedPreferences;
|
||||||
Box<Node> _nodeSource;
|
Box<Node> _nodeSource;
|
||||||
|
|
||||||
|
ObservableMap<WalletType, Node> nodes;
|
||||||
|
|
||||||
|
Node getCurrentNode(WalletType walletType) => nodes[walletType];
|
||||||
|
|
||||||
|
Future<void> setCurrentNode(Node node, WalletType walletType) async {
|
||||||
|
switch (walletType) {
|
||||||
|
case WalletType.bitcoin:
|
||||||
|
await _sharedPreferences.setInt(
|
||||||
|
currentBitcoinElectrumSererIdKey, node.key as int);
|
||||||
|
break;
|
||||||
|
case WalletType.monero:
|
||||||
|
await _sharedPreferences.setInt(currentNodeIdKey, node.key as int);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
nodes[walletType] = node;
|
||||||
|
}
|
||||||
|
|
||||||
static Future<SettingsStore> load(
|
static Future<SettingsStore> load(
|
||||||
{@required Box<Node> nodeSource,
|
{@required Box<Node> nodeSource,
|
||||||
FiatCurrency initialFiatCurrency = FiatCurrency.usd,
|
FiatCurrency initialFiatCurrency = FiatCurrency.usd,
|
||||||
|
@ -132,12 +152,18 @@ abstract class SettingsStoreBase with Store {
|
||||||
await Language.localeDetection();
|
await Language.localeDetection();
|
||||||
final initialCurrentLocale = await Devicelocale.currentLocale;
|
final initialCurrentLocale = await Devicelocale.currentLocale;
|
||||||
final nodeId = sharedPreferences.getInt(currentNodeIdKey);
|
final nodeId = sharedPreferences.getInt(currentNodeIdKey);
|
||||||
final node = nodeSource.get(nodeId);
|
final bitcoinElectrumServerId =
|
||||||
|
sharedPreferences.getInt(currentBitcoinElectrumSererIdKey);
|
||||||
|
final moneroNode = nodeSource.get(nodeId);
|
||||||
|
final bitcoinElectrumServer = nodeSource.get(bitcoinElectrumServerId);
|
||||||
final packageInfo = await PackageInfo.fromPlatform();
|
final packageInfo = await PackageInfo.fromPlatform();
|
||||||
|
|
||||||
return SettingsStore(
|
return SettingsStore(
|
||||||
sharedPreferences: sharedPreferences,
|
sharedPreferences: sharedPreferences,
|
||||||
node: node,
|
nodes: {
|
||||||
|
WalletType.monero: moneroNode,
|
||||||
|
WalletType.bitcoin: bitcoinElectrumServer
|
||||||
|
},
|
||||||
nodeSource: nodeSource,
|
nodeSource: nodeSource,
|
||||||
appVersion: packageInfo.version,
|
appVersion: packageInfo.version,
|
||||||
initialFiatCurrency: currentFiatCurrency,
|
initialFiatCurrency: currentFiatCurrency,
|
||||||
|
|
11
lib/utils/item_cell.dart
Normal file
11
lib/utils/item_cell.dart
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:cake_wallet/utils/mobx.dart';
|
||||||
|
|
||||||
|
class ItemCell<Item> with Keyable {
|
||||||
|
ItemCell(this.value, {@required this.isSelected, @required dynamic key}) {
|
||||||
|
keyIndex = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Item value;
|
||||||
|
final bool isSelected;
|
||||||
|
}
|
90
lib/utils/mobx.dart
Normal file
90
lib/utils/mobx.dart
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
|
import 'package:mobx/mobx.dart';
|
||||||
|
|
||||||
|
mixin Keyable {
|
||||||
|
dynamic keyIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
void connectDifferent<T extends Keyable, Y extends Keyable>(
|
||||||
|
ObservableList<T> source, ObservableList<Y> dest, Y Function(T) transform,
|
||||||
|
{bool Function(T) filter}) {
|
||||||
|
source.observe((ListChange<T> change) {
|
||||||
|
change.elementChanges.forEach((change) {
|
||||||
|
switch (change.type) {
|
||||||
|
case OperationType.add:
|
||||||
|
if (filter?.call(change.newValue as T) ?? true) {
|
||||||
|
dest.add(transform(change.newValue as T));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case OperationType.remove:
|
||||||
|
// Hive could has equal index and key
|
||||||
|
dest.removeWhere(
|
||||||
|
(elem) => elem.keyIndex == (change.oldValue.key ?? change.index));
|
||||||
|
break;
|
||||||
|
case OperationType.update:
|
||||||
|
for (var i = 0; i < dest.length; i++) {
|
||||||
|
final item = dest[i];
|
||||||
|
|
||||||
|
if (item.keyIndex == change.newValue.key) {
|
||||||
|
dest[i] = transform(change.newValue as T);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void connect<T extends Keyable>(
|
||||||
|
ObservableList<T> source, ObservableList<T> dest) {
|
||||||
|
source.observe((ListChange<T> change) {
|
||||||
|
source.observe((ListChange<T> change) {
|
||||||
|
change.elementChanges.forEach((change) {
|
||||||
|
switch (change.type) {
|
||||||
|
case OperationType.add:
|
||||||
|
// if (filter?.call(change.newValue as T) ?? true) {
|
||||||
|
dest.add(change.newValue as T);
|
||||||
|
// }
|
||||||
|
break;
|
||||||
|
case OperationType.remove:
|
||||||
|
// Hive could has equal index and key
|
||||||
|
dest.removeWhere((elem) =>
|
||||||
|
elem.keyIndex == (change.oldValue.key ?? change.index));
|
||||||
|
break;
|
||||||
|
case OperationType.update:
|
||||||
|
for (var i = 0; i < dest.length; i++) {
|
||||||
|
final item = dest[i];
|
||||||
|
|
||||||
|
if (item.keyIndex == change.newValue.key) {
|
||||||
|
dest[i] = change.newValue as T;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
StreamSubscription<BoxEvent> bindBox<T extends Keyable>(
|
||||||
|
Box<T> source, ObservableList<T> dest) {
|
||||||
|
return source.watch().listen((event) {
|
||||||
|
if (event.deleted) {
|
||||||
|
dest.removeWhere((el) => el.keyIndex == event.key);
|
||||||
|
}
|
||||||
|
|
||||||
|
final dynamic value = event.value;
|
||||||
|
|
||||||
|
if (value is T) {
|
||||||
|
final elIndex = dest.indexWhere((el) => el.keyIndex == value.keyIndex);
|
||||||
|
|
||||||
|
if (elIndex > -1) {
|
||||||
|
dest[elIndex] = value;
|
||||||
|
} else {
|
||||||
|
dest.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
|
@ -1,20 +1,34 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
import 'package:cake_wallet/core/contact_service.dart';
|
import 'package:cake_wallet/core/contact_service.dart';
|
||||||
import 'package:cake_wallet/store/contact_list_store.dart';
|
import 'package:cake_wallet/store/contact_list_store.dart';
|
||||||
import 'package:cake_wallet/src/domain/common/contact.dart';
|
import 'package:cake_wallet/src/domain/common/contact.dart';
|
||||||
|
import 'package:cake_wallet/utils/mobx.dart';
|
||||||
|
|
||||||
part 'contact_list_view_model.g.dart';
|
part 'contact_list_view_model.g.dart';
|
||||||
|
|
||||||
class ContactListViewModel = ContactListViewModelBase with _$ContactListViewModel;
|
class ContactListViewModel = ContactListViewModelBase
|
||||||
|
with _$ContactListViewModel;
|
||||||
|
|
||||||
abstract class ContactListViewModelBase with Store {
|
abstract class ContactListViewModelBase with Store {
|
||||||
ContactListViewModelBase(this.addressBookStore, this.contactService);
|
ContactListViewModelBase(
|
||||||
|
this.addressBookStore, this.contactService, this.contactSource) {
|
||||||
|
_subscription = bindBox(contactSource, addressBookStore.contacts);
|
||||||
|
}
|
||||||
|
|
||||||
final ContactListStore addressBookStore;
|
final ContactListStore addressBookStore;
|
||||||
final ContactService contactService;
|
final ContactService contactService;
|
||||||
|
final Box<Contact> contactSource;
|
||||||
|
|
||||||
@computed
|
|
||||||
ObservableList<Contact> get contacts => addressBookStore.contacts;
|
ObservableList<Contact> get contacts => addressBookStore.contacts;
|
||||||
|
|
||||||
|
StreamSubscription<BoxEvent> _subscription;
|
||||||
|
|
||||||
|
void dispose() {
|
||||||
|
_subscription.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> delete(Contact contact) async => contactService.delete(contact);
|
Future<void> delete(Contact contact) async => contactService.delete(contact);
|
||||||
}
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
import 'package:cake_wallet/core/wallet_base.dart';
|
import 'package:cake_wallet/core/wallet_base.dart';
|
||||||
import 'package:cake_wallet/core/contact_service.dart';
|
import 'package:cake_wallet/core/contact_service.dart';
|
||||||
|
@ -10,7 +11,7 @@ part 'contact_view_model.g.dart';
|
||||||
class ContactViewModel = ContactViewModelBase with _$ContactViewModel;
|
class ContactViewModel = ContactViewModelBase with _$ContactViewModel;
|
||||||
|
|
||||||
abstract class ContactViewModelBase with Store {
|
abstract class ContactViewModelBase with Store {
|
||||||
ContactViewModelBase(this._contactService, this._wallet, {Contact contact})
|
ContactViewModelBase(this._contacts, this._wallet, {Contact contact})
|
||||||
: state = InitialContactViewModelState(),
|
: state = InitialContactViewModelState(),
|
||||||
currencies = CryptoCurrency.all,
|
currencies = CryptoCurrency.all,
|
||||||
_contact = contact {
|
_contact = contact {
|
||||||
|
@ -33,12 +34,14 @@ abstract class ContactViewModelBase with Store {
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
bool get isReady =>
|
bool get isReady =>
|
||||||
(name?.isNotEmpty ?? false) && (currency?.toString()?.isNotEmpty ?? false)
|
(name?.isNotEmpty ?? false) &&
|
||||||
&& (address?.isNotEmpty ?? false);
|
(currency?.toString()?.isNotEmpty ?? false) &&
|
||||||
|
(address?.isNotEmpty ?? false);
|
||||||
|
|
||||||
final List<CryptoCurrency> currencies;
|
final List<CryptoCurrency> currencies;
|
||||||
final ContactService _contactService;
|
// final ContactService _contactService;
|
||||||
final WalletBase _wallet;
|
final WalletBase _wallet;
|
||||||
|
final Box<Contact> _contacts;
|
||||||
final Contact _contact;
|
final Contact _contact;
|
||||||
|
|
||||||
@action
|
@action
|
||||||
|
@ -57,10 +60,13 @@ abstract class ContactViewModelBase with Store {
|
||||||
_contact.name = name;
|
_contact.name = name;
|
||||||
_contact.address = address;
|
_contact.address = address;
|
||||||
_contact.updateCryptoCurrency(currency: currency);
|
_contact.updateCryptoCurrency(currency: currency);
|
||||||
await _contactService.update(_contact);
|
await _contacts.put(_contact.key, _contact);
|
||||||
|
// await _contactService.update(_contact);
|
||||||
} else {
|
} else {
|
||||||
await _contactService
|
await _contacts
|
||||||
.add(Contact(name: name, address: address, type: currency));
|
.add(Contact(name: name, address: address, type: currency));
|
||||||
|
// await _contactService
|
||||||
|
// .add(Contact(name: name, address: address, type: currency));
|
||||||
}
|
}
|
||||||
|
|
||||||
state = ContactSavingSuccessfully();
|
state = ContactSavingSuccessfully();
|
||||||
|
|
|
@ -35,9 +35,11 @@ abstract class BalanceViewModelBase with Store {
|
||||||
|
|
||||||
if (_wallet is BitcoinWallet) {
|
if (_wallet is BitcoinWallet) {
|
||||||
return WalletBalance(
|
return WalletBalance(
|
||||||
unlockedBalance: _wallet.balance.confirmedFormatted,
|
unlockedBalance: _wallet.balance.totalFormatted,
|
||||||
totalBalance: _wallet.balance.unconfirmedFormatted);
|
totalBalance: _wallet.balance.totalFormatted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
String _getFiatBalance({double price, String cryptoAmount}) {
|
String _getFiatBalance({double price, String cryptoAmount}) {
|
||||||
|
|
|
@ -29,56 +29,52 @@ part 'dashboard_view_model.g.dart';
|
||||||
class DashboardViewModel = DashboardViewModelBase with _$DashboardViewModel;
|
class DashboardViewModel = DashboardViewModelBase with _$DashboardViewModel;
|
||||||
|
|
||||||
abstract class DashboardViewModelBase with Store {
|
abstract class DashboardViewModelBase with Store {
|
||||||
DashboardViewModelBase({
|
DashboardViewModelBase(
|
||||||
this.balanceViewModel,
|
{this.balanceViewModel,
|
||||||
this.appStore,
|
this.appStore,
|
||||||
this.tradesStore,
|
this.tradesStore,
|
||||||
this.tradeFilterStore,
|
this.tradeFilterStore,
|
||||||
this.transactionFilterStore}) {
|
this.transactionFilterStore}) {
|
||||||
|
filterItems = {
|
||||||
filterItems = {S.current.transactions : [
|
S.current.transactions: [
|
||||||
FilterItem(
|
FilterItem(
|
||||||
value: transactionFilterStore.displayIncoming,
|
value: transactionFilterStore.displayIncoming,
|
||||||
caption: S.current.incoming,
|
caption: S.current.incoming,
|
||||||
onChanged: (value) => transactionFilterStore.toggleIncoming()
|
onChanged: (value) => transactionFilterStore.toggleIncoming()),
|
||||||
),
|
|
||||||
FilterItem(
|
FilterItem(
|
||||||
value: transactionFilterStore.displayOutgoing,
|
value: transactionFilterStore.displayOutgoing,
|
||||||
caption: S.current.outgoing,
|
caption: S.current.outgoing,
|
||||||
onChanged: (value) => transactionFilterStore.toggleOutgoing()
|
onChanged: (value) => transactionFilterStore.toggleOutgoing()),
|
||||||
),
|
|
||||||
FilterItem(
|
FilterItem(
|
||||||
value: false,
|
value: false,
|
||||||
caption: S.current.transactions_by_date,
|
caption: S.current.transactions_by_date,
|
||||||
onChanged: null
|
onChanged: null),
|
||||||
),
|
|
||||||
],
|
],
|
||||||
S.current.trades : [
|
S.current.trades: [
|
||||||
FilterItem(
|
FilterItem(
|
||||||
value: tradeFilterStore.displayXMRTO,
|
value: tradeFilterStore.displayXMRTO,
|
||||||
caption: 'XMR.TO',
|
caption: 'XMR.TO',
|
||||||
onChanged: (value) => tradeFilterStore
|
onChanged: (value) => tradeFilterStore
|
||||||
.toggleDisplayExchange(ExchangeProviderDescription.xmrto)
|
.toggleDisplayExchange(ExchangeProviderDescription.xmrto)),
|
||||||
),
|
|
||||||
FilterItem(
|
FilterItem(
|
||||||
value: tradeFilterStore.displayChangeNow,
|
value: tradeFilterStore.displayChangeNow,
|
||||||
caption: 'Change.NOW',
|
caption: 'Change.NOW',
|
||||||
onChanged: (value) => tradeFilterStore
|
onChanged: (value) => tradeFilterStore
|
||||||
.toggleDisplayExchange(ExchangeProviderDescription.changeNow)
|
.toggleDisplayExchange(ExchangeProviderDescription.changeNow)),
|
||||||
),
|
|
||||||
FilterItem(
|
FilterItem(
|
||||||
value: tradeFilterStore.displayMorphToken,
|
value: tradeFilterStore.displayMorphToken,
|
||||||
caption: 'MorphToken',
|
caption: 'MorphToken',
|
||||||
onChanged: (value) => tradeFilterStore
|
onChanged: (value) => tradeFilterStore
|
||||||
.toggleDisplayExchange(ExchangeProviderDescription.morphToken)
|
.toggleDisplayExchange(ExchangeProviderDescription.morphToken)),
|
||||||
),
|
]
|
||||||
]};
|
};
|
||||||
|
|
||||||
name = appStore.wallet?.name;
|
name = appStore.wallet?.name;
|
||||||
wallet ??= appStore.wallet;
|
wallet ??= appStore.wallet;
|
||||||
type = wallet.type;
|
type = wallet.type;
|
||||||
|
|
||||||
transactions = ObservableList.of(wallet.transactionHistory.transactions
|
transactions = ObservableList.of(wallet
|
||||||
|
.transactionHistory.transactions.values
|
||||||
.map((transaction) => TransactionListItem(
|
.map((transaction) => TransactionListItem(
|
||||||
transaction: transaction,
|
transaction: transaction,
|
||||||
price: price,
|
price: price,
|
||||||
|
@ -117,15 +113,11 @@ abstract class DashboardViewModelBase with Store {
|
||||||
var statusText = '';
|
var statusText = '';
|
||||||
|
|
||||||
if (status is SyncingSyncStatus) {
|
if (status is SyncingSyncStatus) {
|
||||||
statusText = S.current
|
statusText = S.current.Blocks_remaining(status.toString());
|
||||||
.Blocks_remaining(
|
|
||||||
status.toString());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status is FailedSyncStatus) {
|
if (status is FailedSyncStatus || status is LostConnectionSyncStatus) {
|
||||||
statusText = S
|
statusText = S.current.please_try_to_connect_to_another_node;
|
||||||
.current
|
|
||||||
.please_try_to_connect_to_another_node;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return statusText;
|
return statusText;
|
||||||
|
@ -145,8 +137,7 @@ abstract class DashboardViewModelBase with Store {
|
||||||
List<ActionListItem> get items {
|
List<ActionListItem> get items {
|
||||||
final _items = <ActionListItem>[];
|
final _items = <ActionListItem>[];
|
||||||
|
|
||||||
_items
|
_items.addAll(transactionFilterStore.filtered(transactions: transactions));
|
||||||
.addAll(transactionFilterStore.filtered(transactions: transactions));
|
|
||||||
_items.addAll(tradeFilterStore.filtered(trades: trades, wallet: wallet));
|
_items.addAll(tradeFilterStore.filtered(trades: trades, wallet: wallet));
|
||||||
|
|
||||||
return formattedItemsList(_items);
|
return formattedItemsList(_items);
|
||||||
|
@ -168,11 +159,16 @@ abstract class DashboardViewModelBase with Store {
|
||||||
|
|
||||||
ReactionDisposer _reaction;
|
ReactionDisposer _reaction;
|
||||||
|
|
||||||
|
Future<void> reconnect() async {
|
||||||
|
final node = appStore.settingsStore.getCurrentNode(wallet.type);
|
||||||
|
await wallet.connectToNode(node: node);
|
||||||
|
}
|
||||||
|
|
||||||
void _onWalletChange(WalletBase wallet) {
|
void _onWalletChange(WalletBase wallet) {
|
||||||
name = wallet.name;
|
name = wallet.name;
|
||||||
transactions.clear();
|
transactions.clear();
|
||||||
transactions.addAll(wallet.transactionHistory.transactions
|
transactions.addAll(wallet.transactionHistory.transactions.values.map(
|
||||||
.map((transaction) => TransactionListItem(
|
(transaction) => TransactionListItem(
|
||||||
transaction: transaction,
|
transaction: transaction,
|
||||||
price: price,
|
price: price,
|
||||||
fiatCurrency: appStore.settingsStore.fiatCurrency,
|
fiatCurrency: appStore.settingsStore.fiatCurrency,
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import 'package:cake_wallet/core/wallet_base.dart';
|
import 'package:cake_wallet/core/wallet_base.dart';
|
||||||
import 'package:cake_wallet/src/domain/common/crypto_currency.dart';
|
import 'package:cake_wallet/src/domain/common/crypto_currency.dart';
|
||||||
|
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
|
||||||
import 'package:cake_wallet/src/domain/exchange/exchange_provider.dart';
|
import 'package:cake_wallet/src/domain/exchange/exchange_provider.dart';
|
||||||
import 'package:cake_wallet/src/domain/exchange/limits.dart';
|
import 'package:cake_wallet/src/domain/exchange/limits.dart';
|
||||||
import 'package:cake_wallet/src/domain/exchange/trade.dart';
|
import 'package:cake_wallet/src/domain/exchange/trade.dart';
|
||||||
|
@ -25,22 +26,18 @@ part 'exchange_view_model.g.dart';
|
||||||
class ExchangeViewModel = ExchangeViewModelBase with _$ExchangeViewModel;
|
class ExchangeViewModel = ExchangeViewModelBase with _$ExchangeViewModel;
|
||||||
|
|
||||||
abstract class ExchangeViewModelBase with Store {
|
abstract class ExchangeViewModelBase with Store {
|
||||||
ExchangeViewModelBase({
|
ExchangeViewModelBase(
|
||||||
this.wallet,
|
{this.wallet,
|
||||||
this.trades,
|
this.trades,
|
||||||
this.exchangeTemplateStore,
|
this.exchangeTemplateStore,
|
||||||
this.tradesStore}) {
|
this.tradesStore}) {
|
||||||
|
|
||||||
providerList = [
|
providerList = [
|
||||||
XMRTOExchangeProvider(),
|
XMRTOExchangeProvider(),
|
||||||
ChangeNowExchangeProvider(),
|
ChangeNowExchangeProvider(),
|
||||||
MorphTokenExchangeProvider(trades: trades)
|
MorphTokenExchangeProvider(trades: trades)
|
||||||
];
|
];
|
||||||
|
|
||||||
provider = providerList[ 0 ];
|
_initialPairBasedOnWallet();
|
||||||
|
|
||||||
depositCurrency = CryptoCurrency.xmr;
|
|
||||||
receiveCurrency = CryptoCurrency.btc;
|
|
||||||
isDepositAddressEnabled = !(depositCurrency == wallet.currency);
|
isDepositAddressEnabled = !(depositCurrency == wallet.currency);
|
||||||
isReceiveAddressEnabled = !(receiveCurrency == wallet.currency);
|
isReceiveAddressEnabled = !(receiveCurrency == wallet.currency);
|
||||||
depositAmount = '';
|
depositAmount = '';
|
||||||
|
@ -50,6 +47,7 @@ abstract class ExchangeViewModelBase with Store {
|
||||||
limitsState = LimitsInitialState();
|
limitsState = LimitsInitialState();
|
||||||
tradeState = ExchangeTradeStateInitial();
|
tradeState = ExchangeTradeStateInitial();
|
||||||
_cryptoNumberFormat = NumberFormat()..maximumFractionDigits = 12;
|
_cryptoNumberFormat = NumberFormat()..maximumFractionDigits = 12;
|
||||||
|
provider = providersForCurrentPair().first;
|
||||||
loadLimits();
|
loadLimits();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,7 +139,10 @@ abstract class ExchangeViewModelBase with Store {
|
||||||
provider
|
provider
|
||||||
.calculateAmount(
|
.calculateAmount(
|
||||||
from: depositCurrency, to: receiveCurrency, amount: _amount)
|
from: depositCurrency, to: receiveCurrency, amount: _amount)
|
||||||
.then((amount) => _cryptoNumberFormat.format(amount).toString().replaceAll(RegExp("\\,"), ""))
|
.then((amount) => _cryptoNumberFormat
|
||||||
|
.format(amount)
|
||||||
|
.toString()
|
||||||
|
.replaceAll(RegExp('\\,'), ''))
|
||||||
.then((amount) => depositAmount = amount);
|
.then((amount) => depositAmount = amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -159,7 +160,10 @@ abstract class ExchangeViewModelBase with Store {
|
||||||
provider
|
provider
|
||||||
.calculateAmount(
|
.calculateAmount(
|
||||||
from: depositCurrency, to: receiveCurrency, amount: _amount)
|
from: depositCurrency, to: receiveCurrency, amount: _amount)
|
||||||
.then((amount) => _cryptoNumberFormat.format(amount).toString().replaceAll(RegExp("\\,"), ""))
|
.then((amount) => _cryptoNumberFormat
|
||||||
|
.format(amount)
|
||||||
|
.toString()
|
||||||
|
.replaceAll(RegExp('\\,'), ''))
|
||||||
.then((amount) => receiveAmount = amount);
|
.then((amount) => receiveAmount = amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -217,10 +221,12 @@ abstract class ExchangeViewModelBase with Store {
|
||||||
|
|
||||||
if (limitsState is LimitsLoadedSuccessfully && amount != null) {
|
if (limitsState is LimitsLoadedSuccessfully && amount != null) {
|
||||||
if (double.parse(amount) < limits.min) {
|
if (double.parse(amount) < limits.min) {
|
||||||
tradeState = TradeIsCreatedFailure(error: S.current.error_text_minimal_limit('${provider.description}',
|
tradeState = TradeIsCreatedFailure(
|
||||||
|
error: S.current.error_text_minimal_limit('${provider.description}',
|
||||||
'${limits.min}', currency.toString()));
|
'${limits.min}', currency.toString()));
|
||||||
} else if (limits.max != null && double.parse(amount) > limits.max) {
|
} else if (limits.max != null && double.parse(amount) > limits.max) {
|
||||||
tradeState = TradeIsCreatedFailure(error: S.current.error_text_maximum_limit('${provider.description}',
|
tradeState = TradeIsCreatedFailure(
|
||||||
|
error: S.current.error_text_maximum_limit('${provider.description}',
|
||||||
'${limits.max}', currency.toString()));
|
'${limits.max}', currency.toString()));
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
|
@ -235,9 +241,10 @@ abstract class ExchangeViewModelBase with Store {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
tradeState = TradeIsCreatedFailure(error: S.current.error_text_limits_loading_failed('${provider.description}'));
|
tradeState = TradeIsCreatedFailure(
|
||||||
|
error: S.current
|
||||||
|
.error_text_limits_loading_failed('${provider.description}'));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
|
@ -295,4 +302,18 @@ abstract class ExchangeViewModelBase with Store {
|
||||||
return providers.isNotEmpty ? providers[0] : null;
|
return providers.isNotEmpty ? providers[0] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _initialPairBasedOnWallet() {
|
||||||
|
switch (wallet.type) {
|
||||||
|
case WalletType.monero:
|
||||||
|
depositCurrency = CryptoCurrency.xmr;
|
||||||
|
receiveCurrency = CryptoCurrency.btc;
|
||||||
|
break;
|
||||||
|
case WalletType.bitcoin:
|
||||||
|
depositCurrency = CryptoCurrency.btc;
|
||||||
|
receiveCurrency = CryptoCurrency.xmr;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,26 +1,88 @@
|
||||||
|
import 'package:cake_wallet/utils/item_cell.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
import 'package:cake_wallet/core/wallet_base.dart';
|
import 'package:cake_wallet/core/wallet_base.dart';
|
||||||
import 'package:cake_wallet/src/domain/common/node.dart';
|
import 'package:cake_wallet/src/domain/common/node.dart';
|
||||||
import 'package:cake_wallet/src/domain/common/node_list.dart';
|
import 'package:cake_wallet/src/domain/common/node_list.dart';
|
||||||
import 'package:cake_wallet/store/node_list_store.dart';
|
import 'package:cake_wallet/store/node_list_store.dart';
|
||||||
|
import 'package:cake_wallet/store/settings_store.dart';
|
||||||
|
import 'package:cake_wallet/src/domain/common/default_settings_migration.dart';
|
||||||
|
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
|
||||||
|
import 'package:cake_wallet/utils/mobx.dart';
|
||||||
|
|
||||||
part 'node_list_view_model.g.dart';
|
part 'node_list_view_model.g.dart';
|
||||||
|
|
||||||
class NodeListViewModel = NodeListViewModelBase with _$NodeListViewModel;
|
class NodeListViewModel = NodeListViewModelBase with _$NodeListViewModel;
|
||||||
|
|
||||||
abstract class NodeListViewModelBase with Store {
|
abstract class NodeListViewModelBase with Store {
|
||||||
NodeListViewModelBase(this._nodeListStore, this._nodeSource, this._wallet);
|
NodeListViewModelBase(
|
||||||
|
this._nodeListStore, this._nodeSource, this._wallet, this._settingsStore)
|
||||||
|
: nodes = ObservableList<ItemCell<Node>>() {
|
||||||
|
final currentNode = _settingsStore.getCurrentNode(_wallet.type);
|
||||||
|
final values = _nodeListStore.nodes;
|
||||||
|
nodes.clear();
|
||||||
|
nodes.addAll(values.where((Node node) => node.type == _wallet.type).map(
|
||||||
|
(Node val) => ItemCell<Node>(val,
|
||||||
|
isSelected: val.key == currentNode.key, key: val.key)));
|
||||||
|
connectDifferent(
|
||||||
|
_nodeListStore.nodes,
|
||||||
|
nodes,
|
||||||
|
(Node val) => ItemCell<Node>(val,
|
||||||
|
isSelected: val.key == currentNode.key, key: val.key),
|
||||||
|
filter: (Node val) => val.type == _wallet.type);
|
||||||
|
reaction((_) => _settingsStore.nodes[_wallet.type],
|
||||||
|
(Node _) => _updateCurrentNode());
|
||||||
|
}
|
||||||
|
|
||||||
@computed
|
ObservableList<ItemCell<Node>> nodes;
|
||||||
ObservableList<Node> get nodes => ObservableList<Node>.of(
|
|
||||||
_nodeListStore.nodes.where((node) => node.type == _wallet.type));
|
|
||||||
|
|
||||||
final WalletBase _wallet;
|
final WalletBase _wallet;
|
||||||
final Box<Node> _nodeSource;
|
final Box<Node> _nodeSource;
|
||||||
final NodeListStore _nodeListStore;
|
final NodeListStore _nodeListStore;
|
||||||
|
final SettingsStore _settingsStore;
|
||||||
|
|
||||||
Future<void> reset() async => await resetToDefault(_nodeSource);
|
Future<void> reset() async {
|
||||||
|
await resetToDefault(_nodeSource);
|
||||||
|
|
||||||
Future<void> delete(Node node) async => node.delete();
|
Node node;
|
||||||
|
|
||||||
|
switch (_wallet.type) {
|
||||||
|
case WalletType.bitcoin:
|
||||||
|
node = getBitcoinDefaultElectrumServer(nodes: _nodeSource);
|
||||||
|
break;
|
||||||
|
case WalletType.monero:
|
||||||
|
node = getMoneroDefaultNode(
|
||||||
|
nodes: _nodeSource,
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
await setAsCurrent(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> delete(Node node) async => _nodeSource.delete(node.key);
|
||||||
|
|
||||||
|
Future<void> setAsCurrent(Node node) async {
|
||||||
|
await _settingsStore.setCurrentNode(node, _wallet.type);
|
||||||
|
_updateCurrentNode();
|
||||||
|
await _wallet.connectToNode(node: node);
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
void _updateCurrentNode() {
|
||||||
|
final currentNode = _settingsStore.getCurrentNode(_wallet.type);
|
||||||
|
|
||||||
|
for (var i = 0; i < nodes.length; i++) {
|
||||||
|
final item = nodes[i];
|
||||||
|
final isSelected = item.value.key == currentNode.key;
|
||||||
|
|
||||||
|
if (item.isSelected != isSelected) {
|
||||||
|
nodes[i] = ItemCell<Node>(item.value,
|
||||||
|
isSelected: isSelected, key: item.keyIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
182
lib/view_model/send/send_view_model.dart
Normal file
182
lib/view_model/send/send_view_model.dart
Normal file
|
@ -0,0 +1,182 @@
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:mobx/mobx.dart';
|
||||||
|
import 'package:cake_wallet/core/template_validator.dart';
|
||||||
|
import 'package:cake_wallet/src/domain/common/calculate_fiat_amount.dart';
|
||||||
|
import 'package:cake_wallet/store/dashboard/fiat_convertation_store.dart';
|
||||||
|
import 'package:cake_wallet/core/address_validator.dart';
|
||||||
|
import 'package:cake_wallet/core/amount_validator.dart';
|
||||||
|
import 'package:cake_wallet/core/pending_transaction.dart';
|
||||||
|
import 'package:cake_wallet/core/validator.dart';
|
||||||
|
import 'package:cake_wallet/core/wallet_base.dart';
|
||||||
|
import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart';
|
||||||
|
import 'package:cake_wallet/monero/monero_wallet.dart';
|
||||||
|
import 'package:cake_wallet/src/domain/common/sync_status.dart';
|
||||||
|
import 'package:cake_wallet/src/domain/common/crypto_currency.dart';
|
||||||
|
import 'package:cake_wallet/src/domain/common/fiat_currency.dart';
|
||||||
|
import 'package:cake_wallet/src/domain/common/transaction_priority.dart';
|
||||||
|
import 'package:cake_wallet/store/settings_store.dart';
|
||||||
|
import 'package:cake_wallet/view_model/send/send_view_model_state.dart';
|
||||||
|
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
|
||||||
|
import 'package:cake_wallet/bitcoin/bitcoin_transaction_credentials.dart';
|
||||||
|
|
||||||
|
part 'send_view_model.g.dart';
|
||||||
|
|
||||||
|
class SendViewModel = SendViewModelBase with _$SendViewModel;
|
||||||
|
|
||||||
|
abstract class SendViewModelBase with Store {
|
||||||
|
SendViewModelBase(
|
||||||
|
this._wallet, this._settingsStore, this._fiatConversationStore)
|
||||||
|
: state = InitialSendViewModelState(),
|
||||||
|
_cryptoNumberFormat = NumberFormat()..maximumFractionDigits = 12,
|
||||||
|
// FIXME: need to be based on wallet type.
|
||||||
|
sendAll = false;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
SendViewModelState state;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
String fiatAmount;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
String cryptoAmount;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
String address;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
bool sendAll;
|
||||||
|
|
||||||
|
FiatCurrency get fiat => _settingsStore.fiatCurrency;
|
||||||
|
|
||||||
|
TransactionPriority get transactionPriority =>
|
||||||
|
_settingsStore.transactionPriority;
|
||||||
|
|
||||||
|
double get estimatedFee =>
|
||||||
|
_wallet.calculateEstimatedFee(_settingsStore.transactionPriority);
|
||||||
|
|
||||||
|
CryptoCurrency get currency => _wallet.currency;
|
||||||
|
|
||||||
|
Validator get amountValidator => AmountValidator(type: _wallet.type);
|
||||||
|
|
||||||
|
Validator get addressValidator => AddressValidator(type: _wallet.currency);
|
||||||
|
|
||||||
|
Validator get templateValidator => TemplateValidator();
|
||||||
|
|
||||||
|
PendingTransaction pendingTransaction;
|
||||||
|
|
||||||
|
@computed
|
||||||
|
String get balance {
|
||||||
|
if (_wallet is MoneroWallet) {
|
||||||
|
_wallet.balance.formattedUnlockedBalance;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_wallet is BitcoinWallet) {
|
||||||
|
_wallet.balance.confirmedFormatted;
|
||||||
|
}
|
||||||
|
|
||||||
|
return '0.0';
|
||||||
|
}
|
||||||
|
|
||||||
|
@computed
|
||||||
|
bool get isReadyForSend => _wallet.syncStatus is SyncedSyncStatus;
|
||||||
|
|
||||||
|
final WalletBase _wallet;
|
||||||
|
final SettingsStore _settingsStore;
|
||||||
|
final FiatConvertationStore _fiatConversationStore;
|
||||||
|
final NumberFormat _cryptoNumberFormat;
|
||||||
|
|
||||||
|
@action
|
||||||
|
void setSendAll() => sendAll = true;
|
||||||
|
|
||||||
|
@action
|
||||||
|
void reset() {
|
||||||
|
cryptoAmount = '';
|
||||||
|
fiatAmount = '';
|
||||||
|
address = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
Future<void> createTransaction() async {
|
||||||
|
try {
|
||||||
|
state = TransactionIsCreating();
|
||||||
|
pendingTransaction = await _wallet.createTransaction(_credentials());
|
||||||
|
state = TransactionCreatedSuccessfully();
|
||||||
|
} catch (e) {
|
||||||
|
state = SendingFailed(error: e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
Future<void> commitTransaction() async {
|
||||||
|
try {
|
||||||
|
state = TransactionCommitting();
|
||||||
|
await pendingTransaction.commit();
|
||||||
|
state = TransactionCommitted();
|
||||||
|
} catch (e) {
|
||||||
|
state = SendingFailed(error: e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
void setCryptoAmount(String amount) {
|
||||||
|
// FIXME: hardcoded value.
|
||||||
|
if (amount.toUpperCase() != 'ALL') {
|
||||||
|
sendAll = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
cryptoAmount = amount;
|
||||||
|
_updateFiatAmount();
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
void setFiatAmount(String amount) {
|
||||||
|
fiatAmount = amount;
|
||||||
|
_updateCryptoAmount();
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
void _updateFiatAmount() {
|
||||||
|
try {
|
||||||
|
final fiat = calculateFiatAmount(
|
||||||
|
price: _fiatConversationStore.price,
|
||||||
|
cryptoAmount: cryptoAmount.replaceAll(',', '.'));
|
||||||
|
if (fiatAmount != fiat) {
|
||||||
|
fiatAmount = fiat;
|
||||||
|
}
|
||||||
|
} catch (_) {
|
||||||
|
fiatAmount = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
void _updateCryptoAmount() {
|
||||||
|
try {
|
||||||
|
final crypto = double.parse(fiatAmount.replaceAll(',', '.')) /
|
||||||
|
_fiatConversationStore.price;
|
||||||
|
final cryptoAmountTmp = _cryptoNumberFormat.format(crypto);
|
||||||
|
|
||||||
|
if (cryptoAmount != cryptoAmountTmp) {
|
||||||
|
cryptoAmount = cryptoAmountTmp;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
cryptoAmount = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Object _credentials() {
|
||||||
|
final amount =
|
||||||
|
!sendAll ? double.parse(cryptoAmount.replaceAll(',', '.')) : null;
|
||||||
|
|
||||||
|
switch (_wallet.type) {
|
||||||
|
case WalletType.bitcoin:
|
||||||
|
return BitcoinTransactionCredentials(
|
||||||
|
address, amount, _settingsStore.transactionPriority);
|
||||||
|
case WalletType.monero:
|
||||||
|
// FIXME: Wrong credentials
|
||||||
|
return BitcoinTransactionCredentials(
|
||||||
|
address, amount, _settingsStore.transactionPriority);
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
18
lib/view_model/send/send_view_model_state.dart
Normal file
18
lib/view_model/send/send_view_model_state.dart
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
|
abstract class SendViewModelState {}
|
||||||
|
|
||||||
|
class InitialSendViewModelState extends SendViewModelState {}
|
||||||
|
|
||||||
|
class TransactionIsCreating extends SendViewModelState {}
|
||||||
|
class TransactionCreatedSuccessfully extends SendViewModelState {}
|
||||||
|
|
||||||
|
class TransactionCommitting extends SendViewModelState {}
|
||||||
|
|
||||||
|
class TransactionCommitted extends SendViewModelState {}
|
||||||
|
|
||||||
|
class SendingFailed extends SendViewModelState {
|
||||||
|
SendingFailed({@required this.error});
|
||||||
|
|
||||||
|
String error;
|
||||||
|
}
|
|
@ -193,6 +193,11 @@ abstract class SendViewModelBase with Store {
|
||||||
fiatAmount = '';
|
fiatAmount = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
void setTransactionPriority(TransactionPriority transactionPriority) {
|
||||||
|
_settingsStore.transactionPriority = transactionPriority;
|
||||||
|
}
|
||||||
|
|
||||||
final WalletBase _wallet;
|
final WalletBase _wallet;
|
||||||
|
|
||||||
final SettingsStore _settingsStore;
|
final SettingsStore _settingsStore;
|
||||||
|
|
|
@ -5,13 +5,20 @@ class PickerListItem<ItemType> extends SettingsListItem {
|
||||||
PickerListItem(
|
PickerListItem(
|
||||||
{@required String title,
|
{@required String title,
|
||||||
@required this.selectedItem,
|
@required this.selectedItem,
|
||||||
@required this.setItem,
|
|
||||||
@required this.items,
|
@required this.items,
|
||||||
|
void Function(ItemType item) onItemSelected,
|
||||||
this.isAlwaysShowScrollThumb = false})
|
this.isAlwaysShowScrollThumb = false})
|
||||||
: super(title);
|
: _onItemSelected = onItemSelected,
|
||||||
|
super(title);
|
||||||
|
|
||||||
final ItemType Function() selectedItem;
|
final ItemType Function() selectedItem;
|
||||||
final Function(ItemType value) setItem;
|
|
||||||
final List<ItemType> items;
|
final List<ItemType> items;
|
||||||
|
final void Function(ItemType item) _onItemSelected;
|
||||||
final bool isAlwaysShowScrollThumb;
|
final bool isAlwaysShowScrollThumb;
|
||||||
|
|
||||||
|
void onItemSelected(dynamic item) {
|
||||||
|
if (item is ItemType) {
|
||||||
|
_onItemSelected?.call(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
import 'package:cake_wallet/core/wallet_base.dart';
|
||||||
|
import 'package:cake_wallet/src/domain/common/biometric_auth.dart';
|
||||||
|
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
|
||||||
import 'package:cake_wallet/di.dart';
|
import 'package:cake_wallet/di.dart';
|
||||||
import 'package:cake_wallet/store/theme_changer_store.dart';
|
import 'package:cake_wallet/store/theme_changer_store.dart';
|
||||||
import 'package:cake_wallet/themes.dart';
|
import 'package:cake_wallet/themes.dart';
|
||||||
|
@ -25,33 +28,38 @@ part 'settings_view_model.g.dart';
|
||||||
class SettingsViewModel = SettingsViewModelBase with _$SettingsViewModel;
|
class SettingsViewModel = SettingsViewModelBase with _$SettingsViewModel;
|
||||||
|
|
||||||
abstract class SettingsViewModelBase with Store {
|
abstract class SettingsViewModelBase with Store {
|
||||||
SettingsViewModelBase(this._settingsStore) : itemHeaders = {} {
|
SettingsViewModelBase(this._settingsStore, WalletBase wallet)
|
||||||
|
: itemHeaders = {},
|
||||||
|
_walletType = wallet.type,
|
||||||
|
_biometricAuth = BiometricAuth() {
|
||||||
currentVersion = '';
|
currentVersion = '';
|
||||||
PackageInfo.fromPlatform().then((PackageInfo packageInfo) => currentVersion = packageInfo.version);
|
PackageInfo.fromPlatform().then(
|
||||||
|
(PackageInfo packageInfo) => currentVersion = packageInfo.version);
|
||||||
sections = [
|
sections = [
|
||||||
[
|
[
|
||||||
PickerListItem(
|
PickerListItem(
|
||||||
title: S.current.settings_display_balance_as,
|
title: S.current.settings_display_balance_as,
|
||||||
items: BalanceDisplayMode.all,
|
items: BalanceDisplayMode.all,
|
||||||
setItem: (dynamic value) => balanceDisplayMode = value as BalanceDisplayMode,
|
|
||||||
selectedItem: () => balanceDisplayMode),
|
selectedItem: () => balanceDisplayMode),
|
||||||
PickerListItem(
|
PickerListItem(
|
||||||
title: S.current.settings_currency,
|
title: S.current.settings_currency,
|
||||||
items: FiatCurrency.all,
|
items: FiatCurrency.all,
|
||||||
setItem: (dynamic value) => fiatCurrency = value as FiatCurrency,
|
|
||||||
isAlwaysShowScrollThumb: true,
|
isAlwaysShowScrollThumb: true,
|
||||||
selectedItem: () => fiatCurrency),
|
selectedItem: () => fiatCurrency,
|
||||||
|
onItemSelected: (FiatCurrency currency) =>
|
||||||
|
setFiatCurrency(currency)),
|
||||||
PickerListItem(
|
PickerListItem(
|
||||||
title: S.current.settings_fee_priority,
|
title: S.current.settings_fee_priority,
|
||||||
items: TransactionPriority.all,
|
items: _transactionPriorities(wallet.type),
|
||||||
setItem: (dynamic value) => transactionPriority = value as TransactionPriority,
|
selectedItem: () => transactionPriority,
|
||||||
isAlwaysShowScrollThumb: true,
|
isAlwaysShowScrollThumb: true,
|
||||||
selectedItem: () => transactionPriority),
|
onItemSelected: (TransactionPriority priority) =>
|
||||||
|
_settingsStore.transactionPriority = priority),
|
||||||
SwitcherListItem(
|
SwitcherListItem(
|
||||||
title: S.current.settings_save_recipient_address,
|
title: S.current.settings_save_recipient_address,
|
||||||
value: () => shouldSaveRecipientAddress,
|
value: () => shouldSaveRecipientAddress,
|
||||||
onValueChange: (bool value) => shouldSaveRecipientAddress = value)
|
onValueChange: (_, bool value) =>
|
||||||
|
setShouldSaveRecipientAddress(value))
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
RegularListItem(
|
RegularListItem(
|
||||||
|
@ -76,15 +84,37 @@ abstract class SettingsViewModelBase with Store {
|
||||||
SwitcherListItem(
|
SwitcherListItem(
|
||||||
title: S.current.settings_allow_biometrical_authentication,
|
title: S.current.settings_allow_biometrical_authentication,
|
||||||
value: () => allowBiometricalAuthentication,
|
value: () => allowBiometricalAuthentication,
|
||||||
onValueChange: (bool value) =>
|
onValueChange: (BuildContext context, bool value) {
|
||||||
allowBiometricalAuthentication = value),
|
if (value) {
|
||||||
|
Navigator.of(context).pushNamed(Routes.auth, arguments:
|
||||||
|
(bool isAuthenticatedSuccessfully,
|
||||||
|
AuthPageState auth) async {
|
||||||
|
if (isAuthenticatedSuccessfully) {
|
||||||
|
if (await _biometricAuth.canCheckBiometrics() &&
|
||||||
|
await _biometricAuth.isAuthenticated()) {
|
||||||
|
setAllowBiometricalAuthentication(
|
||||||
|
isAuthenticatedSuccessfully);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setAllowBiometricalAuthentication(
|
||||||
|
isAuthenticatedSuccessfully);
|
||||||
|
}
|
||||||
|
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setAllowBiometricalAuthentication(value);
|
||||||
|
}
|
||||||
|
}),
|
||||||
SwitcherListItem(
|
SwitcherListItem(
|
||||||
title: S.current.settings_dark_mode,
|
title: S.current.settings_dark_mode,
|
||||||
value: () => _settingsStore.isDarkTheme,
|
value: () => _settingsStore.isDarkTheme,
|
||||||
onValueChange: (bool value) {
|
onValueChange: (_, bool value) {
|
||||||
_settingsStore.isDarkTheme = value;
|
_settingsStore.isDarkTheme = value;
|
||||||
getIt.get<ThemeChangerStore>().themeChanger.setTheme(
|
getIt
|
||||||
value ? Themes.darkTheme : Themes.lightTheme);
|
.get<ThemeChangerStore>()
|
||||||
|
.themeChanger
|
||||||
|
.setTheme(value ? Themes.darkTheme : Themes.lightTheme);
|
||||||
})
|
})
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
|
@ -128,9 +158,7 @@ abstract class SettingsViewModelBase with Store {
|
||||||
Navigator.pushNamed(context, Routes.faq),
|
Navigator.pushNamed(context, Routes.faq),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
[
|
[VersionListItem(title: currentVersion)]
|
||||||
VersionListItem(title: currentVersion)
|
|
||||||
]
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,7 +166,7 @@ abstract class SettingsViewModelBase with Store {
|
||||||
String currentVersion;
|
String currentVersion;
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
Node get node => _settingsStore.node;
|
Node get node => _settingsStore.getCurrentNode(_walletType);
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
FiatCurrency get fiatCurrency => _settingsStore.fiatCurrency;
|
FiatCurrency get fiatCurrency => _settingsStore.fiatCurrency;
|
||||||
|
@ -159,40 +187,30 @@ abstract class SettingsViewModelBase with Store {
|
||||||
bool get shouldSaveRecipientAddress =>
|
bool get shouldSaveRecipientAddress =>
|
||||||
_settingsStore.shouldSaveRecipientAddress;
|
_settingsStore.shouldSaveRecipientAddress;
|
||||||
|
|
||||||
@action
|
|
||||||
set shouldSaveRecipientAddress(bool value) =>
|
|
||||||
_settingsStore.shouldSaveRecipientAddress = value;
|
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
bool get allowBiometricalAuthentication =>
|
bool get allowBiometricalAuthentication =>
|
||||||
_settingsStore.allowBiometricalAuthentication;
|
_settingsStore.allowBiometricalAuthentication;
|
||||||
|
|
||||||
@action
|
|
||||||
set allowBiometricalAuthentication(bool value) =>
|
|
||||||
_settingsStore.allowBiometricalAuthentication = value;
|
|
||||||
|
|
||||||
@action
|
|
||||||
set balanceDisplayMode(BalanceDisplayMode value) =>
|
|
||||||
_settingsStore.balanceDisplayMode = value;
|
|
||||||
|
|
||||||
@action
|
|
||||||
set fiatCurrency(FiatCurrency value) =>
|
|
||||||
_settingsStore.fiatCurrency = value;
|
|
||||||
|
|
||||||
@action
|
|
||||||
set transactionPriority(TransactionPriority value) =>
|
|
||||||
_settingsStore.transactionPriority = value;
|
|
||||||
|
|
||||||
// @observable
|
|
||||||
// bool isDarkTheme;
|
|
||||||
//
|
|
||||||
// @observable
|
|
||||||
// int defaultPinLength;
|
|
||||||
|
|
||||||
// @observable
|
|
||||||
final Map<String, String> itemHeaders;
|
final Map<String, String> itemHeaders;
|
||||||
List<List<SettingsListItem>> sections;
|
List<List<SettingsListItem>> sections;
|
||||||
final SettingsStore _settingsStore;
|
final SettingsStore _settingsStore;
|
||||||
|
final WalletType _walletType;
|
||||||
|
final BiometricAuth _biometricAuth;
|
||||||
|
|
||||||
|
@action
|
||||||
|
void setBalanceDisplayMode(BalanceDisplayMode value) =>
|
||||||
|
_settingsStore.balanceDisplayMode = value;
|
||||||
|
|
||||||
|
@action
|
||||||
|
void setFiatCurrency(FiatCurrency value) =>
|
||||||
|
_settingsStore.fiatCurrency = value;
|
||||||
|
@action
|
||||||
|
void setShouldSaveRecipientAddress(bool value) =>
|
||||||
|
_settingsStore.shouldSaveRecipientAddress = value;
|
||||||
|
|
||||||
|
@action
|
||||||
|
void setAllowBiometricalAuthentication(bool value) =>
|
||||||
|
_settingsStore.allowBiometricalAuthentication = value;
|
||||||
|
|
||||||
@action
|
@action
|
||||||
void toggleTransactionsDisplay() =>
|
void toggleTransactionsDisplay() =>
|
||||||
|
@ -220,4 +238,19 @@ abstract class SettingsViewModelBase with Store {
|
||||||
|
|
||||||
@action
|
@action
|
||||||
void _showTrades() => actionlistDisplayMode.add(ActionListDisplayMode.trades);
|
void _showTrades() => actionlistDisplayMode.add(ActionListDisplayMode.trades);
|
||||||
|
|
||||||
|
static List<TransactionPriority> _transactionPriorities(WalletType type) {
|
||||||
|
switch (type) {
|
||||||
|
case WalletType.monero:
|
||||||
|
return TransactionPriority.all;
|
||||||
|
case WalletType.bitcoin:
|
||||||
|
return [
|
||||||
|
TransactionPriority.slow,
|
||||||
|
TransactionPriority.regular,
|
||||||
|
TransactionPriority.fast
|
||||||
|
];
|
||||||
|
default:
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:cake_wallet/view_model/settings/settings_list_item.dart';
|
import 'package:cake_wallet/view_model/settings/settings_list_item.dart';
|
||||||
|
|
||||||
|
@ -9,5 +10,5 @@ class SwitcherListItem extends SettingsListItem {
|
||||||
: super(title);
|
: super(title);
|
||||||
|
|
||||||
final bool Function() value;
|
final bool Function() value;
|
||||||
final void Function(bool value) onValueChange;
|
final void Function(BuildContext context, bool value) onValueChange;
|
||||||
}
|
}
|
|
@ -67,7 +67,8 @@ abstract class WalletAddressListViewModelBase with Store {
|
||||||
WalletType get type => _wallet.type;
|
WalletType get type => _wallet.type;
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
WalletAddressListItem get address => WalletAddressListItem(address: _wallet.address);
|
WalletAddressListItem get address =>
|
||||||
|
WalletAddressListItem(address: _wallet.address);
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
PaymentURI get uri {
|
PaymentURI get uri {
|
||||||
|
@ -100,15 +101,16 @@ abstract class WalletAddressListViewModelBase with Store {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (wallet is BitcoinWallet) {
|
if (wallet is BitcoinWallet) {
|
||||||
final bitcoinAddresses = wallet.addresses.map(
|
final bitcoinAddresses = wallet.addresses.map((addr) =>
|
||||||
(addr) => WalletAddressListItem(name: addr.label, address: addr.address));
|
WalletAddressListItem(name: addr.label, address: addr.address));
|
||||||
addressList.addAll(bitcoinAddresses);
|
addressList.addAll(bitcoinAddresses);
|
||||||
}
|
}
|
||||||
|
|
||||||
return addressList;
|
return addressList;
|
||||||
}
|
}
|
||||||
|
|
||||||
set address(WalletAddressListItem address) => _wallet.address = address.address;
|
set address(WalletAddressListItem address) =>
|
||||||
|
_wallet.address = address.address;
|
||||||
|
|
||||||
bool hasAccounts;
|
bool hasAccounts;
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@ abstract class WalletCreationVMBase with Store {
|
||||||
|
|
||||||
final bool isRecovery;
|
final bool isRecovery;
|
||||||
|
|
||||||
Box<WalletInfo> _walletInfoSource;
|
final Box<WalletInfo> _walletInfoSource;
|
||||||
|
|
||||||
Future<void> create({dynamic options}) async {
|
Future<void> create({dynamic options}) async {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -3,9 +3,10 @@ import 'package:cake_wallet/src/domain/common/wallet_type.dart';
|
||||||
|
|
||||||
class WalletListItem {
|
class WalletListItem {
|
||||||
const WalletListItem(
|
const WalletListItem(
|
||||||
{@required this.name, @required this.type, this.isCurrent = false});
|
{@required this.name, @required this.type, @required this.key, this.isCurrent = false});
|
||||||
|
|
||||||
final String name;
|
final String name;
|
||||||
final WalletType type;
|
final WalletType type;
|
||||||
final bool isCurrent;
|
final bool isCurrent;
|
||||||
|
final dynamic key;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,11 +17,7 @@ abstract class WalletListViewModelBase with Store {
|
||||||
WalletListViewModelBase(
|
WalletListViewModelBase(
|
||||||
this._walletInfoSource, this._appStore, this._keyService) {
|
this._walletInfoSource, this._appStore, this._keyService) {
|
||||||
wallets = ObservableList<WalletListItem>();
|
wallets = ObservableList<WalletListItem>();
|
||||||
wallets.addAll(_walletInfoSource.values.map((info) => WalletListItem(
|
_updateList();
|
||||||
name: info.name,
|
|
||||||
type: info.type,
|
|
||||||
isCurrent: info.name == _appStore.wallet.name &&
|
|
||||||
info.type == _appStore.wallet.type)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
|
@ -40,7 +36,12 @@ abstract class WalletListViewModelBase with Store {
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
Future<void> remove(WalletListItem wallet) async {}
|
Future<void> remove(WalletListItem wallet) async {
|
||||||
|
final walletService = _getWalletService(wallet.type);
|
||||||
|
await walletService.remove(wallet.name);
|
||||||
|
await _walletInfoSource.delete(wallet.key);
|
||||||
|
_updateList();
|
||||||
|
}
|
||||||
|
|
||||||
WalletService _getWalletService(WalletType type) {
|
WalletService _getWalletService(WalletType type) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
|
@ -52,4 +53,14 @@ abstract class WalletListViewModelBase with Store {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _updateList() {
|
||||||
|
wallets.clear();
|
||||||
|
wallets.addAll(_walletInfoSource.values.map((info) => WalletListItem(
|
||||||
|
name: info.name,
|
||||||
|
type: info.type,
|
||||||
|
key: info.key,
|
||||||
|
isCurrent: info.name == _appStore.wallet.name &&
|
||||||
|
info.type == _appStore.wallet.type)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
152
pubspec.lock
152
pubspec.lock
|
@ -1,13 +1,20 @@
|
||||||
# Generated by pub
|
# Generated by pub
|
||||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||||
packages:
|
packages:
|
||||||
analyzer:
|
_fe_analyzer_shared:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: _fe_analyzer_shared
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "6.0.0"
|
||||||
|
analyzer:
|
||||||
|
dependency: "direct overridden"
|
||||||
description:
|
description:
|
||||||
name: analyzer
|
name: analyzer
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.36.4"
|
version: "0.39.14"
|
||||||
archive:
|
archive:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -28,14 +35,14 @@ packages:
|
||||||
name: asn1lib
|
name: asn1lib
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.6.4"
|
version: "0.6.5"
|
||||||
async:
|
async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: async
|
name: async
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.1"
|
version: "2.4.2"
|
||||||
auto_size_text:
|
auto_size_text:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -105,7 +112,7 @@ packages:
|
||||||
name: build
|
name: build
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.2"
|
version: "1.3.0"
|
||||||
build_config:
|
build_config:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -121,26 +128,26 @@ packages:
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.4"
|
version: "2.1.4"
|
||||||
build_resolvers:
|
build_resolvers:
|
||||||
dependency: transitive
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: build_resolvers
|
name: build_resolvers
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.1"
|
version: "1.3.11"
|
||||||
build_runner:
|
build_runner:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: build_runner
|
name: build_runner
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.9.0"
|
version: "1.10.1"
|
||||||
build_runner_core:
|
build_runner_core:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: build_runner_core
|
name: build_runner_core
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.1.0"
|
version: "6.0.1"
|
||||||
built_collection:
|
built_collection:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -161,7 +168,7 @@ packages:
|
||||||
name: characters
|
name: characters
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.3.1"
|
version: "1.0.0"
|
||||||
charcode:
|
charcode:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -176,6 +183,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.2"
|
version: "1.0.2"
|
||||||
|
cli_util:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: cli_util
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.4"
|
||||||
clock:
|
clock:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -189,14 +203,14 @@ packages:
|
||||||
name: code_builder
|
name: code_builder
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.3.0"
|
version: "3.4.1"
|
||||||
collection:
|
collection:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: collection
|
name: collection
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.14.12"
|
version: "1.14.13"
|
||||||
convert:
|
convert:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -210,14 +224,14 @@ packages:
|
||||||
name: crypto
|
name: crypto
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.4"
|
version: "2.1.5"
|
||||||
csslib:
|
csslib:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: csslib
|
name: csslib
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.16.1"
|
version: "0.16.2"
|
||||||
cupertino_icons:
|
cupertino_icons:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -245,7 +259,7 @@ packages:
|
||||||
name: dartx
|
name: dartx
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.3.0"
|
version: "0.5.0"
|
||||||
date_range_picker:
|
date_range_picker:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -273,7 +287,7 @@ packages:
|
||||||
name: dotted_border
|
name: dotted_border
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.5"
|
version: "1.0.6"
|
||||||
encrypt:
|
encrypt:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -288,6 +302,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.2"
|
version: "1.0.2"
|
||||||
|
fake_async:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: fake_async
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.0"
|
||||||
ffi:
|
ffi:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -301,7 +322,7 @@ packages:
|
||||||
name: file
|
name: file
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.2.0"
|
version: "5.2.1"
|
||||||
fixnum:
|
fixnum:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -339,7 +360,7 @@ packages:
|
||||||
name: flutter_mobx
|
name: flutter_mobx
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.3.0+1"
|
version: "1.1.0+2"
|
||||||
flutter_plugin_android_lifecycle:
|
flutter_plugin_android_lifecycle:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -360,7 +381,7 @@ packages:
|
||||||
name: flutter_slidable
|
name: flutter_slidable
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.5.4"
|
version: "0.5.7"
|
||||||
flutter_test:
|
flutter_test:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description: flutter
|
description: flutter
|
||||||
|
@ -371,20 +392,13 @@ packages:
|
||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
front_end:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: front_end
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "0.1.19"
|
|
||||||
get_it:
|
get_it:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: get_it
|
name: get_it
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.0.2"
|
version: "4.0.4"
|
||||||
glob:
|
glob:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -412,21 +426,21 @@ packages:
|
||||||
name: hive
|
name: hive
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.1+1"
|
version: "1.4.4"
|
||||||
hive_flutter:
|
hive_flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: hive_flutter
|
name: hive_flutter
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.3.0+2"
|
version: "0.3.1"
|
||||||
hive_generator:
|
hive_generator:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: hive_generator
|
name: hive_generator
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.0+2"
|
version: "0.7.1"
|
||||||
html:
|
html:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -440,7 +454,7 @@ packages:
|
||||||
name: http
|
name: http
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.12.1"
|
version: "0.12.2"
|
||||||
http_multi_server:
|
http_multi_server:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -461,7 +475,7 @@ packages:
|
||||||
name: image
|
name: image
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.12"
|
version: "2.1.14"
|
||||||
intl:
|
intl:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -490,20 +504,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.1"
|
version: "3.0.1"
|
||||||
kernel:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: kernel
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "0.3.19"
|
|
||||||
local_auth:
|
local_auth:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: local_auth
|
name: local_auth
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.6.2+3"
|
version: "0.6.3+1"
|
||||||
logging:
|
logging:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -517,7 +524,7 @@ packages:
|
||||||
name: matcher
|
name: matcher
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.12.6"
|
version: "0.12.8"
|
||||||
meta:
|
meta:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -531,21 +538,21 @@ packages:
|
||||||
name: mime
|
name: mime
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.9.6+3"
|
version: "0.9.7"
|
||||||
mobx:
|
mobx:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: mobx
|
name: mobx
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.3.10"
|
version: "1.2.1+2"
|
||||||
mobx_codegen:
|
mobx_codegen:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: mobx_codegen
|
name: mobx_codegen
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.3.3+1"
|
version: "1.1.0+1"
|
||||||
node_interop:
|
node_interop:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -573,14 +580,7 @@ packages:
|
||||||
name: package_info
|
name: package_info
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.4.1"
|
version: "0.4.3"
|
||||||
package_resolver:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: package_resolver
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "1.0.10"
|
|
||||||
password:
|
password:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -594,14 +594,14 @@ packages:
|
||||||
name: path
|
name: path
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.6.4"
|
version: "1.7.0"
|
||||||
path_drawing:
|
path_drawing:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_drawing
|
name: path_drawing
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.4.1"
|
version: "0.4.1+1"
|
||||||
path_parsing:
|
path_parsing:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -615,14 +615,14 @@ packages:
|
||||||
name: path_provider
|
name: path_provider
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.6.11"
|
version: "1.6.14"
|
||||||
path_provider_linux:
|
path_provider_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_linux
|
name: path_provider_linux
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.0.1+1"
|
version: "0.0.1+2"
|
||||||
path_provider_macos:
|
path_provider_macos:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -636,7 +636,7 @@ packages:
|
||||||
name: path_provider_platform_interface
|
name: path_provider_platform_interface
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.2"
|
version: "1.0.3"
|
||||||
pedantic:
|
pedantic:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
|
@ -650,7 +650,7 @@ packages:
|
||||||
name: petitparser
|
name: petitparser
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.0"
|
version: "3.0.4"
|
||||||
platform:
|
platform:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -748,14 +748,21 @@ packages:
|
||||||
name: share
|
name: share
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.6.4+3"
|
version: "0.6.5"
|
||||||
shared_preferences:
|
shared_preferences:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: shared_preferences
|
name: shared_preferences
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.5.7+3"
|
version: "0.5.10"
|
||||||
|
shared_preferences_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_linux
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.0.2+2"
|
||||||
shared_preferences_macos:
|
shared_preferences_macos:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -783,7 +790,7 @@ packages:
|
||||||
name: shelf
|
name: shelf
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.7"
|
version: "0.7.9"
|
||||||
shelf_web_socket:
|
shelf_web_socket:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -823,7 +830,7 @@ packages:
|
||||||
name: stack_trace
|
name: stack_trace
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.9.3"
|
version: "1.9.5"
|
||||||
stream_channel:
|
stream_channel:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -858,7 +865,7 @@ packages:
|
||||||
name: test_api
|
name: test_api
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.2.15"
|
version: "0.2.17"
|
||||||
time:
|
time:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -879,14 +886,21 @@ packages:
|
||||||
name: typed_data
|
name: typed_data
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.6"
|
version: "1.2.0"
|
||||||
url_launcher:
|
url_launcher:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: url_launcher
|
name: url_launcher
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.4.11"
|
version: "5.5.1"
|
||||||
|
url_launcher_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_linux
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.0.1+1"
|
||||||
url_launcher_macos:
|
url_launcher_macos:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -900,14 +914,14 @@ packages:
|
||||||
name: url_launcher_platform_interface
|
name: url_launcher_platform_interface
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.7"
|
version: "1.0.8"
|
||||||
url_launcher_web:
|
url_launcher_web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_web
|
name: url_launcher_web
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.1+6"
|
version: "0.1.3"
|
||||||
uuid:
|
uuid:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -949,7 +963,7 @@ packages:
|
||||||
name: xml
|
name: xml
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.6.1"
|
version: "4.2.0"
|
||||||
yaml:
|
yaml:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -958,5 +972,5 @@ packages:
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.1"
|
version: "2.2.1"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=2.7.0 <3.0.0"
|
dart: ">=2.9.0-14.0.dev <3.0.0"
|
||||||
flutter: ">=1.12.13+hotfix.5 <2.0.0"
|
flutter: ">=1.12.13+hotfix.5 <2.0.0"
|
||||||
|
|
16
pubspec.yaml
16
pubspec.yaml
|
@ -34,8 +34,8 @@ dependencies:
|
||||||
barcode_scan: any
|
barcode_scan: any
|
||||||
http: ^0.12.0+2
|
http: ^0.12.0+2
|
||||||
path_provider: ^1.3.0
|
path_provider: ^1.3.0
|
||||||
mobx: ^0.3.7
|
mobx: ^1.2.1+2
|
||||||
flutter_mobx: 0.3.0+1
|
flutter_mobx: ^1.1.0+2
|
||||||
flutter_slidable: ^0.5.3
|
flutter_slidable: ^0.5.3
|
||||||
share: ^0.6.2+1
|
share: ^0.6.2+1
|
||||||
esys_flutter_share: ^1.0.2
|
esys_flutter_share: ^1.0.2
|
||||||
|
@ -43,8 +43,8 @@ dependencies:
|
||||||
dio: 3.0.7
|
dio: 3.0.7
|
||||||
cw_monero:
|
cw_monero:
|
||||||
path: ./cw_monero
|
path: ./cw_monero
|
||||||
hive: ^1.4.1+1
|
hive: ^1.4.2
|
||||||
hive_flutter: ^0.3.0+2
|
hive_flutter: ^0.3.1
|
||||||
local_auth: ^0.6.1
|
local_auth: ^0.6.1
|
||||||
package_info: ^0.4.0+13
|
package_info: ^0.4.0+13
|
||||||
devicelocale: ^0.2.1
|
devicelocale: ^0.2.1
|
||||||
|
@ -64,15 +64,17 @@ dependencies:
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
build_runner: ^1.3.1
|
build_runner: 1.10.1
|
||||||
mobx_codegen: 0.3.3+1
|
build_resolvers: ^1.3.10
|
||||||
|
mobx_codegen: ^1.1.0+1
|
||||||
hive_generator: ^0.7.0+2
|
hive_generator: ^0.7.0+2
|
||||||
flutter_launcher_icons: ^0.7.4
|
flutter_launcher_icons: ^0.7.4
|
||||||
pedantic: ^1.8.0
|
pedantic: ^1.8.0
|
||||||
|
|
||||||
# Fix for hive https://github.com/hivedb/hive/issues/247#issuecomment-606838497
|
# Fix for hive https://github.com/hivedb/hive/issues/247#issuecomment-606838497
|
||||||
dependency_overrides:
|
dependency_overrides:
|
||||||
dartx: ^0.3.0
|
dartx: ^0.5.0
|
||||||
|
analyzer: 0.39.14
|
||||||
|
|
||||||
flutter_icons:
|
flutter_icons:
|
||||||
image_path: "assets/images/app_logo.png"
|
image_path: "assets/images/app_logo.png"
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"welcome" : "Welcome to",
|
"welcome" : "Welcome to",
|
||||||
"cake_wallet" : "Cake Wallet",
|
"cake_wallet" : "Cake Wallet",
|
||||||
"first_wallet_text" : "Awesome wallet for Monero",
|
"first_wallet_text" : "Awesome wallet for Monero and Bitcoin",
|
||||||
"please_make_selection" : "Please make selection below to create or recover your wallet.",
|
"please_make_selection" : "Please make selection below to create or recover your wallet.",
|
||||||
"create_new" : "Create New Wallet",
|
"create_new" : "Create New Wallet",
|
||||||
"restore_wallet" : "Restore Wallet",
|
"restore_wallet" : "Restore Wallet",
|
||||||
|
|
Loading…
Reference in a new issue