Merge pull request from cake-tech/btc

Btc
This commit is contained in:
M 2020-09-02 11:48:30 +03:00
commit e8db93a079
121 changed files with 5077 additions and 2677 deletions
.gitignore
assets
ios
lib
bitcoin
core
di.dart
generated
main.dart
monero
palette.dart
reactions
router.dart
src
store
themes.dart
utils
view_model

4
.gitignore vendored
View file

@ -89,4 +89,6 @@ android/key.properties
**/tool/.secrets-prod.json **/tool/.secrets-prod.json
**/lib/.secrets.g.dart **/lib/.secrets.g.dart
vendor/ vendor/
android/app/.cxx/**

View file

@ -1,2 +1,4 @@
- -
uri: electrum2.hodlister.co:50002 uri: electrum2.hodlister.co:50002
-
uri: bitcoin.electrumx.multicoin.co:50002

Binary file not shown.

After

(image error) Size: 337 B

Binary file not shown.

After

(image error) Size: 1.2 KiB

Binary file not shown.

After

(image error) Size: 459 B

Binary file not shown.

After

(image error) Size: 1.6 KiB

Binary file not shown.

After

(image error) Size: 251 B

Binary file not shown.

After

(image error) Size: 713 B

View file

@ -0,0 +1 @@
a2dce69f54a78f5b00e19850e4b2d402

View file

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

View file

@ -319,7 +319,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;
@ -373,7 +372,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 = (
@ -387,7 +386,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";
@ -399,7 +398,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;
@ -455,7 +453,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;
@ -509,7 +506,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 = (
@ -523,7 +520,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";
@ -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";

View file

@ -19,9 +19,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>Cake Wallet requires access to your phones camera.</string>
<key>UILaunchStoryboardName</key> <key>UILaunchStoryboardName</key>
<string>LaunchScreen</string> <string>LaunchScreen</string>
<key>UIMainStoryboardFile</key> <key>UIMainStoryboardFile</key>

View file

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

View file

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

View file

@ -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 {
final content = await read(path: path, password: _password);
return json.decode(content) as Map<String, Object>;
}
Future<void> _load() async {
try { try {
final content = await read(path: path, password: _password); final content = await _read();
final jsoned = json.decode(content) as Map<String, Object>; final txs = content['transactions'] 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; txs.entries.forEach((entry) {
}) final val = entry.value;
.where((el) => el != null)
.toList();
return {'transactions': transactions, 'height': height}; if (val is Map<String, Object>) {
} catch (_) { final tx = BitcoinTransactionInfo.fromJson(val);
return {'transactions': <BitcoinTransactionInfo>[], 'height': 0}; _updateOrInsert(tx);
}
});
_height = content['height'] as int;
} catch (_) {}
}
void _updateOrInsert(BitcoinTransactionInfo transaction) {
if (transactions[transaction.id] == null) {
transactions[transaction.id] = transaction;
} 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;
} }
} }
void _updateHeight() {
final newHeight = transactions.fold(
0, (int acc, val) => val.height > acc ? val.height : acc);
_height = newHeight > _height ? newHeight : _height;
}
} }

View file

@ -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,42 +15,97 @@ 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;
tx.outs.forEach((out) { if (addresses != null) {
try { tx.outs.forEach((out) {
final p2pkh = bitcoin.P2PKH( try {
data: PaymentData(output: out.script), network: bitcoin.bitcoin); final p2pkh = bitcoin.P2PKH(
exist = addresses.contains(p2pkh.data.address); data: PaymentData(output: out.script), network: bitcoin.bitcoin);
exist = addresses.contains(p2pkh.data.address);
if (exist) { if (exist) {
amount += out.value; amount += out.value;
} }
} 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;
} }
} }

View file

@ -0,0 +1,4 @@
class BitcoinTransactionNoInputsException implements Exception {
@override
String toString() => 'No inputs for the transaction.';
}

View file

@ -0,0 +1,4 @@
class BitcoinTransactionWrongBalanceException implements Exception {
@override
String toString() => 'Wrong balance. Not enough BTC on your balance.';
}

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

View file

@ -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,39 +219,103 @@ 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;
final unspent = addresses.map((address) => eclient
.getListUnspentWithAddress(address.address)
.then((unspent) => unspent
.map((unspent) => BitcoinUnspent.fromJSON(address, unspent))));
for (final unptsFutures in unspent) {
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;
}
}
if (leftAmount <= 0) {
break;
}
}
if (inputs.isEmpty) {
throw BitcoinTransactionNoInputsException();
}
if (amount <= 0 || totalInputAmount < amount) {
throw BitcoinTransactionWrongBalanceException();
}
final changeValue = totalInputAmount - amount - fee;
txb.setVersion(1); txb.setVersion(1);
txb.addInput(prevTx, 0);
txb.addOutput(transactionCredentials.address,
doubleToBitcoinAmount(transactionCredentials.amount));
txb.sign(vin: 0, keyPair: keyPair);
final encoded = txb.build().toHex();
print('Enoded transaction $encoded'); inputs.forEach((input) {
await eclient.broadcastTransaction(transactionRaw: encoded); 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));
} }
@override
Future<void> save() async =>
await write(path: path, password: _password, data: toJSON());
String toJSON() => json.encode({ String toJSON() => json.encode({
'mnemonic': mnemonic, 'mnemonic': mnemonic,
'account_index': _accountIndex.toString(), 'account_index': _accountIndex.toString(),
@ -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;
}
}
} }

View file

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

View file

@ -1,17 +1,19 @@
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';
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 +33,66 @@ 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 _uri = Uri.parse(uri);
await socket.close(); final host = _uri.scheme;
} final port = int.parse(_uri.path);
await connect(host: host, port: port);
}
final start = DateTime.now(); Future<void> connect({@required String host, @required int port}) async {
try {
await socket?.close();
} catch (_) {}
socket = await SecureSocket.connect(host, port, timeout: connectionTimeout); socket = await SecureSocket.connect(host, port, timeout: connectionTimeout);
_setIsConnected(true);
_isConnected = true; socket.listen((Uint8List event) {
socket.listen((List<int> event) {
try { try {
final jsoned = json.decode(utf8.decode(event)) as Map<String, Object>; 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 +103,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 +122,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 +240,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 +267,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 +302,9 @@ class ElectrumClient {
return; return;
} }
_tasks[id]?.completer?.complete(data); if (!(_tasks[id]?.completer?.isCompleted ?? false)) {
_tasks[id]?.completer?.complete(data);
}
if (!(_tasks[id]?.isSubscription ?? false)) { if (!(_tasks[id]?.isSubscription ?? false)) {
_tasks[id] = null; _tasks[id] = null;
@ -218,18 +316,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;
} }

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

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

View file

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

View file

@ -0,0 +1,6 @@
mixin PendingTransaction {
String get amountFormatted;
String get feeFormatted;
Future<void> commit();
}

View file

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

View file

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

View file

@ -4,6 +4,8 @@ import 'package:cake_wallet/src/domain/common/node.dart';
import 'package:cake_wallet/src/domain/exchange/trade.dart'; import 'package:cake_wallet/src/domain/exchange/trade.dart';
import 'package:cake_wallet/src/screens/contact/contact_list_page.dart'; import 'package:cake_wallet/src/screens/contact/contact_list_page.dart';
import 'package:cake_wallet/src/screens/contact/contact_page.dart'; import 'package:cake_wallet/src/screens/contact/contact_page.dart';
import 'package:cake_wallet/src/screens/exchange_trade/exchange_confirm_page.dart';
import 'package:cake_wallet/src/screens/exchange_trade/exchange_trade_page.dart';
import 'package:cake_wallet/src/screens/nodes/node_create_or_edit_page.dart'; import 'package:cake_wallet/src/screens/nodes/node_create_or_edit_page.dart';
import 'package:cake_wallet/src/screens/nodes/nodes_list_page.dart'; import 'package:cake_wallet/src/screens/nodes/nodes_list_page.dart';
import 'package:cake_wallet/src/screens/seed/wallet_seed_page.dart'; import 'package:cake_wallet/src/screens/seed/wallet_seed_page.dart';
@ -27,9 +29,13 @@ import 'package:cake_wallet/src/screens/receive/receive_page.dart';
import 'package:cake_wallet/src/screens/send/send_page.dart'; import 'package:cake_wallet/src/screens/send/send_page.dart';
import 'package:cake_wallet/src/screens/subaddress/address_edit_or_create_page.dart'; import 'package:cake_wallet/src/screens/subaddress/address_edit_or_create_page.dart';
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/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/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';
import 'package:cake_wallet/view_model/exchange/exchange_trade_view_model.dart';
import 'package:cake_wallet/view_model/node_list/node_list_view_model.dart'; import 'package:cake_wallet/view_model/node_list/node_list_view_model.dart';
import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart'; import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_edit_or_create_view_model.dart'; import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_edit_or_create_view_model.dart';
@ -39,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';
@ -81,8 +87,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;
} }
@ -114,10 +132,8 @@ 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(wallet: getIt.get<AppStore>().wallet));
getIt.registerSingleton<TransactionFilterStore>(TransactionFilterStore()); getIt.registerSingleton<TransactionFilterStore>(TransactionFilterStore());
getIt.registerSingleton<FiatConvertationStore>(FiatConvertationStore()); getIt.registerSingleton<FiatConvertationStore>(FiatConvertationStore());
getIt.registerSingleton<SendTemplateStore>( getIt.registerSingleton<SendTemplateStore>(
@ -155,20 +171,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>(),
@ -196,10 +209,9 @@ Future setup(
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>()));
getIt.registerFactory<ReceivePage>(() => ReceivePage( getIt.registerFactory<ReceivePage>(() => ReceivePage(
addressListViewModel: getIt.get<WalletAddressListViewModel>())); addressListViewModel: getIt.get<WalletAddressListViewModel>()));
@ -213,17 +225,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>()));
@ -260,8 +272,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>()));
@ -292,10 +306,11 @@ Future setup(
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>()));
@ -305,16 +320,30 @@ 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>()));
));
getIt.registerFactory(() => getIt.registerFactory(() => ExchangeTradeViewModel(
ExchangePage(getIt.get<ExchangeViewModel>())); wallet: getIt.get<AppStore>().wallet,
trades: tradesSource,
tradesStore: getIt.get<TradesStore>()));
getIt.registerFactory(() => getIt.registerFactory(() => ExchangePage(getIt.get<ExchangeViewModel>()));
ExchangeTemplatePage(getIt.get<ExchangeViewModel>()));
getIt.registerFactory(
() => ExchangeConfirmPage(tradesStore: getIt.get<TradesStore>()));
getIt.registerFactory(() => ExchangeTradePage(
exchangeTradeViewModel: getIt.get<ExchangeTradeViewModel>()));
getIt.registerFactory(
() => ExchangeTemplatePage(getIt.get<ExchangeViewModel>()));
}
void setupThemeChangerStore(ThemeChanger themeChanger) {
getIt.registerSingleton<ThemeChangerStore>(
ThemeChangerStore(themeChanger: themeChanger));
} }

View file

@ -96,7 +96,7 @@ class S implements WidgetsLocalizations {
String get expired => "Expired"; String get expired => "Expired";
String get faq => "FAQ"; String get faq => "FAQ";
String get fetching => "Fetching"; String get fetching => "Fetching";
String get filters => "Filters"; String get filters => "Filter";
String get first_wallet_text => "Awesome wallet for Monero"; String get first_wallet_text => "Awesome wallet for Monero";
String get full_balance => "Full Balance"; String get full_balance => "Full Balance";
String get hidden_balance => "Hidden Balance"; String get hidden_balance => "Hidden Balance";
@ -312,8 +312,8 @@ class S implements WidgetsLocalizations {
String error_text_limits_loading_failed(String provider) => "Trade for ${provider} is not created. Limits loading failed"; String error_text_limits_loading_failed(String provider) => "Trade for ${provider} is not created. Limits loading failed";
String error_text_maximum_limit(String provider, String max, String currency) => "Trade for ${provider} is not created. Amount is more then maximum: ${max} ${currency}"; String error_text_maximum_limit(String provider, String max, String currency) => "Trade for ${provider} is not created. Amount is more then maximum: ${max} ${currency}";
String error_text_minimal_limit(String provider, String min, String currency) => "Trade for ${provider} is not created. Amount is less then minimal: ${min} ${currency}"; String error_text_minimal_limit(String provider, String min, String currency) => "Trade for ${provider} is not created. Amount is less then minimal: ${min} ${currency}";
String exchange_result_confirm(String fetchingLabel, String from, String walletName) => "By pressing confirm, you will be sending ${fetchingLabel} ${from} from your wallet called ${walletName} to the address shown above. Or you can send from your external wallet to the above address/QR code.\n\nPlease press confirm to continue or go back to change the amounts.\n\n"; String exchange_result_confirm(String fetchingLabel, String from, String walletName) => "By pressing confirm, you will be sending ${fetchingLabel} ${from} from your wallet called ${walletName} to the address shown above. Or you can send from your external wallet to the above address/QR code.\n\nPlease press confirm to continue or go back to change the amounts.";
String exchange_result_description(String fetchingLabel, String from) => "Please send ${fetchingLabel} ${from} to the address shown above.\n\n"; String exchange_result_description(String fetchingLabel, String from) => "Please send ${fetchingLabel} ${from} to the address shown above.";
String failed_authentication(String state_error) => "Failed authentication. ${state_error}"; String failed_authentication(String state_error) => "Failed authentication. ${state_error}";
String max_value(String value, String currency) => "Max: ${value} ${currency}"; String max_value(String value, String currency) => "Max: ${value} ${currency}";
String min_value(String value, String currency) => "Min: ${value} ${currency}"; String min_value(String value, String currency) => "Min: ${value} ${currency}";
@ -948,11 +948,11 @@ class $de extends S {
@override @override
String Blocks_remaining(String status) => "${status} Verbleibende Blöcke"; String Blocks_remaining(String status) => "${status} Verbleibende Blöcke";
@override @override
String exchange_result_confirm(String fetchingLabel, String from, String walletName) => "Durch Drücken von Bestätigen wird gesendet ${fetchingLabel} ${from} von Ihrer Brieftasche aus angerufen ${walletName} an die oben angegebene Adresse. Oder Sie können von Ihrem externen Portemonnaie an die oben angegebene Adresse / QR-Code senden.\n\nBitte bestätigen Sie, um fortzufahren, oder gehen Sie zurück, um die Beträge zu änderns.\n\n"; String exchange_result_confirm(String fetchingLabel, String from, String walletName) => "Durch Drücken von Bestätigen wird gesendet ${fetchingLabel} ${from} von Ihrer Brieftasche aus angerufen ${walletName} an die oben angegebene Adresse. Oder Sie können von Ihrem externen Portemonnaie an die oben angegebene Adresse / QR-Code senden.\n\nBitte bestätigen Sie, um fortzufahren, oder gehen Sie zurück, um die Beträge zu änderns.";
@override @override
String error_text_limits_loading_failed(String provider) => "Handel für ${provider} wird nicht erstellt. Das Laden der Limits ist fehlgeschlagen"; String error_text_limits_loading_failed(String provider) => "Handel für ${provider} wird nicht erstellt. Das Laden der Limits ist fehlgeschlagen";
@override @override
String exchange_result_description(String fetchingLabel, String from) => "Bitte senden ${fetchingLabel} ${from} an die oben angegebene Adresse.\n\n'"; String exchange_result_description(String fetchingLabel, String from) => "Bitte senden ${fetchingLabel} ${from} an die oben angegebene Adresse.'";
@override @override
String commit_transaction_amount_fee(String amount, String fee) => "Transaktion festschreiben\nMenge: ${amount}\nGebühr: ${fee}"; String commit_transaction_amount_fee(String amount, String fee) => "Transaktion festschreiben\nMenge: ${amount}\nGebühr: ${fee}";
@override @override
@ -1216,7 +1216,7 @@ class $hi extends S {
@override @override
String get estimated => "अनुमानित"; String get estimated => "अनुमानित";
@override @override
String get filters => "िल्टर"; String get filters => "िल्टर";
@override @override
String get settings_current_node => "वर्तमान नोड"; String get settings_current_node => "वर्तमान नोड";
@override @override
@ -1580,11 +1580,11 @@ class $hi extends S {
@override @override
String Blocks_remaining(String status) => "${status} शेष रहते हैं"; String Blocks_remaining(String status) => "${status} शेष रहते हैं";
@override @override
String exchange_result_confirm(String fetchingLabel, String from, String walletName) => "पुष्टि दबाकर, आप भेज रहे होंगे ${fetchingLabel} ${from} अपने बटुए से ${walletName} ऊपर दिखाए गए पते पर। या आप अपने बाहरी वॉलेट से उपरोक्त पते / क्यूआर कोड पर भेज सकते हैं।\n\nकृपया जारी रखने या राशि बदलने के लिए वापस जाने के लिए पुष्टि करें दबाएं.\n\n"; String exchange_result_confirm(String fetchingLabel, String from, String walletName) => "पुष्टि दबाकर, आप भेज रहे होंगे ${fetchingLabel} ${from} अपने बटुए से ${walletName} ऊपर दिखाए गए पते पर। या आप अपने बाहरी वॉलेट से उपरोक्त पते / क्यूआर कोड पर भेज सकते हैं।\n\nकृपया जारी रखने या राशि बदलने के लिए वापस जाने के लिए पुष्टि करें दबाएं.";
@override @override
String error_text_limits_loading_failed(String provider) => "व्यापार ${provider} के लिए नहीं बनाया गया है। लोडिंग की सीमाएं विफल रहीं"; String error_text_limits_loading_failed(String provider) => "व्यापार ${provider} के लिए नहीं बनाया गया है। लोडिंग की सीमाएं विफल रहीं";
@override @override
String exchange_result_description(String fetchingLabel, String from) => "कृपया भेजें ${fetchingLabel} ${from} ऊपर दिखाए गए पते पर\n\n'"; String exchange_result_description(String fetchingLabel, String from) => "कृपया भेजें ${fetchingLabel} ${from} ऊपर दिखाए गए पते पर";
@override @override
String commit_transaction_amount_fee(String amount, String fee) => "लेन-देन करें\nरकम: ${amount}\nशुल्क: ${fee}"; String commit_transaction_amount_fee(String amount, String fee) => "लेन-देन करें\nरकम: ${amount}\nशुल्क: ${fee}";
@override @override
@ -1848,7 +1848,7 @@ class $ru extends S {
@override @override
String get estimated => "Примерно"; String get estimated => "Примерно";
@override @override
String get filters => "Фильтры"; String get filters => "Фильтр";
@override @override
String get settings_current_node => "Текущая нода"; String get settings_current_node => "Текущая нода";
@override @override
@ -2212,11 +2212,11 @@ class $ru extends S {
@override @override
String Blocks_remaining(String status) => "${status} Осталось блоков"; String Blocks_remaining(String status) => "${status} Осталось блоков";
@override @override
String exchange_result_confirm(String fetchingLabel, String from, String walletName) => "Нажимая подтвердить, вы отправите ${fetchingLabel} ${from} с вашего кошелька ${walletName} на адрес указанный выше. Или вы можете отправить со своего внешнего кошелька на вышеуказанный адрес/QR-код.\n\nПожалуйста, нажмите подтвердить для продолжения, или вернитесь назад для изменения суммы.\n\n"; String exchange_result_confirm(String fetchingLabel, String from, String walletName) => "Нажимая подтвердить, вы отправите ${fetchingLabel} ${from} с вашего кошелька ${walletName} на адрес указанный выше. Или вы можете отправить со своего внешнего кошелька на вышеуказанный адрес/QR-код.\n\nПожалуйста, нажмите подтвердить для продолжения, или вернитесь назад для изменения суммы.";
@override @override
String error_text_limits_loading_failed(String provider) => "Сделка для ${provider} не создана. Ошибка загрузки лимитов"; String error_text_limits_loading_failed(String provider) => "Сделка для ${provider} не создана. Ошибка загрузки лимитов";
@override @override
String exchange_result_description(String fetchingLabel, String from) => "Пожалуйста отправьте ${fetchingLabel} ${from} на адрес, указанный выше.\n\n'"; String exchange_result_description(String fetchingLabel, String from) => "Пожалуйста отправьте ${fetchingLabel} ${from} на адрес, указанный выше.";
@override @override
String commit_transaction_amount_fee(String amount, String fee) => "Подтвердить транзакцию \nСумма: ${amount}\nКомиссия: ${fee}"; String commit_transaction_amount_fee(String amount, String fee) => "Подтвердить транзакцию \nСумма: ${amount}\nКомиссия: ${fee}";
@override @override
@ -2844,11 +2844,11 @@ class $ko extends S {
@override @override
String Blocks_remaining(String status) => "${status} 남은 블록"; String Blocks_remaining(String status) => "${status} 남은 블록";
@override @override
String exchange_result_confirm(String fetchingLabel, String from, String walletName) => "확인을 누르면 전송됩니다 ${fetchingLabel} ${from} 지갑에서 ${walletName} 위에 표시된 주소로. 또는 외부 지갑에서 위의 주소 / QR 코드로 보낼 수 있습니다.\n\n확인을 눌러 계속하거나 금액을 변경하려면 돌아가십시오.\n\n"; String exchange_result_confirm(String fetchingLabel, String from, String walletName) => "확인을 누르면 전송됩니다 ${fetchingLabel} ${from} 지갑에서 ${walletName} 위에 표시된 주소로. 또는 외부 지갑에서 위의 주소 / QR 코드로 보낼 수 있습니다.\n\n확인을 눌러 계속하거나 금액을 변경하려면 돌아가십시오.";
@override @override
String error_text_limits_loading_failed(String provider) => "거래 ${provider} 가 생성되지 않습니다. 로딩 실패"; String error_text_limits_loading_failed(String provider) => "거래 ${provider} 가 생성되지 않습니다. 로딩 실패";
@override @override
String exchange_result_description(String fetchingLabel, String from) => "보내주세요 ${fetchingLabel} ${from} 위에 표시된 주소로.\n\n'"; String exchange_result_description(String fetchingLabel, String from) => "보내주세요 ${fetchingLabel} ${from} 위에 표시된 주소로.";
@override @override
String commit_transaction_amount_fee(String amount, String fee) => "커밋 거래\n양: ${amount}\n보수: ${fee}"; String commit_transaction_amount_fee(String amount, String fee) => "커밋 거래\n양: ${amount}\n보수: ${fee}";
@override @override
@ -3112,7 +3112,7 @@ class $pt extends S {
@override @override
String get estimated => "Estimado"; String get estimated => "Estimado";
@override @override
String get filters => "Filtros"; String get filters => "Filtro";
@override @override
String get settings_current_node => "Nó atual"; String get settings_current_node => "Nó atual";
@override @override
@ -3476,11 +3476,11 @@ class $pt extends S {
@override @override
String Blocks_remaining(String status) => "${status} blocos restantes"; String Blocks_remaining(String status) => "${status} blocos restantes";
@override @override
String exchange_result_confirm(String fetchingLabel, String from, String walletName) => "Ao confirmar, você enviará ${fetchingLabel} ${from} da sua carteira ${walletName} para o endereço exibido acima. Você também pode enviar com uma carteira externa para o endereço/código QR acima.\n\nPressione Confirmar para continuar ou volte para alterar os valores.\n\n"; String exchange_result_confirm(String fetchingLabel, String from, String walletName) => "Ao confirmar, você enviará ${fetchingLabel} ${from} da sua carteira ${walletName} para o endereço exibido acima. Você também pode enviar com uma carteira externa para o endereço/código QR acima.\n\nPressione Confirmar para continuar ou volte para alterar os valores.";
@override @override
String error_text_limits_loading_failed(String provider) => "A troca por ${provider} não é criada. Falha no carregamento dos limites"; String error_text_limits_loading_failed(String provider) => "A troca por ${provider} não é criada. Falha no carregamento dos limites";
@override @override
String exchange_result_description(String fetchingLabel, String from) => "Por favor, envie ${fetchingLabel} ${from} para o endereço mostrado acima.\n\n'"; String exchange_result_description(String fetchingLabel, String from) => "Por favor, envie ${fetchingLabel} ${from} para o endereço mostrado acima.";
@override @override
String commit_transaction_amount_fee(String amount, String fee) => "Confirmar transação\nQuantia: ${amount}\nTaxa: ${fee}"; String commit_transaction_amount_fee(String amount, String fee) => "Confirmar transação\nQuantia: ${amount}\nTaxa: ${fee}";
@override @override
@ -3744,7 +3744,7 @@ class $uk extends S {
@override @override
String get estimated => "Приблизно "; String get estimated => "Приблизно ";
@override @override
String get filters => "Фільтри"; String get filters => "Фільтр";
@override @override
String get settings_current_node => "Поточний вузол"; String get settings_current_node => "Поточний вузол";
@override @override
@ -4108,11 +4108,11 @@ class $uk extends S {
@override @override
String Blocks_remaining(String status) => "${status} Залишилось блоків"; String Blocks_remaining(String status) => "${status} Залишилось блоків";
@override @override
String exchange_result_confirm(String fetchingLabel, String from, String walletName) => "Натиснувши підтвердити, ви відправите ${fetchingLabel} ${from} з вашого гаманця ${walletName} на адресу вказану вище. Або ви можете відправити зі свого зовнішнього гаманця на вищевказану адресу/QR-код.\n\nБудь ласка, натисніть підтвердити для продовження або поверніться назад щоб змінити суму.\n\n"; String exchange_result_confirm(String fetchingLabel, String from, String walletName) => "Натиснувши підтвердити, ви відправите ${fetchingLabel} ${from} з вашого гаманця ${walletName} на адресу вказану вище. Або ви можете відправити зі свого зовнішнього гаманця на вищевказану адресу/QR-код.\n\nБудь ласка, натисніть підтвердити для продовження або поверніться назад щоб змінити суму.";
@override @override
String error_text_limits_loading_failed(String provider) => "Операція для ${provider} не створена. Помилка завантаження лімітів"; String error_text_limits_loading_failed(String provider) => "Операція для ${provider} не створена. Помилка завантаження лімітів";
@override @override
String exchange_result_description(String fetchingLabel, String from) => "Будь ласка, відправте ${fetchingLabel} ${from} на адресу, вказану вище.\n\n'"; String exchange_result_description(String fetchingLabel, String from) => "Будь ласка, відправте ${fetchingLabel} ${from} на адресу, вказану вище.";
@override @override
String commit_transaction_amount_fee(String amount, String fee) => "Підтвердити транзакцію \nСума: ${amount}\nКомісія: ${fee}"; String commit_transaction_amount_fee(String amount, String fee) => "Підтвердити транзакцію \nСума: ${amount}\nКомісія: ${fee}";
@override @override
@ -4376,7 +4376,7 @@ class $ja extends S {
@override @override
String get estimated => "推定"; String get estimated => "推定";
@override @override
String get filters => "フィルタ"; String get filters => "フィルタ";
@override @override
String get settings_current_node => "現在のノード"; String get settings_current_node => "現在のノード";
@override @override
@ -4740,11 +4740,11 @@ class $ja extends S {
@override @override
String Blocks_remaining(String status) => "${status} 残りのブロック"; String Blocks_remaining(String status) => "${status} 残りのブロック";
@override @override
String exchange_result_confirm(String fetchingLabel, String from, String walletName) => "確認を押すと、送信されます ${fetchingLabel} ${from} と呼ばれるあなたの財布から ${walletName} 上記のアドレスへ. または、外部ウォレットから上記のアドレス/ QRコードに送信できます.\n\n確認を押して続行するか、戻って金額を変更してください.\n\n"; String exchange_result_confirm(String fetchingLabel, String from, String walletName) => "確認を押すと、送信されます ${fetchingLabel} ${from} と呼ばれるあなたの財布から ${walletName} 上記のアドレスへ. または、外部ウォレットから上記のアドレス/ QRコードに送信できます.\n\n確認を押して続行するか、戻って金額を変更してください.";
@override @override
String error_text_limits_loading_failed(String provider) => "${provider} の取引は作成されません。 制限の読み込みに失敗しました"; String error_text_limits_loading_failed(String provider) => "${provider} の取引は作成されません。 制限の読み込みに失敗しました";
@override @override
String exchange_result_description(String fetchingLabel, String from) => "送信してください ${fetchingLabel} ${from} 上記のアドレスへ.\n\n'"; String exchange_result_description(String fetchingLabel, String from) => "送信してください ${fetchingLabel} ${from} 上記のアドレスへ.";
@override @override
String commit_transaction_amount_fee(String amount, String fee) => "トランザクションをコミット\n量: ${amount}\n費用: ${fee}"; String commit_transaction_amount_fee(String amount, String fee) => "トランザクションをコミット\n量: ${amount}\n費用: ${fee}";
@override @override
@ -5012,7 +5012,7 @@ class $pl extends S {
@override @override
String get estimated => "Oszacowano"; String get estimated => "Oszacowano";
@override @override
String get filters => "Filtry"; String get filters => "Filtr";
@override @override
String get settings_current_node => "Bieżący węzeł"; String get settings_current_node => "Bieżący węzeł";
@override @override
@ -5376,11 +5376,11 @@ class $pl extends S {
@override @override
String Blocks_remaining(String status) => "${status} Bloki pozostałe"; String Blocks_remaining(String status) => "${status} Bloki pozostałe";
@override @override
String exchange_result_confirm(String fetchingLabel, String from, String walletName) => "Naciskając Potwierdź, wyślesz ${fetchingLabel} ${from} z twojego portfela ${walletName} z twojego portfela. Lub możesz wysłać z zewnętrznego portfela na powyższy adres / kod QR.\n\nNaciśnij Potwierdź, aby kontynuować lub wróć, aby zmienić kwoty.\n\n"; String exchange_result_confirm(String fetchingLabel, String from, String walletName) => "Naciskając Potwierdź, wyślesz ${fetchingLabel} ${from} z twojego portfela ${walletName} z twojego portfela. Lub możesz wysłać z zewnętrznego portfela na powyższy adres / kod QR.\n\nNaciśnij Potwierdź, aby kontynuować lub wróć, aby zmienić kwoty.";
@override @override
String error_text_limits_loading_failed(String provider) => "Wymiana dla ${provider} nie została utworzona. Ładowanie limitów nie powiodło się"; String error_text_limits_loading_failed(String provider) => "Wymiana dla ${provider} nie została utworzona. Ładowanie limitów nie powiodło się";
@override @override
String exchange_result_description(String fetchingLabel, String from) => "Proszę wyślij ${fetchingLabel} ${from} na adres podany powyżej.\n\n'"; String exchange_result_description(String fetchingLabel, String from) => "Proszę wyślij ${fetchingLabel} ${from} na adres podany powyżej.";
@override @override
String commit_transaction_amount_fee(String amount, String fee) => "Zatwierdź transakcję\nIlość: ${amount}\nOpłata: ${fee}"; String commit_transaction_amount_fee(String amount, String fee) => "Zatwierdź transakcję\nIlość: ${amount}\nOpłata: ${fee}";
@override @override
@ -5644,7 +5644,7 @@ class $es extends S {
@override @override
String get estimated => "Estimado"; String get estimated => "Estimado";
@override @override
String get filters => "Filtros"; String get filters => "Filtrar";
@override @override
String get settings_current_node => "Nodo actual"; String get settings_current_node => "Nodo actual";
@override @override
@ -6008,11 +6008,11 @@ class $es extends S {
@override @override
String Blocks_remaining(String status) => "${status} Bloques restantes"; String Blocks_remaining(String status) => "${status} Bloques restantes";
@override @override
String exchange_result_confirm(String fetchingLabel, String from, String walletName) => "Al presionar confirmar, enviará ${fetchingLabel} ${from} desde su billetera llamada ${walletName} a la dirección que se muestra arriba. O puede enviar desde su billetera externa a la dirección / código QR anterior.\n\nPresione confirmar para continuar o regrese para cambiar los montos.\n\n"; String exchange_result_confirm(String fetchingLabel, String from, String walletName) => "Al presionar confirmar, enviará ${fetchingLabel} ${from} desde su billetera llamada ${walletName} a la dirección que se muestra arriba. O puede enviar desde su billetera externa a la dirección / código QR anterior.\n\nPresione confirmar para continuar o regrese para cambiar los montos.";
@override @override
String error_text_limits_loading_failed(String provider) => "El comercio por ${provider} no se crea. Límites de carga fallidos"; String error_text_limits_loading_failed(String provider) => "El comercio por ${provider} no se crea. Límites de carga fallidos";
@override @override
String exchange_result_description(String fetchingLabel, String from) => "Envíe ${fetchingLabel} ${from} a la dirección que se muestra arriba.\n\n'"; String exchange_result_description(String fetchingLabel, String from) => "Envíe ${fetchingLabel} ${from} a la dirección que se muestra arriba.";
@override @override
String commit_transaction_amount_fee(String amount, String fee) => "Confirmar transacción\nCantidad: ${amount}\nCuota: ${fee}"; String commit_transaction_amount_fee(String amount, String fee) => "Confirmar transacción\nCantidad: ${amount}\nCuota: ${fee}";
@override @override
@ -6276,7 +6276,7 @@ class $nl extends S {
@override @override
String get estimated => "Geschatte"; String get estimated => "Geschatte";
@override @override
String get filters => "Filters"; String get filters => "Filter";
@override @override
String get settings_current_node => "Huidige knooppunt"; String get settings_current_node => "Huidige knooppunt";
@override @override
@ -6640,11 +6640,11 @@ class $nl extends S {
@override @override
String Blocks_remaining(String status) => "${status} Resterende blokken"; String Blocks_remaining(String status) => "${status} Resterende blokken";
@override @override
String exchange_result_confirm(String fetchingLabel, String from, String walletName) => "Door op bevestigen te drukken, wordt u verzonden ${fetchingLabel} ${from} uit je portemonnee genoemd ${walletName} naar bovenstaand adres. Of u kunt uw externe portemonnee naar bovenstaand adres / QR-code sturen.\n\nDruk op bevestigen om door te gaan of terug te gaan om de bedragen te wijzigen.\n\n"; String exchange_result_confirm(String fetchingLabel, String from, String walletName) => "Door op bevestigen te drukken, wordt u verzonden ${fetchingLabel} ${from} uit je portemonnee genoemd ${walletName} naar bovenstaand adres. Of u kunt uw externe portemonnee naar bovenstaand adres / QR-code sturen.\n\nDruk op bevestigen om door te gaan of terug te gaan om de bedragen te wijzigen.";
@override @override
String error_text_limits_loading_failed(String provider) => "Ruil voor ${provider} is niet gemaakt. Beperkingen laden mislukt"; String error_text_limits_loading_failed(String provider) => "Ruil voor ${provider} is niet gemaakt. Beperkingen laden mislukt";
@override @override
String exchange_result_description(String fetchingLabel, String from) => "Zend alstublieft ${fetchingLabel} ${from} naar bovenstaand adres.\n\n'"; String exchange_result_description(String fetchingLabel, String from) => "Zend alstublieft ${fetchingLabel} ${from} naar bovenstaand adres.";
@override @override
String commit_transaction_amount_fee(String amount, String fee) => "Verricht transactie\nBedrag: ${amount}\nhonorarium: ${fee}"; String commit_transaction_amount_fee(String amount, String fee) => "Verricht transactie\nBedrag: ${amount}\nhonorarium: ${fee}";
@override @override
@ -6908,7 +6908,7 @@ class $zh extends S {
@override @override
String get estimated => "估计的"; String get estimated => "估计的";
@override @override
String get filters => "筛选器"; String get filters => "過濾";
@override @override
String get settings_current_node => "当前节点"; String get settings_current_node => "当前节点";
@override @override
@ -7272,11 +7272,11 @@ class $zh extends S {
@override @override
String Blocks_remaining(String status) => "${status} 剩余的块"; String Blocks_remaining(String status) => "${status} 剩余的块";
@override @override
String exchange_result_confirm(String fetchingLabel, String from, String walletName) => "点击确认 您将发送 ${fetchingLabel} ${from} 从你的钱包里 ${walletName} 到上面显示的地址. 或者,您也可以从外部钱包发送上述地址/ QR码。\n\n请按确认继续或返回以更改金额\n\n"; String exchange_result_confirm(String fetchingLabel, String from, String walletName) => "点击确认 您将发送 ${fetchingLabel} ${from} 从你的钱包里 ${walletName} 到上面显示的地址. 或者,您也可以从外部钱包发送上述地址/ QR码。\n\n请按确认继续或返回以更改金额";
@override @override
String error_text_limits_loading_failed(String provider) => "未創建 ${provider} 交易。 限制加載失敗"; String error_text_limits_loading_failed(String provider) => "未創建 ${provider} 交易。 限制加載失敗";
@override @override
String exchange_result_description(String fetchingLabel, String from) => "请发送 ${fetchingLabel} ${from} 到上面显示的地址.\n\n'"; String exchange_result_description(String fetchingLabel, String from) => "请发送 ${fetchingLabel} ${from} 到上面显示的地址.";
@override @override
String commit_transaction_amount_fee(String amount, String fee) => "提交交易\n量: ${amount}\nFee: ${fee}"; String commit_transaction_amount_fee(String amount, String fee) => "提交交易\n量: ${amount}\nFee: ${fee}";
@override @override

View file

@ -1,4 +1,5 @@
import 'package:cake_wallet/reactions/bootstrap.dart'; import 'package:cake_wallet/reactions/bootstrap.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/store/authentication_store.dart'; import 'package:cake_wallet/store/authentication_store.dart';
import 'package:cake_wallet/core/auth_service.dart'; import 'package:cake_wallet/core/auth_service.dart';
import 'package:cake_wallet/bitcoin/bitcoin_wallet_service.dart'; import 'package:cake_wallet/bitcoin/bitcoin_wallet_service.dart';
@ -52,6 +53,8 @@ import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/domain/common/language.dart'; import 'package:cake_wallet/src/domain/common/language.dart';
import 'package:cake_wallet/src/stores/seed_language/seed_language_store.dart'; import 'package:cake_wallet/src/stores/seed_language/seed_language_store.dart';
bool isThemeChangerRegistered = false;
void main() async { void main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
@ -129,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,
@ -169,11 +172,11 @@ Future<void> initialSetup(
@required Box<Node> nodes, @required Box<Node> nodes,
@required Box<WalletInfo> walletInfoSource, @required Box<WalletInfo> walletInfoSource,
@required Box<Contact> contactSource, @required Box<Contact> contactSource,
@required Box<Trade> tradesSource, @required Box<Trade> tradesSource,
@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,
@ -197,7 +200,8 @@ class CakeWalletApp extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final settingsStore = Provider.of<SettingsStore>(context); //final settingsStore = Provider.of<SettingsStore>(context);
final settingsStore = getIt.get<AppStore>().settingsStore;
return ChangeNotifierProvider<ThemeChanger>( return ChangeNotifierProvider<ThemeChanger>(
create: (_) => ThemeChanger( create: (_) => ThemeChanger(
@ -228,12 +232,20 @@ class MaterialAppWithTheme extends StatelessWidget {
final transactionDescriptions = final transactionDescriptions =
Provider.of<Box<TransactionDescription>>(context); Provider.of<Box<TransactionDescription>>(context);
final statusBarColor = if (!isThemeChangerRegistered) {
settingsStore.isDarkTheme ? Colors.black : Colors.white; setupThemeChangerStore(theme);
isThemeChangerRegistered = true;
}
/*final statusBarColor =
settingsStore.isDarkTheme ? Colors.black : Colors.white;*/
final _settingsStore = getIt.get<AppStore>().settingsStore;
final statusBarColor = Colors.transparent;
final statusBarBrightness = final statusBarBrightness =
settingsStore.isDarkTheme ? Brightness.light : Brightness.dark; _settingsStore.isDarkTheme ? Brightness.light : Brightness.dark;
final statusBarIconBrightness = final statusBarIconBrightness =
settingsStore.isDarkTheme ? Brightness.light : Brightness.dark; _settingsStore.isDarkTheme ? Brightness.light : Brightness.dark;
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle( SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
statusBarColor: statusBarColor, statusBarColor: statusBarColor,
@ -270,4 +282,4 @@ class MaterialAppWithTheme extends StatelessWidget {
authenticationStore: getIt.get<AuthenticationStore>(), authenticationStore: getIt.get<AuthenticationStore>(),
)); ));
} }
} }

View file

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

View file

@ -16,6 +16,9 @@ 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,7 +136,7 @@ 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,
@ -146,6 +149,33 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance> with Store {
// 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
Future<void> save() async { Future<void> save() async {
// if (_isSaving) { // if (_isSaving) {

View file

@ -9,11 +9,20 @@ class Palette {
static const Color lavender = Color.fromRGBO(237, 245, 252, 1.0); static const Color lavender = Color.fromRGBO(237, 245, 252, 1.0);
static const Color oceanBlue = Color.fromRGBO(30, 52, 78, 1.0); static const Color oceanBlue = Color.fromRGBO(30, 52, 78, 1.0);
static const Color lightBlueGrey = Color.fromRGBO(118, 131, 169, 1.0); static const Color lightBlueGrey = Color.fromRGBO(118, 131, 169, 1.0);
static const Color periwinkle = Color.fromRGBO(197, 208, 230, 1.0); static const Color periwinkle = Color.fromRGBO(195, 210, 227, 1.0);
static const Color blue = Color.fromRGBO(88, 143, 252, 1.0); static const Color blue = Color.fromRGBO(88, 143, 252, 1.0);
static const Color darkLavender = Color.fromRGBO(225, 238, 250, 1.0); static const Color darkLavender = Color.fromRGBO(229, 238, 250, 1.0);
static const Color nightBlue = Color.fromRGBO(46, 57, 96, 1.0); static const Color nightBlue = Color.fromRGBO(46, 57, 96, 1.0);
static const Color moderateOrangeYellow = Color.fromRGBO(245, 134, 82, 1.0);
static const Color moderateOrange = Color.fromRGBO(235, 117, 63, 1.0);
static const Color shineGreen = Color.fromRGBO(76, 189, 87, 1.0);
static const Color moderateGreen = Color.fromRGBO(45, 158, 56, 1.0);
static const Color cornflower = Color.fromRGBO(85, 147, 240, 1.0);
static const Color royalBlue = Color.fromRGBO(43, 114, 221, 1.0);
static const Color lightRed = Color.fromRGBO(227, 87, 87, 1.0);
static const Color persianRed = Color.fromRGBO(206, 55, 55, 1.0);
// NEW DESIGN // NEW DESIGN
static const Color blueCraiola = Color.fromRGBO(69, 110, 255, 1.0); static const Color blueCraiola = Color.fromRGBO(69, 110, 255, 1.0);
static const Color darkBlueCraiola = Color.fromRGBO(53, 86, 136, 1.0); static const Color darkBlueCraiola = Color.fromRGBO(53, 86, 136, 1.0);
@ -29,6 +38,9 @@ class Palette {
static const Color gray = Color.fromRGBO(112, 147, 186, 1.0); static const Color gray = Color.fromRGBO(112, 147, 186, 1.0);
static const Color wildPeriwinkle = Color.fromRGBO(219, 227, 243, 1.0); static const Color wildPeriwinkle = Color.fromRGBO(219, 227, 243, 1.0);
static const Color darkGray = Color.fromRGBO(122, 147, 186, 1.0); static const Color darkGray = Color.fromRGBO(122, 147, 186, 1.0);
static const Color shadowWhite = Color.fromRGBO(242, 245, 255, 1.0);
static const Color niagara = Color.fromRGBO(152, 172, 201, 1.0);
static const Color alizarinRed = Color.fromRGBO(233, 45, 45, 1.0);
// FIXME: Rename. // FIXME: Rename.
static const Color eee = Color.fromRGBO(236, 239, 245, 1.0); static const Color eee = Color.fromRGBO(236, 239, 245, 1.0);
@ -76,6 +88,8 @@ class PaletteDark {
static const Color dividerColor = Color.fromRGBO(48, 59, 95, 1.0); static const Color dividerColor = Color.fromRGBO(48, 59, 95, 1.0);
static const Color violetBlue = Color.fromRGBO(59, 72, 119, 1.0); static const Color violetBlue = Color.fromRGBO(59, 72, 119, 1.0);
static const Color distantBlue = Color.fromRGBO(72, 85, 131, 1.0); static const Color distantBlue = Color.fromRGBO(72, 85, 131, 1.0);
static const Color moderateVioletBlue = Color.fromRGBO(62, 73, 113, 1.0);
static const Color deepVioletBlue = Color.fromRGBO(52, 66, 104, 1.0);
// FIXME: Rename. // FIXME: Rename.
static const Color eee = Color.fromRGBO(236, 239, 245, 1.0); static const Color eee = Color.fromRGBO(236, 239, 245, 1.0);

View file

@ -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';
@ -49,8 +51,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 +76,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 +100,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);
}); });

View file

@ -358,7 +358,9 @@ class Router {
case Routes.exchangeTrade: case Routes.exchangeTrade:
return CupertinoPageRoute<void>( return CupertinoPageRoute<void>(
builder: (_) => MultiProvider( builder: (_) => getIt.get<ExchangeTradePage>());
/*MultiProvider(
providers: [ providers: [
ProxyProvider<SettingsStore, ExchangeTradeStore>( ProxyProvider<SettingsStore, ExchangeTradeStore>(
update: (_, settingsStore, __) => ExchangeTradeStore( update: (_, settingsStore, __) => ExchangeTradeStore(
@ -374,12 +376,13 @@ class Router {
priceStore: priceStore)), priceStore: priceStore)),
], ],
child: ExchangeTradePage(), child: ExchangeTradePage(),
)); ));*/
case Routes.exchangeConfirm: case Routes.exchangeConfirm:
return MaterialPageRoute<void>( return MaterialPageRoute<void>(
builder: (_) => builder: (_) => getIt.get<ExchangeConfirmPage>());
ExchangeConfirmPage(trade: settings.arguments as Trade));
//ExchangeConfirmPage(trade: settings.arguments as Trade));
case Routes.tradeDetails: case Routes.tradeDetails:
return MaterialPageRoute<void>(builder: (context) { return MaterialPageRoute<void>(builder: (context) {

View file

@ -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 = 'bitcoin.electrumx.multicoin.co: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);
} }

View file

@ -9,12 +9,16 @@ part 'node.g.dart';
@HiveType(typeId: 1) @HiveType(typeId: 1)
class Node extends HiveObject { class Node extends HiveObject {
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;

View file

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

View file

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

View file

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

View file

@ -9,6 +9,7 @@ import 'package:cake_wallet/src/domain/common/crypto_currency.dart';
import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart'; import 'package:cake_wallet/src/widgets/alert_with_two_actions.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/src/widgets/standard_list.dart';
class ContactListPage extends BasePage { class ContactListPage extends BasePage {
ContactListPage(this.contactListViewModel, {this.isEditable = true}); ContactListPage(this.contactListViewModel, {this.isEditable = true});
@ -30,7 +31,7 @@ class ContactListPage extends BasePage {
height: 32.0, height: 32.0,
decoration: BoxDecoration( decoration: BoxDecoration(
shape: BoxShape.circle, shape: BoxShape.circle,
color: Theme.of(context).accentTextTheme.title.backgroundColor), color: Theme.of(context).accentTextTheme.caption.color),
child: Stack( child: Stack(
alignment: Alignment.center, alignment: Alignment.center,
children: <Widget>[ children: <Widget>[
@ -54,30 +55,30 @@ class ContactListPage extends BasePage {
@override @override
Widget body(BuildContext context) { Widget body(BuildContext context) {
final shortDivider = Container(
height: 1,
padding: EdgeInsets.only(left: 24),
color: Theme.of(context).backgroundColor,
child: Container(
height: 1,
color: Theme.of(context).primaryTextTheme.title.backgroundColor,
),
);
return Container( return Container(
color: Theme.of(context).backgroundColor,
padding: EdgeInsets.only(top: 20.0, bottom: 20.0), padding: EdgeInsets.only(top: 20.0, bottom: 20.0),
child: Observer( child: Observer(
builder: (_) { builder: (_) {
return contactListViewModel.contacts.isNotEmpty return contactListViewModel.contacts.isNotEmpty
? ListView.separated( ? SectionStandardList(
separatorBuilder: (_, __) => Container( sectionCount: 1,
height: 1, context: context,
padding: EdgeInsets.only(left: 24), itemCounter: (int sectionIndex) => contactListViewModel.contacts.length,
color: Theme.of(context) itemBuilder: (_, sectionIndex, index) {
.accentTextTheme final contact = contactListViewModel.contacts[index];
.title final image = _getCurrencyImage(contact.type);
.backgroundColor, final content = Builder(
child: Container( builder: (context) => GestureDetector(
height: 1,
color: Theme.of(context).dividerColor,
),
),
itemCount: contactListViewModel.contacts.length,
itemBuilder: (BuildContext context, int index) {
final contact = contactListViewModel.contacts[index];
final image = _getCurrencyImage(contact.type);
final content = GestureDetector(
onTap: () async { onTap: () async {
if (!isEditable) { if (!isEditable) {
Navigator.of(context).pop(contact); Navigator.of(context).pop(contact);
@ -106,10 +107,6 @@ class ContactListPage extends BasePage {
children: <Widget>[ children: <Widget>[
Container( Container(
width: double.infinity, width: double.infinity,
color: Theme.of(context)
.accentTextTheme
.title
.backgroundColor,
child: Padding( child: Padding(
padding: const EdgeInsets.only( padding: const EdgeInsets.only(
left: 24, top: 16, bottom: 16, right: 24), left: 24, top: 16, bottom: 16, right: 24),
@ -117,7 +114,7 @@ class ContactListPage extends BasePage {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: crossAxisAlignment:
CrossAxisAlignment.center, CrossAxisAlignment.center,
children: <Widget>[ children: <Widget>[
image ?? Offstage(), image ?? Offstage(),
Padding( Padding(
@ -128,6 +125,7 @@ class ContactListPage extends BasePage {
contact.name, contact.name,
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.normal,
color: Theme.of(context) color: Theme.of(context)
.primaryTextTheme .primaryTextTheme
.title .title
@ -139,57 +137,55 @@ class ContactListPage extends BasePage {
), ),
], ],
), ),
); )
);
return !isEditable return !isEditable
? content ? content
: Slidable( : Slidable(
key: Key('${contact.key}'), key: Key('${contact.key}'),
actionPane: SlidableDrawerActionPane(), actionPane: SlidableDrawerActionPane(),
child: content, child: content,
secondaryActions: <Widget>[ secondaryActions: <Widget>[
IconSlideAction( IconSlideAction(
caption: S.of(context).edit, caption: S.of(context).edit,
color: Colors.blue, color: Colors.blue,
icon: Icons.edit, icon: Icons.edit,
onTap: () async => await Navigator.of(context) onTap: () async => await Navigator.of(context)
.pushNamed(Routes.addressBookAddContact, .pushNamed(Routes.addressBookAddContact,
arguments: contact), arguments: contact),
), ),
IconSlideAction( IconSlideAction(
caption: S.of(context).delete, caption: S.of(context).delete,
color: Colors.red, color: Colors.red,
icon: CupertinoIcons.delete, icon: CupertinoIcons.delete,
onTap: () async { onTap: () async {
final isDelete = final isDelete =
await showAlertDialog(context) ?? false; await showAlertDialog(context) ?? false;
if (isDelete) { if (isDelete) {
await contactListViewModel await contactListViewModel
.delete(contact); .delete(contact);
} }
}, },
), ),
], ],
dismissal: SlidableDismissal( dismissal: SlidableDismissal(
child: SlidableDrawerDismissal(), child: SlidableDrawerDismissal(),
onDismissed: (actionType) async => onDismissed: (actionType) async =>
await contactListViewModel.delete(contact), await contactListViewModel.delete(contact),
onWillDismiss: (actionType) async => onWillDismiss: (actionType) async =>
showAlertDialog(context), showAlertDialog(context),
), ),
); );
}) },
)
: Center( : Center(
child: Text( child: Text(
S.of(context).placeholder_contacts, S.of(context).placeholder_contacts,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
color: Theme.of(context) color: Colors.grey,
.primaryTextTheme
.caption
.color
.withOpacity(0.5),
fontSize: 14), fontSize: 14),
), ),
); );

View file

@ -1,3 +1,4 @@
import 'package:cake_wallet/palette.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:flutter_mobx/flutter_mobx.dart';
@ -30,7 +31,7 @@ class ContactPage extends BasePage {
.addListener(() => contactViewModel.address = _addressController.text); .addListener(() => contactViewModel.address = _addressController.text);
autorun((_) => autorun((_) =>
_currencyTypeController.text = contactViewModel.currency.toString()); _currencyTypeController.text = contactViewModel.currency?.toString()??'');
} }
@override @override
@ -45,7 +46,7 @@ class ContactPage extends BasePage {
@override @override
Widget body(BuildContext context) { Widget body(BuildContext context) {
final downArrow = Image.asset('assets/images/arrow_bottom_purple_icon.png', final downArrow = Image.asset('assets/images/arrow_bottom_purple_icon.png',
color: Theme.of(context).dividerColor, height: 8); color: Theme.of(context).primaryTextTheme.overline.color, height: 8);
reaction((_) => contactViewModel.state, (ContactViewModelState state) { reaction((_) => contactViewModel.state, (ContactViewModelState state) {
if (state is ContactCreationFailure) { if (state is ContactCreationFailure) {
@ -57,79 +58,83 @@ class ContactPage extends BasePage {
} }
}); });
return Container( return ScrollableWithBottomSection(
color: Theme.of(context).backgroundColor, contentPadding: EdgeInsets.all(24),
child: ScrollableWithBottomSection( content: Form(
contentPadding: EdgeInsets.all(24), key: _formKey,
content: Form( child: Column(
key: _formKey, mainAxisSize: MainAxisSize.min,
child: Column( children: <Widget>[
mainAxisSize: MainAxisSize.min, BaseTextFormField(
children: <Widget>[ controller: _nameController,
BaseTextFormField( hintText: S.of(context).contact_name,
controller: _nameController, validator: ContactNameValidator()),
hintText: S.of(context).contact_name, Padding(
validator: ContactNameValidator()), padding: EdgeInsets.only(top: 20),
Padding( child: Container(
padding: EdgeInsets.only(top: 20), child: InkWell(
child: Container( onTap: () => _presentCurrencyPicker(context),
child: InkWell( child: IgnorePointer(
onTap: () => _presentCurrencyPicker(context), child: BaseTextFormField(
child: IgnorePointer( controller: _currencyTypeController,
child: BaseTextFormField( hintText: S.of(context).settings_currency,
controller: _currencyTypeController, suffixIcon: Row(
hintText: S.of(context).settings_currency, mainAxisSize: MainAxisSize.min,
suffixIcon: Row( mainAxisAlignment: MainAxisAlignment.end,
mainAxisSize: MainAxisSize.min, children: <Widget>[downArrow],
mainAxisAlignment: MainAxisAlignment.end, ),
children: <Widget>[downArrow], )),
),
)),
),
), ),
), ),
Padding(
padding: EdgeInsets.only(top: 20),
child: Observer(
builder: (_) => AddressTextField(
controller: _addressController,
options: [AddressTextFieldOption.qrCode],
validator: AddressValidator(
type: contactViewModel.currency),
)),
)
],
),
),
bottomSectionPadding:
EdgeInsets.only(left: 24, right: 24, bottom: 24),
bottomSection: Row(
children: <Widget>[
Expanded(
child: PrimaryButton(
onPressed: () => contactViewModel.reset(),
text: S.of(context).reset,
color: Colors.red,
textColor: Colors.white),
), ),
SizedBox(width: 20), Padding(
Expanded( padding: EdgeInsets.only(top: 20),
child: Observer( child: Observer(
builder: (_) => PrimaryButton( builder: (_) => AddressTextField(
onPressed: () async { controller: _addressController,
if (!_formKey.currentState.validate()) { options: [AddressTextFieldOption.qrCode],
return; buttonColor: Theme.of(context).accentTextTheme.display2.color,
} iconColor: PaletteDark.gray,
borderColor: Theme.of(context).primaryTextTheme.title.backgroundColor,
await contactViewModel.save(); validator: AddressValidator(
}, type: contactViewModel.currency),
text: S.of(context).save, )),
color: Colors.green, )
textColor: Colors.white,
isDisabled: !contactViewModel.isReady)))
], ],
)), ),
); ),
bottomSectionPadding:
EdgeInsets.only(left: 24, right: 24, bottom: 24),
bottomSection: Row(
children: <Widget>[
Expanded(
child: PrimaryButton(
onPressed: () {
contactViewModel.reset();
_nameController.text = '';
_addressController.text = '';
},
text: S.of(context).reset,
color: Colors.red,
textColor: Colors.white),
),
SizedBox(width: 20),
Expanded(
child: Observer(
builder: (_) => PrimaryButton(
onPressed: () async {
if (!_formKey.currentState.validate()) {
return;
}
await contactViewModel.save();
},
text: S.of(context).save,
color: Palette.blueCraiola,
textColor: Colors.white,
isDisabled: !contactViewModel.isReady)))
],
));
} }
void _presentCurrencyPicker(BuildContext context) { void _presentCurrencyPicker(BuildContext context) {

View file

@ -27,25 +27,20 @@ class DashboardPage extends BasePage {
@override @override
Widget Function(BuildContext, Widget) get rootWrapper => Widget Function(BuildContext, Widget) get rootWrapper =>
(BuildContext context, Widget scaffold) => Container( (BuildContext context, Widget scaffold) => Container(
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: LinearGradient(colors: [ gradient: LinearGradient(colors: [
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,20 +49,18 @@ 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,
width: 40, width: 40,
child: FlatButton( child: FlatButton(
highlightColor: Colors.transparent, highlightColor: Colors.transparent,
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,77 +78,64 @@ class DashboardPage extends BasePage {
@override @override
Widget body(BuildContext context) { Widget body(BuildContext context) {
_setEffects(); _setEffects();
return SafeArea( return SafeArea(
child: Column( child: Column(
mainAxisSize: MainAxisSize.max, mainAxisSize: MainAxisSize.max,
children: <Widget>[ children: <Widget>[
Expanded( Expanded(
child: PageView.builder( child: PageView.builder(
controller: controller, controller: controller,
itemCount: pages.length, itemCount: pages.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
return pages[index]; return pages[index];
} })),
) Padding(
), padding: EdgeInsets.only(bottom: 24),
Padding(
padding: EdgeInsets.only(
bottom: 24
),
child: SmoothPageIndicator( child: SmoothPageIndicator(
controller: controller, controller: controller,
count: pages.length, count: pages.length,
effect: ColorTransitionEffect( effect: ColorTransitionEffect(
spacing: 6.0, spacing: 6.0,
radius: 6.0, radius: 6.0,
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(
), width: double.infinity,
Container( padding: EdgeInsets.only(left: 45, right: 45, bottom: 24),
width: double.infinity, child: Row(
padding: EdgeInsets.only( children: <Widget>[
left: 45, Flexible(
right: 45, child: ActionButton(
bottom: 24 image: sendImage,
), title: S.of(context).send,
child: Row( route: Routes.send,
children: <Widget>[ alignment: Alignment.centerLeft,
Flexible(
child: ActionButton(
image: sendImage,
title: S.of(context).send,
route: Routes.send,
alignment: Alignment.centerLeft,
),
), ),
Flexible( ),
child: ActionButton( Flexible(
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,
), ),
Flexible( )
child: ActionButton( ],
image: receiveImage, ),
title: S.of(context).receive, )
route: Routes.receive, ],
alignment: Alignment.centerRight, ));
),
)
],
),
)
],
)
);
} }
void _setEffects() { void _setEffects() {

View file

@ -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,8 +73,6 @@ 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) {
@ -80,12 +81,11 @@ class WalletMenu {
alertContent: S.of(context).reconnect_alert_text, alertContent: S.of(context).reconnect_alert_text,
leftButtonText: S.of(context).ok, leftButtonText: S.of(context).ok,
rightButtonText: S.of(context).cancel, rightButtonText: S.of(context).cancel,
actionLeftButton: () { actionLeftButton: () async {
walletStore.reconnect(); await reconnect?.call();
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
actionRightButton: () => Navigator.of(context).pop() actionRightButton: () => Navigator.of(context).pop());
);
}); });
} }
} }

View file

@ -0,0 +1,21 @@
import 'package:flutter/material.dart';
class FilterTile extends StatelessWidget {
FilterTile({@required this.child});
final Widget child;
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
padding: EdgeInsets.only(
top: 18,
bottom: 18,
left: 24,
right: 24
),
child: child,
);
}
}

View file

@ -0,0 +1,155 @@
import 'dart:ui';
import 'package:cake_wallet/palette.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/filter_tile.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/src/widgets/alert_background.dart';
import 'package:cake_wallet/src/widgets/alert_close_button.dart';
import 'package:cake_wallet/src/widgets/checkbox_widget.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:date_range_picker/date_range_picker.dart' as date_rage_picker;
class FilterWidget extends StatelessWidget {
FilterWidget({@required this.dashboardViewModel});
final DashboardViewModel dashboardViewModel;
final backVector = Image.asset('assets/images/back_vector.png',
color: Palette.darkBlueCraiola
);
@override
Widget build(BuildContext context) {
return AlertBackground(
child: Stack(
alignment: Alignment.center,
children: <Widget>[
Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(
S.of(context).filters,
style: TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.bold,
fontFamily: 'Poppins',
decoration: TextDecoration.none,
),
),
Padding(
padding: EdgeInsets.only(
left: 24,
right: 24,
top: 24
),
child: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(14)),
child: Container(
color: Theme.of(context).textTheme.body2.decorationColor,
child: ListView.separated(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: dashboardViewModel.filterItems.length,
separatorBuilder: (context, _) => Container(
height: 1,
color: Theme.of(context).accentTextTheme.subhead.backgroundColor,
),
itemBuilder: (_, index1) {
final title = dashboardViewModel.filterItems.keys.elementAt(index1);
final section = dashboardViewModel.filterItems.values.elementAt(index1);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding: EdgeInsets.only(
top: 20,
left: 24,
right: 24
),
child: Text(
title,
style: TextStyle(
color: Theme.of(context).accentTextTheme.subhead.color,
fontSize: 16,
fontWeight: FontWeight.w500,
fontFamily: 'Poppins',
decoration: TextDecoration.none
),
),
),
ListView.separated(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: section.length,
separatorBuilder: (context, _) => Container(
height: 1,
padding: EdgeInsets.only(left: 24),
color: Theme.of(context).textTheme.body2.decorationColor,
child: Container(
height: 1,
color: Theme.of(context).accentTextTheme.subhead.backgroundColor,
),
),
itemBuilder: (_, index2) {
final item = section[index2];
final content = item.onChanged != null
? CheckboxWidget(
value: item.value,
caption: item.caption,
onChanged: item.onChanged
)
: GestureDetector(
onTap: () async {
final List<DateTime> picked =
await date_rage_picker.showDatePicker(
context: context,
initialFirstDate: DateTime.now()
.subtract(Duration(days: 1)),
initialLastDate: (DateTime.now()),
firstDate: DateTime(2015),
lastDate: DateTime.now()
.add(Duration(days: 1)));
if (picked != null && picked.length == 2) {
dashboardViewModel.transactionFilterStore
.changeStartDate(picked.first);
dashboardViewModel.transactionFilterStore
.changeEndDate(picked.last);
}
},
child: Padding(
padding: EdgeInsets.only(left: 32),
child: Text(
item.caption,
style: TextStyle(
color: Theme.of(context).primaryTextTheme.title.color,
fontSize: 18,
fontFamily: 'Poppins',
fontWeight: FontWeight.w500,
decoration: TextDecoration.none
),
),
),
);
return FilterTile(child: content);
},
)
],
);
},
),
),
),
),
],
),
AlertCloseButton(image: backVector)
],
),
);
}
}

View file

@ -1,9 +1,7 @@
import 'package:cake_wallet/src/screens/dashboard/widgets/filter_widget.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:cake_wallet/src/domain/exchange/exchange_provider_description.dart';
import 'package:date_range_picker/date_range_picker.dart' as date_rage_picker;
import 'package:flutter_mobx/flutter_mobx.dart';
class HeaderRow extends StatelessWidget { class HeaderRow extends StatelessWidget {
HeaderRow({this.dashboardViewModel}); HeaderRow({this.dashboardViewModel});
@ -31,148 +29,13 @@ class HeaderRow extends StatelessWidget {
color: Colors.white color: Colors.white
), ),
), ),
PopupMenuButton<int>( GestureDetector(
itemBuilder: (context) => [ onTap: () {
PopupMenuItem( showDialog<void>(
enabled: false, context: context,
value: -1, builder: (context) => FilterWidget(dashboardViewModel: dashboardViewModel)
child: Text(S.of(context).transactions, );
style: TextStyle( },
fontWeight: FontWeight.bold,
color: Theme.of(context).primaryTextTheme.title.color))),
PopupMenuItem(
value: 0,
child: Observer(
builder: (_) => Row(
mainAxisAlignment:
MainAxisAlignment
.spaceBetween,
children: [
Text(S.of(context).incoming,
style: TextStyle(
color: Theme.of(context).primaryTextTheme.title.color
),
),
Checkbox(
value: dashboardViewModel
.transactionFilterStore
.displayIncoming,
onChanged: (value) => dashboardViewModel
.transactionFilterStore
.toggleIncoming()
)
]))),
PopupMenuItem(
value: 1,
child: Observer(
builder: (_) => Row(
mainAxisAlignment:
MainAxisAlignment
.spaceBetween,
children: [
Text(S.of(context).outgoing,
style: TextStyle(
color: Theme.of(context).primaryTextTheme.title.color
)
),
Checkbox(
value: dashboardViewModel
.transactionFilterStore
.displayOutgoing,
onChanged: (value) => dashboardViewModel
.transactionFilterStore
.toggleOutgoing(),
)
]))),
PopupMenuItem(
value: 2,
child:
Text(S.of(context).transactions_by_date,
style: TextStyle(
color: Theme.of(context).primaryTextTheme.title.color
)
)),
PopupMenuDivider(),
PopupMenuItem(
enabled: false,
value: -1,
child: Text(S.of(context).trades,
style: TextStyle(
fontWeight: FontWeight.bold,
color: Theme.of(context).primaryTextTheme.title.color))),
PopupMenuItem(
value: 3,
child: Observer(
builder: (_) => Row(
mainAxisAlignment:
MainAxisAlignment
.spaceBetween,
children: [
Text('XMR.TO',
style: TextStyle(
color: Theme.of(context).primaryTextTheme.title.color
)
),
Checkbox(
value: dashboardViewModel
.tradeFilterStore
.displayXMRTO,
onChanged: (value) => dashboardViewModel
.tradeFilterStore
.toggleDisplayExchange(
ExchangeProviderDescription
.xmrto),
)
]))),
PopupMenuItem(
value: 4,
child: Observer(
builder: (_) => Row(
mainAxisAlignment:
MainAxisAlignment
.spaceBetween,
children: [
Text('Change.NOW',
style: TextStyle(
color: Theme.of(context).primaryTextTheme.title.color
)
),
Checkbox(
value: dashboardViewModel
.tradeFilterStore
.displayChangeNow,
onChanged: (value) => dashboardViewModel
.tradeFilterStore
.toggleDisplayExchange(
ExchangeProviderDescription
.changeNow),
)
]))),
PopupMenuItem(
value: 5,
child: Observer(
builder: (_) => Row(
mainAxisAlignment:
MainAxisAlignment
.spaceBetween,
children: [
Text('MorphToken',
style: TextStyle(
color: Theme.of(context).primaryTextTheme.title.color
)
),
Checkbox(
value: dashboardViewModel
.tradeFilterStore
.displayMorphToken,
onChanged: (value) => dashboardViewModel
.tradeFilterStore
.toggleDisplayExchange(
ExchangeProviderDescription
.morphToken),
)
])))
],
child: Container( child: Container(
height: 36, height: 36,
width: 36, width: 36,
@ -182,27 +45,7 @@ class HeaderRow extends StatelessWidget {
), ),
child: filterIcon, child: filterIcon,
), ),
onSelected: (item) async { )
if (item == 2) {
final picked =
await date_rage_picker.showDatePicker(
context: context,
initialFirstDate: DateTime.now()
.subtract(Duration(days: 1)),
initialLastDate: (DateTime.now()),
firstDate: DateTime(2015),
lastDate: DateTime.now()
.add(Duration(days: 1)));
if (picked != null && picked.length == 2) {
dashboardViewModel.transactionFilterStore
.changeStartDate(picked.first);
dashboardViewModel.transactionFilterStore
.changeEndDate(picked.last);
}
}
},
),
], ],
), ),
); );

View file

@ -1,29 +1,30 @@
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';
// FIXME: terrible design.
class MenuWidget extends StatefulWidget { class MenuWidget extends StatefulWidget {
MenuWidget({this.type, this.name, this.subname}); MenuWidget(this.dashboardViewModel);
final WalletType type; final DashboardViewModel dashboardViewModel;
final String name;
final String subname;
@override @override
MenuWidgetState createState() => MenuWidgetState(); MenuWidgetState createState() => MenuWidgetState();
} }
class MenuWidgetState extends State<MenuWidget> { class MenuWidgetState extends State<MenuWidget> {
final moneroIcon = Image.asset('assets/images/monero_menu.png'); Image moneroIcon;
final bitcoinIcon = Image.asset('assets/images/bitcoin_menu.png'); Image bitcoinIcon;
final largeScreen = 731; final largeScreen = 731;
double menuWidth; double menuWidth;
double screenWidth; double screenWidth;
double screenHeight; double screenHeight;
double opacity;
double headerHeight; double headerHeight;
double tileHeight; double tileHeight;
@ -35,12 +36,11 @@ class MenuWidgetState extends State<MenuWidget> {
menuWidth = 0; menuWidth = 0;
screenWidth = 0; screenWidth = 0;
screenHeight = 0; screenHeight = 0;
opacity = 0;
headerHeight = 125; headerHeight = 137;
tileHeight = 75; tileHeight = 75;
fromTopEdge = 50; fromTopEdge = 50;
fromBottomEdge = 21; fromBottomEdge = 30;
super.initState(); super.initState();
WidgetsBinding.instance.addPostFrameCallback(afterLayout); WidgetsBinding.instance.addPostFrameCallback(afterLayout);
@ -52,7 +52,6 @@ class MenuWidgetState extends State<MenuWidget> {
setState(() { setState(() {
menuWidth = screenWidth; menuWidth = screenWidth;
opacity = 1;
if (screenHeight > largeScreen) { if (screenHeight > largeScreen) {
final scale = screenHeight / largeScreen; final scale = screenHeight / largeScreen;
@ -66,145 +65,163 @@ 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;
return SafeArea( moneroIcon = Image.asset('assets/images/monero_menu.png',
child: Row( color: Theme.of(context).accentTextTheme.overline.decorationColor);
mainAxisSize: MainAxisSize.max, bitcoinIcon = Image.asset('assets/images/bitcoin_menu.png',
crossAxisAlignment: CrossAxisAlignment.center, color: Theme.of(context).accentTextTheme.overline.decorationColor);
children: <Widget>[
Padding( return Row(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Padding(
padding: EdgeInsets.only(left: 24), padding: EdgeInsets.only(left: 24),
child: Container( child: Container(
height: 60, height: 60,
width: 4, width: 4,
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(2)), borderRadius: BorderRadius.all(Radius.circular(2)),
color: PaletteDark.gray), color: PaletteDark.gray),
)), )),
SizedBox(width: 12), SizedBox(width: 12),
Expanded( Expanded(
child: ClipRRect( child: ClipRRect(
borderRadius: BorderRadius.only( borderRadius: BorderRadius.only(
topLeft: Radius.circular(24), topLeft: Radius.circular(24),
bottomLeft: Radius.circular(24)), bottomLeft: Radius.circular(24)),
child: Container( child: Container(
width: menuWidth,
height: double.infinity,
color: Theme.of(context).textTheme.body2.decorationColor, color: Theme.of(context).textTheme.body2.decorationColor,
child: SingleChildScrollView( child: ListView.separated(
child: Column( padding: EdgeInsets.only(top: 0),
children: <Widget>[ itemBuilder: (_, index) {
Container( if (index == 0) {
height: headerHeight, return Container(
color: Theme.of(context).textTheme.body2.color, height: headerHeight,
padding: EdgeInsets.only( decoration: BoxDecoration(
left: 24, gradient: LinearGradient(
top: fromTopEdge, colors: [
right: 24, Theme.of(context)
bottom: fromBottomEdge), .accentTextTheme
child: Row( .display1
mainAxisAlignment: MainAxisAlignment.start, .color,
children: <Widget>[ Theme.of(context)
_iconFor(type: widget.type), .accentTextTheme
SizedBox(width: 12), .display1
Expanded( .decorationColor,
child: Container( ],
height: 42, begin: Alignment.topLeft,
child: Column( end: Alignment.bottomRight),
crossAxisAlignment: CrossAxisAlignment.start, ),
mainAxisAlignment: widget.subname != null padding: EdgeInsets.only(
? MainAxisAlignment.spaceBetween left: 24,
: MainAxisAlignment.center, top: fromTopEdge,
children: <Widget>[ right: 24,
Text( bottom: fromBottomEdge),
widget.name, child: Row(
style: TextStyle( mainAxisAlignment: MainAxisAlignment.start,
color: Theme.of(context).textTheme children: <Widget>[
.display2.color, _iconFor(type: widget.dashboardViewModel.type),
fontSize: 16, SizedBox(width: 12),
fontWeight: FontWeight.bold), Expanded(
), child: Container(
if (widget.subname != null) height: 42,
Text( child: Column(
widget.subname, crossAxisAlignment:
style: TextStyle( CrossAxisAlignment.start,
color: Theme.of(context) mainAxisAlignment:
.primaryTextTheme widget.dashboardViewModel.subname !=
.caption.color, null
fontWeight: FontWeight.w500, ? MainAxisAlignment.spaceBetween
fontSize: 12), : MainAxisAlignment.center,
)
],
),
))
],
),
),
Container(
height: 1,
color: Theme.of(context).primaryTextTheme.caption.decorationColor,
),
ListView.separated(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemBuilder: (_, index) {
final item = walletMenu.items[index];
final image = walletMenu.images[index] ?? Offstage();
final isLastTile = index == itemCount - 1;
return GestureDetector(
onTap: () {
Navigator.of(context).pop();
walletMenu.action(index);
},
child: Container(
height: isLastTile
? headerHeight
: tileHeight,
padding: isLastTile
? EdgeInsets.only(
left: 24,
right: 24,
top: fromBottomEdge,
bottom: fromTopEdge)
: EdgeInsets.only(left: 24, right: 24),
alignment: isLastTile ? Alignment.topLeft : null,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[ children: <Widget>[
image, Text(
SizedBox(width: 16), widget.dashboardViewModel.name,
Expanded( style: TextStyle(
child: Text( color: Colors.white,
item, fontSize: 16,
style: TextStyle( fontWeight: FontWeight.bold),
color: Theme.of(context).textTheme ),
.display2.color, if (widget.dashboardViewModel.subname !=
fontSize: 16, null)
fontWeight: FontWeight.bold), Text(
)) widget.dashboardViewModel.subname,
style: TextStyle(
color: Theme.of(context)
.accentTextTheme
.overline
.decorationColor,
fontWeight: FontWeight.w500,
fontSize: 12),
)
], ],
), ),
) ))
); ],
},
separatorBuilder: (_, index) => Container(
height: 1,
color: Theme.of(context).primaryTextTheme.caption.decorationColor,
), ),
itemCount: itemCount) );
], }
),
), index--;
),
) final item = walletMenu.items[index];
) final image = walletMenu.images[index] ?? Offstage();
], final isLastTile = index == itemCount - 1;
)
return GestureDetector(
onTap: () {
Navigator.of(context).pop();
walletMenu.action(index);
},
child: Container(
color: Theme.of(context)
.textTheme
.body2
.decorationColor,
height: isLastTile ? headerHeight : tileHeight,
padding: isLastTile
? EdgeInsets.only(
left: 24,
right: 24,
top: fromBottomEdge,
//bottom: fromTopEdge
)
: EdgeInsets.only(left: 24, right: 24),
alignment: isLastTile ? Alignment.topLeft : null,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
image,
SizedBox(width: 16),
Expanded(
child: Text(
item,
style: TextStyle(
color: Theme.of(context)
.textTheme
.display2
.color,
fontSize: 16,
fontWeight: FontWeight.bold),
))
],
),
));
},
separatorBuilder: (_, index) => Container(
height: 1,
color: Theme.of(context)
.primaryTextTheme
.caption
.decorationColor,
),
itemCount: itemCount + 1),
)))
],
); );
} }

View file

@ -18,10 +18,13 @@ class ExchangePage extends BasePage {
String get title => S.current.exchange; String get title => S.current.exchange;
@override @override
Color get backgroundLightColor => PaletteDark.wildVioletBlue; Color get titleColor => Colors.white;
@override @override
Color get backgroundDarkColor => PaletteDark.wildVioletBlue; Color get backgroundLightColor => Colors.transparent;
@override
Color get backgroundDarkColor => Colors.transparent;
@override @override
Widget middle(BuildContext context) => Widget middle(BuildContext context) =>
@ -36,5 +39,21 @@ class ExchangePage extends BasePage {
@override @override
Widget body(BuildContext context) => Widget body(BuildContext context) =>
BaseExchangeWidget(exchangeViewModel: exchangeViewModel); BaseExchangeWidget(
exchangeViewModel: exchangeViewModel,
leading: leading(context),
middle: middle(context),
trailing: trailing(context),
);
@override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomPadding: resizeToAvoidBottomPadding,
body: Container(
color: Theme.of(context).backgroundColor,
child: body(context)
)
);
}
} }

View file

@ -1,7 +1,6 @@
import 'dart:ui'; import 'dart:ui';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:cake_wallet/palette.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/exchange/widgets/present_provider_picker.dart'; import 'package:cake_wallet/src/screens/exchange/widgets/present_provider_picker.dart';
import 'package:cake_wallet/src/screens/exchange/widgets/base_exchange_widget.dart'; import 'package:cake_wallet/src/screens/exchange/widgets/base_exchange_widget.dart';
@ -17,10 +16,13 @@ class ExchangeTemplatePage extends BasePage {
String get title => S.current.exchange_new_template; String get title => S.current.exchange_new_template;
@override @override
Color get backgroundLightColor => PaletteDark.wildVioletBlue; Color get titleColor => Colors.white;
@override @override
Color get backgroundDarkColor => PaletteDark.wildVioletBlue; Color get backgroundLightColor => Colors.transparent;
@override
Color get backgroundDarkColor => Colors.transparent;
@override @override
Widget trailing(BuildContext context) => Widget trailing(BuildContext context) =>
@ -28,5 +30,22 @@ class ExchangeTemplatePage extends BasePage {
@override @override
Widget body(BuildContext context) => Widget body(BuildContext context) =>
BaseExchangeWidget(exchangeViewModel: exchangeViewModel, isTemplate: true); BaseExchangeWidget(
exchangeViewModel: exchangeViewModel,
leading: leading(context),
middle: middle(context),
trailing: trailing(context),
isTemplate: true
);
@override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomPadding: resizeToAvoidBottomPadding,
body: Container(
color: Theme.of(context).backgroundColor,
child: body(context)
)
);
}
} }

View file

@ -25,29 +25,43 @@ 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.middle,
this.trailing,
this.isTemplate = false, this.isTemplate = false,
}); });
final ExchangeViewModel exchangeViewModel; final ExchangeViewModel exchangeViewModel;
final Widget leading;
final Widget middle;
final Widget trailing;
final bool isTemplate; final bool isTemplate;
@override @override
BaseExchangeWidgetState createState() => BaseExchangeWidgetState createState() => BaseExchangeWidgetState(
BaseExchangeWidgetState( exchangeViewModel: exchangeViewModel,
exchangeViewModel: exchangeViewModel, leading: leading,
isTemplate: isTemplate middle: middle,
); trailing: trailing,
isTemplate: isTemplate);
} }
class BaseExchangeWidgetState extends State<BaseExchangeWidget> { class BaseExchangeWidgetState extends State<BaseExchangeWidget> {
BaseExchangeWidgetState({ BaseExchangeWidgetState({
@ required this.exchangeViewModel, @required this.exchangeViewModel,
@ required this.isTemplate, @required this.leading,
@required this.middle,
@required this.trailing,
@required this.isTemplate,
}); });
final ExchangeViewModel exchangeViewModel; final ExchangeViewModel exchangeViewModel;
final Widget leading;
final Widget middle;
final Widget trailing;
final bool isTemplate; final bool isTemplate;
final double topPanelHeight = 290;
final depositKey = GlobalKey<ExchangeCardState>(); final depositKey = GlobalKey<ExchangeCardState>();
final receiveKey = GlobalKey<ExchangeCardState>(); final receiveKey = GlobalKey<ExchangeCardState>();
@ -68,264 +82,329 @@ class BaseExchangeWidgetState extends State<BaseExchangeWidget> {
); );
final depositWalletName = final depositWalletName =
exchangeViewModel.depositCurrency == CryptoCurrency.xmr exchangeViewModel.depositCurrency == CryptoCurrency.xmr
? exchangeViewModel.wallet.name ? exchangeViewModel.wallet.name
: null; : null;
final receiveWalletName = final receiveWalletName =
exchangeViewModel.receiveCurrency == CryptoCurrency.xmr exchangeViewModel.receiveCurrency == CryptoCurrency.xmr
? exchangeViewModel.wallet.name ? exchangeViewModel.wallet.name
: null; : null;
WidgetsBinding.instance.addPostFrameCallback( WidgetsBinding.instance
(_) => _setReactions(context, exchangeViewModel)); .addPostFrameCallback((_) => _setReactions(context, exchangeViewModel));
return Container( return Form(
color: PaletteDark.backgroundColor, key: _formKey,
child: Form( child: ScrollableWithBottomSection(
key: _formKey, contentPadding: EdgeInsets.only(bottom: 24),
child: ScrollableWithBottomSection( content: Column(
contentPadding: EdgeInsets.only(bottom: 24), children: <Widget>[
content: Column( TopPanel(
children: <Widget>[ gradient: LinearGradient(colors: [
TopPanel( Theme.of(context).primaryTextTheme.body1.color,
color: PaletteDark.darkNightBlue, Theme.of(context).primaryTextTheme.body1.decorationColor,
edgeInsets: EdgeInsets.only(bottom: 32), ], stops: [
widget: Column( 0.35,
children: <Widget>[ 1.0
TopPanel( ], begin: Alignment.topLeft, end: Alignment.bottomRight),
edgeInsets: EdgeInsets.fromLTRB(24, 29, 24, 32), edgeInsets: EdgeInsets.only(bottom: 32),
color: PaletteDark.wildVioletBlue, widget: Column(
widget: Observer(
builder: (_) => ExchangeCard(
key: depositKey,
title: S.of(context).you_will_send,
initialCurrency: exchangeViewModel.depositCurrency,
initialWalletName: depositWalletName,
initialAddress:
exchangeViewModel.depositCurrency == exchangeViewModel.wallet.currency
? exchangeViewModel.wallet.address
: exchangeViewModel.depositAddress,
initialIsAmountEditable: true,
initialIsAddressEditable: exchangeViewModel.isDepositAddressEnabled,
isAmountEstimated: false,
currencies: CryptoCurrency.all,
onCurrencySelected: (currency) =>
exchangeViewModel.changeDepositCurrency(currency: currency),
imageArrow: arrowBottomPurple,
currencyButtonColor: PaletteDark.wildVioletBlue,
addressButtonsColor: PaletteDark.moderateBlue,
currencyValueValidator: AmountValidator(
type: exchangeViewModel.wallet.type),
addressTextFieldValidator: AddressValidator(
type: exchangeViewModel.depositCurrency),
),
)
),
Padding(
padding: EdgeInsets.only(top: 29, left: 24, right: 24),
child: Observer(
builder: (_) => ExchangeCard(
key: receiveKey,
title: S.of(context).you_will_get,
initialCurrency: exchangeViewModel.receiveCurrency,
initialWalletName: receiveWalletName,
initialAddress:
exchangeViewModel.receiveCurrency == exchangeViewModel.wallet.currency
? exchangeViewModel.wallet.address
: exchangeViewModel.receiveAddress,
initialIsAmountEditable: false,
initialIsAddressEditable: exchangeViewModel.isReceiveAddressEnabled,
isAmountEstimated: true,
currencies: CryptoCurrency.all,
onCurrencySelected: (currency) => exchangeViewModel
.changeReceiveCurrency(currency: currency),
imageArrow: arrowBottomCakeGreen,
currencyButtonColor: PaletteDark.darkNightBlue,
addressButtonsColor: PaletteDark.moderateBlue,
currencyValueValidator: AmountValidator(
type: exchangeViewModel.wallet.type),
addressTextFieldValidator: AddressValidator(
type: exchangeViewModel.receiveCurrency),
)),
)
],
)
),
isTemplate
? Offstage()
: Padding(
padding: EdgeInsets.only(
top: 30,
left: 24,
bottom: 24
),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[ children: <Widget>[
Text( TopPanel(
S.of(context).send_templates, edgeInsets: EdgeInsets.all(0),
style: TextStyle( gradient: LinearGradient(
fontSize: 18, colors: [
fontWeight: FontWeight.w600, Theme.of(context)
color: PaletteDark.darkCyanBlue .primaryTextTheme
), .subtitle
.color,
Theme.of(context)
.primaryTextTheme
.subtitle
.decorationColor,
],
begin: Alignment.topLeft,
end: Alignment.bottomRight),
widget: Column(
children: <Widget>[
CupertinoNavigationBar(
leading: leading,
middle: middle,
trailing: trailing,
backgroundColor: Colors.transparent,
border: null,
),
Padding(
padding: EdgeInsets.fromLTRB(24, 29, 24, 32),
child: Observer(
builder: (_) => ExchangeCard(
key: depositKey,
title: S.of(context).you_will_send,
initialCurrency:
exchangeViewModel.depositCurrency,
initialWalletName: depositWalletName,
initialAddress: exchangeViewModel
.depositCurrency ==
exchangeViewModel.wallet.currency
? exchangeViewModel.wallet.address
: exchangeViewModel.depositAddress,
initialIsAmountEditable: true,
initialIsAddressEditable: exchangeViewModel
.isDepositAddressEnabled,
isAmountEstimated: false,
currencies: CryptoCurrency.all,
onCurrencySelected: (currency) =>
exchangeViewModel.changeDepositCurrency(
currency: currency),
imageArrow: arrowBottomPurple,
currencyButtonColor: Colors.transparent,
addressButtonsColor:
Theme.of(context).focusColor,
borderColor: Theme.of(context)
.primaryTextTheme
.body2
.color,
currencyValueValidator: AmountValidator(
type: exchangeViewModel.wallet.type),
addressTextFieldValidator: AddressValidator(
type:
exchangeViewModel.depositCurrency),
),
),
)
],
)),
Padding(
padding: EdgeInsets.only(top: 29, left: 24, right: 24),
child: Observer(
builder: (_) => ExchangeCard(
key: receiveKey,
title: S.of(context).you_will_get,
initialCurrency:
exchangeViewModel.receiveCurrency,
initialWalletName: receiveWalletName,
initialAddress:
exchangeViewModel.receiveCurrency ==
exchangeViewModel.wallet.currency
? exchangeViewModel.wallet.address
: exchangeViewModel.receiveAddress,
initialIsAmountEditable: false,
initialIsAddressEditable:
exchangeViewModel.isReceiveAddressEnabled,
isAmountEstimated: true,
currencies: CryptoCurrency.all,
onCurrencySelected: (currency) =>
exchangeViewModel.changeReceiveCurrency(
currency: currency),
imageArrow: arrowBottomCakeGreen,
currencyButtonColor: Colors.transparent,
addressButtonsColor:
Theme.of(context).focusColor,
borderColor: Theme.of(context)
.primaryTextTheme
.body2
.decorationColor,
currencyValueValidator: AmountValidator(
type: exchangeViewModel.wallet.type),
addressTextFieldValidator: AddressValidator(
type: exchangeViewModel.receiveCurrency),
)),
) )
], ],
), )),
), isTemplate
isTemplate ? Offstage()
? Offstage() : Padding(
: Container( padding: EdgeInsets.only(top: 30, left: 24, bottom: 24),
height: 40, child: Row(
width: double.infinity, mainAxisAlignment: MainAxisAlignment.start,
padding: EdgeInsets.only(left: 24), children: <Widget>[
child: SingleChildScrollView( Text(
scrollDirection: Axis.horizontal, S.of(context).send_templates,
child: Row( style: TextStyle(
children: <Widget>[ fontSize: 18,
GestureDetector( fontWeight: FontWeight.w600,
onTap: () => Navigator.of(context) color: Theme.of(context)
.pushNamed(Routes.exchangeTemplate), .primaryTextTheme
child: Container( .display4
padding: EdgeInsets.only(left: 1, right: 10), .color),
child: DottedBorder( )
borderType: BorderType.RRect, ],
dashPattern: [6, 4],
color: PaletteDark.darkCyanBlue,
strokeWidth: 2,
radius: Radius.circular(20),
child: Container(
height: 34,
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: PaletteDark.darkCyanBlue
),
),
)
),
),
),
Observer(
builder: (_) {
final templates = exchangeViewModel.templates;
final itemCount = exchangeViewModel.templates.length;
return ListView.builder(
scrollDirection: Axis.horizontal,
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: itemCount,
itemBuilder: (context, index) {
final template = templates[index];
return TemplateTile(
key: UniqueKey(),
amount: template.amount,
from: template.depositCurrency,
to: template.receiveCurrency,
onTap: () {
applyTemplate(exchangeViewModel, template);
},
onRemove: () {
showDialog<void>(
context: context,
builder: (dialogContext) {
return AlertWithTwoActions(
alertTitle: S.of(context).template,
alertContent: S.of(context).confirm_delete_template,
leftButtonText: S.of(context).delete,
rightButtonText: S.of(context).cancel,
actionLeftButton: () {
Navigator.of(dialogContext).pop();
exchangeViewModel.exchangeTemplateStore.remove(template: template);
exchangeViewModel.exchangeTemplateStore.update();
},
actionRightButton: () => Navigator.of(dialogContext).pop()
);
}
);
},
);
}
);
}
),
],
)
)
)
],
),
bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
bottomSection: Column(children: <Widget>[
Padding(
padding: EdgeInsets.only(bottom: 15),
child: Observer(builder: (_) {
final description =
exchangeViewModel.provider is XMRTOExchangeProvider
? S.of(context).amount_is_guaranteed
: S.of(context).amount_is_estimate;
return Center(
child: Text(
description,
style: TextStyle(
color: PaletteDark.darkCyanBlue,
fontWeight: FontWeight.w500,
fontSize: 12
), ),
), ),
);
}),
),
isTemplate isTemplate
? PrimaryButton( ? Offstage()
onPressed: () { : Container(
if (_formKey.currentState.validate()) { height: 40,
exchangeViewModel.exchangeTemplateStore.addTemplate( width: double.infinity,
amount: exchangeViewModel.depositAmount, padding: EdgeInsets.only(left: 24),
depositCurrency: exchangeViewModel.depositCurrency.toString(), child: SingleChildScrollView(
receiveCurrency: exchangeViewModel.receiveCurrency.toString(), scrollDirection: Axis.horizontal,
provider: exchangeViewModel.provider.toString(), child: Row(
depositAddress: exchangeViewModel.depositAddress, children: <Widget>[
receiveAddress: exchangeViewModel.receiveAddress GestureDetector(
); onTap: () => Navigator.of(context)
exchangeViewModel.exchangeTemplateStore.update(); .pushNamed(Routes.exchangeTemplate),
Navigator.of(context).pop(); child: Container(
} padding: EdgeInsets.only(left: 1, right: 10),
}, child: DottedBorder(
text: S.of(context).save, borderType: BorderType.RRect,
color: Colors.green, dashPattern: [6, 4],
textColor: Colors.white color: Theme.of(context)
) .primaryTextTheme
: Observer( .display2
builder: (_) => LoadingPrimaryButton( .decorationColor,
text: S.of(context).exchange, strokeWidth: 2,
radius: Radius.circular(20),
child: Container(
height: 34,
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
.display3
.color),
),
)),
),
),
Observer(builder: (_) {
final templates = exchangeViewModel.templates;
final itemCount =
exchangeViewModel.templates.length;
return ListView.builder(
scrollDirection: Axis.horizontal,
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: itemCount,
itemBuilder: (context, index) {
final template = templates[index];
return TemplateTile(
key: UniqueKey(),
amount: template.amount,
from: template.depositCurrency,
to: template.receiveCurrency,
onTap: () {
applyTemplate(
exchangeViewModel, template);
},
onRemove: () {
showDialog<void>(
context: context,
builder: (dialogContext) {
return AlertWithTwoActions(
alertTitle:
S.of(context).template,
alertContent: S
.of(context)
.confirm_delete_template,
leftButtonText:
S.of(context).delete,
rightButtonText:
S.of(context).cancel,
actionLeftButton: () {
Navigator.of(
dialogContext)
.pop();
exchangeViewModel
.exchangeTemplateStore
.remove(
template:
template);
exchangeViewModel
.exchangeTemplateStore
.update();
},
actionRightButton: () =>
Navigator.of(
dialogContext)
.pop());
});
},
);
});
}),
],
)))
],
),
bottomSectionPadding:
EdgeInsets.only(left: 24, right: 24, bottom: 24),
bottomSection: Column(children: <Widget>[
Padding(
padding: EdgeInsets.only(bottom: 15),
child: Observer(builder: (_) {
final description =
exchangeViewModel.provider is XMRTOExchangeProvider
? S.of(context).amount_is_guaranteed
: S.of(context).amount_is_estimate;
return Center(
child: Text(
description,
style: TextStyle(
color: Theme.of(context)
.primaryTextTheme
.display4
.decorationColor,
fontWeight: FontWeight.w500,
fontSize: 12),
),
);
}),
),
isTemplate
? PrimaryButton(
onPressed: () { onPressed: () {
if (_formKey.currentState.validate()) { if (_formKey.currentState.validate()) {
exchangeViewModel.createTrade(); exchangeViewModel.exchangeTemplateStore.addTemplate(
amount: exchangeViewModel.depositAmount,
depositCurrency:
exchangeViewModel.depositCurrency.toString(),
receiveCurrency:
exchangeViewModel.receiveCurrency.toString(),
provider: exchangeViewModel.provider.toString(),
depositAddress: exchangeViewModel.depositAddress,
receiveAddress: exchangeViewModel.receiveAddress);
exchangeViewModel.exchangeTemplateStore.update();
Navigator.of(context).pop();
} }
}, },
color: Colors.blue, text: S.of(context).save,
textColor: Colors.white, color: Colors.green,
isLoading: exchangeViewModel.tradeState is TradeIsCreating, textColor: Colors.white)
)), : Observer(
]), builder: (_) => LoadingPrimaryButton(
)), text: S.of(context).exchange,
); onPressed: () {
if (_formKey.currentState.validate()) {
exchangeViewModel.createTrade();
}
},
color: Palette.blueCraiola,
textColor: Colors.white,
isLoading:
exchangeViewModel.tradeState is TradeIsCreating,
)),
]),
));
} }
void applyTemplate(ExchangeViewModel exchangeViewModel, void applyTemplate(
ExchangeTemplate template) { ExchangeViewModel exchangeViewModel, ExchangeTemplate template) {
exchangeViewModel.changeDepositCurrency( exchangeViewModel.changeDepositCurrency(
currency: CryptoCurrency.fromString(template.depositCurrency)); currency: CryptoCurrency.fromString(template.depositCurrency));
exchangeViewModel.changeReceiveCurrency( exchangeViewModel.changeReceiveCurrency(
@ -374,27 +453,29 @@ 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,
(String _) => _onWalletNameChange( (String _) => _onWalletNameChange(
exchangeViewModel, exchangeViewModel.receiveCurrency, receiveKey)); exchangeViewModel, exchangeViewModel.receiveCurrency, receiveKey));
reaction( reaction(
(_) => exchangeViewModel.wallet.name, (_) => exchangeViewModel.wallet.name,
(String _) => _onWalletNameChange( (String _) => _onWalletNameChange(
exchangeViewModel, exchangeViewModel.depositCurrency, depositKey)); exchangeViewModel, exchangeViewModel.depositCurrency, depositKey));
reaction( reaction(
(_) => exchangeViewModel.receiveCurrency, (_) => exchangeViewModel.receiveCurrency,
(CryptoCurrency currency) => (CryptoCurrency currency) =>
_onCurrencyChange(currency, exchangeViewModel, receiveKey)); _onCurrencyChange(currency, exchangeViewModel, receiveKey));
reaction( reaction(
(_) => exchangeViewModel.depositCurrency, (_) => exchangeViewModel.depositCurrency,
(CryptoCurrency currency) => (CryptoCurrency currency) =>
_onCurrencyChange(currency, exchangeViewModel, depositKey)); _onCurrencyChange(currency, exchangeViewModel, depositKey));
reaction((_) => exchangeViewModel.depositAmount, (String amount) { reaction((_) => exchangeViewModel.depositAmount, (String amount) {
@ -409,7 +490,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);
}); });
@ -425,7 +507,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);
}); });
@ -439,14 +522,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, arguments: state.trade);
} }
}); });
@ -474,20 +555,22 @@ class BaseExchangeWidgetState extends State<BaseExchangeWidget> {
}); });
depositAddressController.addListener( depositAddressController.addListener(
() => exchangeViewModel.depositAddress = depositAddressController.text); () => exchangeViewModel.depositAddress = depositAddressController.text);
depositAmountController.addListener(() { depositAmountController.addListener(() {
if (depositAmountController.text != exchangeViewModel.depositAmount) { if (depositAmountController.text != exchangeViewModel.depositAmount) {
exchangeViewModel.changeDepositAmount(amount: depositAmountController.text); exchangeViewModel.changeDepositAmount(
amount: depositAmountController.text);
} }
}); });
receiveAddressController.addListener( receiveAddressController.addListener(
() => exchangeViewModel.receiveAddress = receiveAddressController.text); () => exchangeViewModel.receiveAddress = receiveAddressController.text);
receiveAmountController.addListener(() { receiveAmountController.addListener(() {
if (receiveAmountController.text != exchangeViewModel.receiveAmount) { if (receiveAmountController.text != exchangeViewModel.receiveAmount) {
exchangeViewModel.changeReceiveAmount(amount: receiveAmountController.text); exchangeViewModel.changeReceiveAmount(
amount: receiveAmountController.text);
} }
}); });
@ -505,34 +588,31 @@ 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);
key.currentState.addressController.text = null; key.currentState.addressController.text = null;
} }
} }
} }

View file

@ -1,8 +1,10 @@
import 'dart:ui'; import 'dart:ui';
import 'package:cake_wallet/palette.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/palette.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/widgets/alert_background.dart';
import 'package:cake_wallet/src/widgets/alert_close_button.dart';
class CurrencyPicker extends StatelessWidget { class CurrencyPicker extends StatelessWidget {
CurrencyPicker({ CurrencyPicker({
@ -16,104 +18,103 @@ class CurrencyPicker extends StatelessWidget {
final List<CryptoCurrency> items; final List<CryptoCurrency> items;
final String title; final String title;
final Function(CryptoCurrency) onItemSelected; final Function(CryptoCurrency) onItemSelected;
final closeButton = Image.asset('assets/images/close.png',
color: Palette.darkBlueCraiola,
);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GestureDetector( return AlertBackground(
onTap: () => Navigator.of(context).pop(), child: Stack(
child: Container( alignment: Alignment.center,
color: Colors.transparent, children: <Widget>[
child: BackdropFilter( Column(
filter: ImageFilter.blur(sigmaX: 3.0, sigmaY: 3.0), mainAxisSize: MainAxisSize.min,
child: Container( children: <Widget>[
decoration: BoxDecoration(color: PaletteDark.darkNightBlue.withOpacity(0.75)), Container(
child: Center( padding: EdgeInsets.only(left: 24, right: 24),
child: Column( child: Text(
mainAxisSize: MainAxisSize.min, title,
children: <Widget>[ textAlign: TextAlign.center,
Container( style: TextStyle(
padding: EdgeInsets.only(left: 24, right: 24), fontSize: 18,
child: Text( fontFamily: 'Poppins',
title, fontWeight: FontWeight.bold,
textAlign: TextAlign.center, decoration: TextDecoration.none,
style: TextStyle( color: Colors.white
fontSize: 18,
fontWeight: FontWeight.bold,
decoration: TextDecoration.none,
color: Colors.white
),
), ),
), ),
Padding( ),
padding: EdgeInsets.only(top: 24), Padding(
child: GestureDetector( padding: EdgeInsets.only(top: 24),
onTap: () => null, child: GestureDetector(
child: ClipRRect( onTap: () => null,
borderRadius: BorderRadius.all(Radius.circular(14)), child: ClipRRect(
child: Container( borderRadius: BorderRadius.all(Radius.circular(14)),
height: 400, child: Container(
width: 300, height: 400,
color: Theme.of(context).dividerColor, width: 300,
child: GridView.count( color: Theme.of(context).accentTextTheme.title.backgroundColor,
shrinkWrap: true, child: GridView.count(
crossAxisCount: 3, shrinkWrap: true,
childAspectRatio: 1.25, crossAxisCount: 3,
physics: const NeverScrollableScrollPhysics(), childAspectRatio: 1.25,
crossAxisSpacing: 1, physics: const NeverScrollableScrollPhysics(),
mainAxisSpacing: 1, crossAxisSpacing: 1,
children: List.generate(15, (index) { mainAxisSpacing: 1,
children: List.generate(15, (index) {
if (index == 14) { if (index == 14) {
return Container( return Container(
color: Theme.of(context).primaryTextTheme.display1.color, color: Theme.of(context).accentTextTheme.title.color,
); );
} }
final item = items[index]; final item = items[index];
final isItemSelected = index == selectedAtIndex; final isItemSelected = index == selectedAtIndex;
final color = isItemSelected final color = isItemSelected
? Theme.of(context).accentTextTheme.subtitle.decorationColor ? Theme.of(context).textTheme.body2.color
: Theme.of(context).primaryTextTheme.display1.color; : Theme.of(context).accentTextTheme.title.color;
final textColor = isItemSelected final textColor = isItemSelected
? Colors.blue ? Palette.blueCraiola
: Theme.of(context).primaryTextTheme.title.color; : Theme.of(context).primaryTextTheme.title.color;
return GestureDetector( return GestureDetector(
onTap: () { onTap: () {
if (onItemSelected == null) { if (onItemSelected == null) {
return; return;
} }
Navigator.of(context).pop(); Navigator.of(context).pop();
onItemSelected(item); onItemSelected(item);
}, },
child: Container( child: Container(
color: color, color: color,
child: Center( child: Center(
child: Text( child: Text(
item.toString(), item.toString(),
style: TextStyle( style: TextStyle(
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.bold, fontFamily: 'Poppins',
decoration: TextDecoration.none, fontWeight: FontWeight.w600,
color: textColor decoration: TextDecoration.none,
), color: textColor
), ),
), ),
), ),
); ),
}) );
), })
), ),
), ),
), ),
) ),
], )
), ],
) ),
), AlertCloseButton(image: closeButton)
), ],
), )
); );
} }
} }

View file

@ -5,7 +5,6 @@ import 'package:cake_wallet/src/domain/common/crypto_currency.dart';
import 'package:cake_wallet/src/widgets/address_text_field.dart'; import 'package:cake_wallet/src/widgets/address_text_field.dart';
import 'package:cake_wallet/src/widgets/base_text_form_field.dart'; import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart'; import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart';
import 'package:cake_wallet/palette.dart';
class ExchangeCard extends StatefulWidget { class ExchangeCard extends StatefulWidget {
ExchangeCard( ExchangeCard(
@ -22,6 +21,7 @@ class ExchangeCard extends StatefulWidget {
this.imageArrow, this.imageArrow,
this.currencyButtonColor = Colors.transparent, this.currencyButtonColor = Colors.transparent,
this.addressButtonsColor = Colors.transparent, this.addressButtonsColor = Colors.transparent,
this.borderColor = Colors.transparent,
this.currencyValueValidator, this.currencyValueValidator,
this.addressTextFieldValidator}) this.addressTextFieldValidator})
: super(key: key); : super(key: key);
@ -38,6 +38,7 @@ class ExchangeCard extends StatefulWidget {
final Image imageArrow; final Image imageArrow;
final Color currencyButtonColor; final Color currencyButtonColor;
final Color addressButtonsColor; final Color addressButtonsColor;
final Color borderColor;
final FormFieldValidator<String> currencyValueValidator; final FormFieldValidator<String> currencyValueValidator;
final FormFieldValidator<String> addressTextFieldValidator; final FormFieldValidator<String> addressTextFieldValidator;
@ -58,10 +59,6 @@ class ExchangeCardState extends State<ExchangeCard> {
bool _isAddressEditable; bool _isAddressEditable;
bool _isAmountEstimated; bool _isAmountEstimated;
final copyImage = Image.asset('assets/images/copy_content.png',
height: 16, width: 16,
color: Colors.white);
@override @override
void initState() { void initState() {
_title = widget.title; _title = widget.title;
@ -115,6 +112,10 @@ class ExchangeCardState extends State<ExchangeCard> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final copyImage = Image.asset('assets/images/copy_content.png',
height: 16, width: 16,
color: Theme.of(context).primaryTextTheme.display2.color);
return Container( return Container(
width: double.infinity, width: double.infinity,
color: Colors.transparent, color: Colors.transparent,
@ -129,7 +130,7 @@ class ExchangeCardState extends State<ExchangeCard> {
style: TextStyle( style: TextStyle(
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: PaletteDark.lightBlueGrey color: Theme.of(context).textTheme.headline.color
), ),
) )
], ],
@ -145,11 +146,12 @@ class ExchangeCardState extends State<ExchangeCard> {
keyboardType: TextInputType.numberWithOptions( keyboardType: TextInputType.numberWithOptions(
signed: false, decimal: true), signed: false, decimal: true),
inputFormatters: [ inputFormatters: [
LengthLimitingTextInputFormatter(15),
BlacklistingTextInputFormatter( BlacklistingTextInputFormatter(
RegExp('[\\-|\\ |\\,]')) RegExp('[\\-|\\ |\\,]'))
], ],
hintText: '0.0000', hintText: '0.0000',
borderColor: PaletteDark.blueGrey, borderColor: widget.borderColor,
textStyle: TextStyle( textStyle: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@ -158,7 +160,7 @@ class ExchangeCardState extends State<ExchangeCard> {
placeholderTextStyle: TextStyle( placeholderTextStyle: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: PaletteDark.lightBlueGrey color: Theme.of(context).textTheme.subhead.decorationColor
), ),
validator: _isAmountEditable validator: _isAmountEditable
? widget.currencyValueValidator ? widget.currencyValueValidator
@ -206,7 +208,7 @@ class ExchangeCardState extends State<ExchangeCard> {
style: TextStyle( style: TextStyle(
fontSize: 10, fontSize: 10,
height: 1.2, height: 1.2,
color: PaletteDark.lightBlueGrey), color: Theme.of(context).textTheme.subhead.decorationColor),
) )
: Offstage(), : Offstage(),
_min != null ? SizedBox(width: 10) : Offstage(), _min != null ? SizedBox(width: 10) : Offstage(),
@ -217,39 +219,45 @@ class ExchangeCardState extends State<ExchangeCard> {
style: TextStyle( style: TextStyle(
fontSize: 10, fontSize: 10,
height: 1.2, height: 1.2,
color: PaletteDark.lightBlueGrey)) color: Theme.of(context).textTheme.subhead.decorationColor))
: Offstage(), : Offstage(),
]), ]),
), ),
Padding( _isAddressEditable
? Offstage()
: Padding(
padding: EdgeInsets.only(top: 20), padding: EdgeInsets.only(top: 20),
child: Text( child: Text(
_isAddressEditable S.of(context).refund_address,
? S.of(context).widgets_address
: S.of(context).refund_address,
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: PaletteDark.lightBlueGrey color: Theme.of(context).textTheme.subhead.decorationColor
), ),
) )
), ),
_isAddressEditable _isAddressEditable
? AddressTextField( ? Padding(
padding: EdgeInsets.only(top: 20),
child: AddressTextField(
controller: addressController, controller: addressController,
options: [ options: [
AddressTextFieldOption.paste, AddressTextFieldOption.paste,
AddressTextFieldOption.qrCode, AddressTextFieldOption.qrCode,
AddressTextFieldOption.addressBook, AddressTextFieldOption.addressBook,
], ],
placeholder: '',
isBorderExist: false, isBorderExist: false,
textStyle: TextStyle( textStyle: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Colors.white), color: Colors.white),
hintStyle: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Theme.of(context).textTheme.subhead.decorationColor),
buttonColor: widget.addressButtonsColor, buttonColor: widget.addressButtonsColor,
validator: widget.addressTextFieldValidator, validator: widget.addressTextFieldValidator,
),
) )
: Padding( : Padding(
padding: EdgeInsets.only(top: 10), padding: EdgeInsets.only(top: 10),

View file

@ -5,7 +5,6 @@ import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/widgets/picker.dart'; import 'package:cake_wallet/src/widgets/picker.dart';
import 'package:cake_wallet/view_model/exchange/exchange_view_model.dart'; import 'package:cake_wallet/view_model/exchange/exchange_view_model.dart';
import 'package:cake_wallet/palette.dart';
class PresentProviderPicker extends StatelessWidget { class PresentProviderPicker extends StatelessWidget {
PresentProviderPicker({@required this.exchangeViewModel}); PresentProviderPicker({@required this.exchangeViewModel});
@ -41,7 +40,7 @@ class PresentProviderPicker extends StatelessWidget {
style: TextStyle( style: TextStyle(
fontSize: 10.0, fontSize: 10.0,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: PaletteDark.lightBlueGrey))) color: Theme.of(context).textTheme.headline.color)))
], ],
), ),
SizedBox(width: 5), SizedBox(width: 5),

View file

@ -1,3 +1,4 @@
import 'package:cake_wallet/store/dashboard/trades_store.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
@ -6,10 +7,12 @@ import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart'; import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/domain/exchange/trade.dart'; import 'package:cake_wallet/src/domain/exchange/trade.dart';
import 'package:cake_wallet/palette.dart';
class ExchangeConfirmPage extends BasePage { class ExchangeConfirmPage extends BasePage {
ExchangeConfirmPage({@required this.trade}); ExchangeConfirmPage({@required this.tradesStore}) : trade = tradesStore.trade;
final TradesStore tradesStore;
final Trade trade; final Trade trade;
@override @override
@ -17,93 +20,102 @@ class ExchangeConfirmPage extends BasePage {
@override @override
Widget body(BuildContext context) { Widget body(BuildContext context) {
final copyImage = Image.asset('assets/images/copy_content.png',
color: Theme.of(context).primaryTextTheme.title.color);
return Container( return Container(
padding: EdgeInsets.all(24), padding: EdgeInsets.fromLTRB(24, 0, 24, 24),
child: Column( child: Column(
children: <Widget>[ children: <Widget>[
Expanded( Expanded(
child: Center( child: Column(
child: Column( children: <Widget>[
mainAxisSize: MainAxisSize.min, Flexible(
children: <Widget>[ child: Center(
Text(
S.of(context).exchange_result_write_down_trade_id,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 18.0,
fontWeight: FontWeight.w600,
color: Theme.of(context).primaryTextTheme.title.color),
),
Padding(
padding: EdgeInsets.only(top: 60),
child: Text( child: Text(
S.of(context).trade_id, S.of(context).exchange_result_write_down_trade_id,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
fontSize: 16.0, fontSize: 18.0,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w500,
color: Theme.of(context).primaryTextTheme.title.color), color: Theme.of(context).primaryTextTheme.title.color),
), ),
)
),
Container(
height: 178,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(30)),
border: Border.all(
width: 1,
color: Theme.of(context).accentTextTheme.caption.color
),
color: Theme.of(context).accentTextTheme.title.color
), ),
Padding( child: Column(
padding: EdgeInsets.only(top: 24), children: <Widget>[
child: Builder( Expanded(
builder: (context) => GestureDetector( child: Padding(
onTap: () { padding: EdgeInsets.all(24),
Clipboard.setData(ClipboardData(text: trade.id)); child: Column(
Scaffold.of(context).showSnackBar(SnackBar( mainAxisAlignment: MainAxisAlignment.spaceBetween,
content: Text( crossAxisAlignment: CrossAxisAlignment.center,
S.of(context).copied_to_clipboard,
textAlign: TextAlign.center,
style: TextStyle(color: Colors.white),
),
backgroundColor: Colors.green,
duration: Duration(milliseconds: 1500),
));
},
child: Container(
height: 60,
padding: EdgeInsets.only(left: 24, right: 24),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(30)),
color: Theme.of(context).accentTextTheme.title.backgroundColor
),
child: Row(
mainAxisSize: MainAxisSize.max,
children: <Widget>[ children: <Widget>[
Expanded( Text(
child: Text( S.of(context).trade_id,
trade.id, style: TextStyle(
maxLines: 1, fontSize: 12.0,
overflow: TextOverflow.ellipsis, fontWeight: FontWeight.w500,
style: TextStyle( color: Theme.of(context).primaryTextTheme.overline.color
fontSize: 18, ),
fontWeight: FontWeight.w600, ),
color: Theme.of(context).primaryTextTheme.title.color Text(
), trade.id,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w600,
color: Theme.of(context).primaryTextTheme.title.color
), ),
), ),
Padding(
padding: EdgeInsets.only(left: 12),
child: copyImage,
)
], ],
), ),
)
),
Padding(
padding: EdgeInsets.fromLTRB(10, 0, 10, 10),
child: Builder(
builder: (context) => PrimaryButton(
onPressed: () {
Clipboard.setData(ClipboardData(text: trade.id));
Scaffold.of(context).showSnackBar(SnackBar(
content: Text(
S.of(context).copied_to_clipboard,
textAlign: TextAlign.center,
style: TextStyle(color: Colors.white),
),
backgroundColor: Colors.green,
duration: Duration(milliseconds: 1500),
));
},
text: S.of(context).copy_id,
color: Theme.of(context).accentTextTheme.caption.backgroundColor,
textColor: Theme.of(context).primaryTextTheme.title.color
),
), ),
) )
), ],
) ),
], ),
), Flexible(
)), child: Offstage()
),
],
)
),
PrimaryButton( PrimaryButton(
onPressed: () => Navigator.of(context) onPressed: () => Navigator.of(context)
.pushReplacementNamed(Routes.exchangeTrade, arguments: trade), .pushReplacementNamed(Routes.exchangeTrade),
text: S.of(context).saved_the_trade_id, text: S.of(context).saved_the_trade_id,
color: Colors.green, color: Palette.blueCraiola,
textColor: Colors.white) textColor: Colors.white)
], ],
), ),

View file

@ -0,0 +1,13 @@
import 'package:flutter/cupertino.dart';
class ExchangeTradeItem {
ExchangeTradeItem({
@required this.title,
@required this.data,
@required this.isCopied,
});
String title;
String data;
bool isCopied;
}

View file

@ -1,5 +1,10 @@
import 'package:cake_wallet/palette.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/exchange/exchange_provider_description.dart'; import 'package:cake_wallet/src/domain/exchange/exchange_provider_description.dart';
import 'package:cake_wallet/src/screens/exchange_trade/exchange_trade_item.dart';
import 'package:cake_wallet/src/screens/exchange_trade/information_page.dart';
import 'package:cake_wallet/src/widgets/standart_list_row.dart';
import 'package:cake_wallet/view_model/exchange/exchange_trade_view_model.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:flutter_mobx/flutter_mobx.dart';
@ -19,15 +24,62 @@ import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart'; import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
void showInformation(ExchangeTradeViewModel exchangeTradeViewModel, BuildContext context) {
final fetchingLabel = S.current.fetching;
final trade = exchangeTradeViewModel.trade;
final walletName = exchangeTradeViewModel.wallet.name;
final information = exchangeTradeViewModel.isSendable
? S.current.exchange_result_confirm(
trade.amount ?? fetchingLabel,
trade.from.toString(),
walletName)
: S.current.exchange_result_description(
trade.amount ?? fetchingLabel, trade.from.toString());
showDialog<void>(
context: context,
builder: (_) => InformationPage(information: information)
);
}
class ExchangeTradePage extends BasePage { class ExchangeTradePage extends BasePage {
ExchangeTradePage({@required this.exchangeTradeViewModel});
final ExchangeTradeViewModel exchangeTradeViewModel;
@override @override
String get title => S.current.exchange; String get title => S.current.exchange;
@override @override
Widget body(BuildContext context) => ExchangeTradeForm(); Widget trailing(BuildContext context) {
final questionImage = Image.asset('assets/images/question_mark.png',
color: Theme.of(context).primaryTextTheme.title.color);
return SizedBox(
height: 20.0,
width: 20.0,
child: ButtonTheme(
minWidth: double.minPositive,
child: FlatButton(
highlightColor: Colors.transparent,
splashColor: Colors.transparent,
padding: EdgeInsets.all(0),
onPressed: () => showInformation(exchangeTradeViewModel, context),
child: questionImage),
),
);
}
@override
Widget body(BuildContext context) => ExchangeTradeForm(exchangeTradeViewModel);
} }
class ExchangeTradeForm extends StatefulWidget { class ExchangeTradeForm extends StatefulWidget {
ExchangeTradeForm(this.exchangeTradeViewModel);
final ExchangeTradeViewModel exchangeTradeViewModel;
@override @override
ExchangeTradeState createState() => ExchangeTradeState(); ExchangeTradeState createState() => ExchangeTradeState();
} }
@ -39,263 +91,139 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
bool _effectsInstalled = false; bool _effectsInstalled = false;
@override @override
Widget build(BuildContext context) { void initState() {
final tradeStore = Provider.of<ExchangeTradeStore>(context); super.initState();
final sendStore = Provider.of<SendStore>(context); WidgetsBinding.instance.addPostFrameCallback(afterLayout);
final walletStore = Provider.of<WalletStore>(context); }
_setEffects(context); void afterLayout(dynamic _) {
showInformation(widget.exchangeTradeViewModel, context);
}
@override
Widget build(BuildContext context) {
final copyImage = Image.asset('assets/images/copy_content.png',
height: 16, width: 16,
color: Theme.of(context).primaryTextTheme.overline.color);
//_setEffects(context);
return Container( return Container(
child: ScrollableWithBottomSection( child: ScrollableWithBottomSection(
contentPadding: EdgeInsets.only(left: 24, right: 24, top: 24), contentPadding: EdgeInsets.only(top: 10, bottom: 16),
content: Observer(builder: (_) { content: Observer(builder: (_) {
final trade = tradeStore.trade; final trade = widget.exchangeTradeViewModel.trade;
final walletName = walletStore.name;
return Column( return Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
Container( trade.expiredAt != null
child: Row( ? Row(
crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.max,
children: [ mainAxisAlignment: MainAxisAlignment.center,
Column( children: <Widget>[
crossAxisAlignment: CrossAxisAlignment.start, Text(
children: <Widget>[ S.of(context).offer_expires_in,
Row( style: TextStyle(
mainAxisAlignment: MainAxisAlignment.start, fontSize: 14.0,
children: <Widget>[ fontWeight: FontWeight.w500,
Text( color: Theme.of(context).primaryTextTheme.overline.color),
S.of(context).id, ),
style: TextStyle( TimerWidget(trade.expiredAt,
height: 2, color: Theme.of(context).primaryTextTheme.title.color)
fontWeight: FontWeight.bold, ])
fontSize: 14.0, : Offstage(),
color: Theme.of(context).primaryTextTheme.title.color), Padding(
), padding: EdgeInsets.only(top: 32),
Text( child: Row(children: <Widget>[
'${trade.id ?? fetchingLabel}', Spacer(flex: 3),
style: TextStyle( Flexible(
fontSize: 14.0, flex: 4,
height: 2, child: Center(
color: Theme.of(context).primaryTextTheme.caption.color), child: AspectRatio(
) aspectRatio: 1.0,
], child: QrImage(
), data: trade.inputAddress ?? fetchingLabel,
Row( backgroundColor: Colors.transparent,
mainAxisAlignment: MainAxisAlignment.start, foregroundColor: Theme.of(context)
children: <Widget>[ .accentTextTheme.subtitle.color,
Text( )))),
S.of(context).amount, Spacer(flex: 3)
style: TextStyle( ]),
height: 2,
fontWeight: FontWeight.bold,
fontSize: 14.0,
color: Theme.of(context).primaryTextTheme.title.color),
),
Text(
'${trade.amount ?? fetchingLabel}',
style: TextStyle(
fontSize: 14.0,
height: 2,
color: Theme.of(context).primaryTextTheme.caption.color),
)
],
),
trade.extraId != null
? Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Text(
S.of(context).payment_id,
style: TextStyle(
height: 2,
fontWeight: FontWeight.bold,
fontSize: 14.0,
color: Theme.of(context).primaryTextTheme.title.color),
),
Text(
'${trade.extraId ?? fetchingLabel}',
style: TextStyle(
fontSize: 14.0,
height: 2,
color: Theme.of(context).primaryTextTheme.caption.color),
)
],
)
: Container(),
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Text(
S.of(context).status,
style: TextStyle(
fontSize: 14.0,
fontWeight: FontWeight.bold,
color: Theme.of(context).primaryTextTheme.title.color,
height: 2),
),
Text(
'${trade.state ?? fetchingLabel}',
style: TextStyle(
fontSize: 14.0,
height: 2,
color: Theme.of(context).primaryTextTheme.caption.color),
)
],
),
trade.expiredAt != null
? Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Text(
S.of(context).offer_expires_in,
style: TextStyle(
fontSize: 14.0,
color: Theme.of(context).primaryTextTheme.title.color),
),
TimerWidget(trade.expiredAt,
color: Theme.of(context).primaryTextTheme.caption.color)
],
)
: Container(),
],
),
],
),
), ),
Padding( Padding(
padding: EdgeInsets.only(top: 20), padding: EdgeInsets.only(top: 16),
child: Row( child: ListView.separated(
children: <Widget>[ shrinkWrap: true,
Spacer( physics: NeverScrollableScrollPhysics(),
flex: 1, itemCount: widget.exchangeTradeViewModel.items.length,
), separatorBuilder: (context, index) => Container(
Flexible( height: 1,
flex: 1, color: Theme.of(context).accentTextTheme.subtitle.backgroundColor,
child: Center(
child: AspectRatio(
aspectRatio: 1.0,
child: QrImage(
data: trade.inputAddress ?? fetchingLabel,
backgroundColor: Colors.transparent,
foregroundColor: Theme.of(context).primaryTextTheme.display4.color,
),
),
)),
Spacer(
flex: 1,
)
],
),
),
SizedBox(
height: 20.0,
),
Center(
child: Text(
S.of(context).trade_is_powered_by(trade.provider != null
? trade.provider.title
: fetchingLabel),
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14.0,
fontWeight: FontWeight.bold,
color: Theme.of(context).primaryTextTheme.title.color),
),
),
Container(
padding: EdgeInsets.only(top: 20, bottom: 20),
child: Center(
child: Text(
trade.inputAddress ?? fetchingLabel,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14.0,
color: Theme.of(context).primaryTextTheme.caption.color),
), ),
itemBuilder: (context, index) {
final item = widget.exchangeTradeViewModel.items[index];
String value;
final content = Observer(
builder: (_) {
switch (index) {
case 0:
value = '${widget.exchangeTradeViewModel.trade.id ?? fetchingLabel}';
break;
case 1:
value = '${widget.exchangeTradeViewModel.trade.amount ?? fetchingLabel}';
break;
case 2:
value = '${widget.exchangeTradeViewModel.trade.state ?? fetchingLabel}';
break;
case 3:
value = widget.exchangeTradeViewModel.trade.inputAddress ?? fetchingLabel;
break;
}
return StandartListRow(
title: item.title,
value: value,
valueFontSize: 14,
image: item.isCopied ? copyImage : null,
);
}
);
return item.isCopied
? Builder(
builder: (context) =>
GestureDetector(
onTap: () {
Clipboard.setData(ClipboardData(text: value));
Scaffold.of(context).showSnackBar(SnackBar(
content: Text(
S.of(context).copied_to_clipboard,
textAlign: TextAlign.center,
style: TextStyle(color: Colors.white),
),
backgroundColor: Colors.green,
duration: Duration(milliseconds: 1500),
));
},
child: content,
)
)
: content;
},
), ),
), ),
Container(
child: Row(
children: <Widget>[
Flexible(
child: Container(
padding: EdgeInsets.only(right: 5.0),
child: Builder(
builder: (context) => PrimaryButton(
onPressed: () {
Clipboard.setData(ClipboardData(text: trade.inputAddress));
Scaffold.of(context).showSnackBar(SnackBar(
content: Text(
S.of(context).copied_to_clipboard,
textAlign: TextAlign.center,
style: TextStyle(color: Colors.white),
),
backgroundColor: Colors.green,
duration: Duration(milliseconds: 1500),
));
},
text: S.of(context).copy_address,
color: Theme.of(context).accentTextTheme.title.backgroundColor,
textColor: Theme.of(context).primaryTextTheme.title.color)
),
)),
Flexible(
child: Container(
padding: EdgeInsets.only(left: 5.0),
child: Builder(
builder: (context) => PrimaryButton(
onPressed: () {
Clipboard.setData(ClipboardData(text: trade.id));
Scaffold.of(context).showSnackBar(SnackBar(
content: Text(
S.of(context).copied_to_clipboard,
textAlign: TextAlign.center,
style: TextStyle(color: Colors.white),
),
backgroundColor: Colors.green,
duration: Duration(milliseconds: 1500),
));
},
text: S.of(context).copy_id,
color: Theme.of(context).accentTextTheme.title.backgroundColor,
textColor: Theme.of(context).primaryTextTheme.title.color)
),
))
],
),
),
Container(
padding: EdgeInsets.only(top: 20),
child: Text(
tradeStore.isSendable
? S.of(context).exchange_result_confirm(
trade.amount ?? fetchingLabel,
trade.from.toString(),
walletName)
: S.of(context).exchange_result_description(
trade.amount ?? fetchingLabel, trade.from.toString()),
textAlign: TextAlign.left,
style: TextStyle(
fontSize: 13.0,
color: Theme.of(context).primaryTextTheme.title.color),
),
),
Text(
S.of(context).exchange_result_write_down_ID,
textAlign: TextAlign.left,
style: TextStyle(
fontSize: 13.0,
color: Theme.of(context).primaryTextTheme.title.color),
)
], ],
); );
}), }),
bottomSectionPadding: EdgeInsets.all(24), bottomSectionPadding: EdgeInsets.fromLTRB(24, 0, 24, 24),
bottomSection: Observer( bottomSection: PrimaryButton(
onPressed: () {},
text: S.of(context).confirm,
color: Palette.blueCraiola,
textColor: Colors.white
)
/*Observer(
builder: (_) => tradeStore.trade.from == CryptoCurrency.xmr && builder: (_) => tradeStore.trade.from == CryptoCurrency.xmr &&
!(sendStore.state is TransactionCommitted) !(sendStore.state is TransactionCommitted)
? LoadingPrimaryButton( ? LoadingPrimaryButton(
@ -312,7 +240,7 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
: S.of(context).send_xmr, : S.of(context).send_xmr,
color: Colors.blue, color: Colors.blue,
textColor: Colors.white) textColor: Colors.white)
: Offstage()), : Offstage()),*/
), ),
); );
} }
@ -322,7 +250,7 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
return; return;
} }
final sendStore = Provider.of<SendStore>(context); /*final sendStore = Provider.of<SendStore>(context);
reaction((_) => sendStore.state, (SendingState state) { reaction((_) => sendStore.state, (SendingState state) {
if (state is SendingFailed) { if (state is SendingFailed) {
@ -376,7 +304,7 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
}); });
}); });
} }
}); });*/
_effectsInstalled = true; _effectsInstalled = true;
} }

View file

@ -0,0 +1,57 @@
import 'dart:ui';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/src/widgets/alert_background.dart';
class InformationPage extends StatelessWidget {
InformationPage({@required this.information});
final String information;
@override
Widget build(BuildContext context) {
return AlertBackground(
child: Center(
child: Container(
margin: EdgeInsets.only(
left: 24,
right: 24
),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(30)),
color: Theme.of(context).textTheme.body2.decorationColor
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(
padding: EdgeInsets.fromLTRB(24, 28, 24, 24),
child: Text(
information,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.normal,
fontFamily: 'Poppins',
decoration: TextDecoration.none,
color: Theme.of(context).accentTextTheme.caption.decorationColor
),
),
),
Padding(
padding: EdgeInsets.fromLTRB(10, 0, 10, 10),
child: PrimaryButton(
onPressed: () => Navigator.of(context).pop(),
text: S.of(context).send_got_it,
color: Theme.of(context).accentTextTheme.caption.backgroundColor,
textColor: Theme.of(context).primaryTextTheme.title.color
),
)
],
),
),
)
);
}
}

View file

@ -53,10 +53,16 @@ class TimerWidgetState extends State<TimerWidget> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return _isExpired return _isExpired
? Text(S.of(context).expired, ? Text(S.of(context).expired,
style: TextStyle(fontSize: 14.0, color: Colors.red)) style: TextStyle(
fontSize: 14.0,
fontWeight: FontWeight.w500,
color: Colors.red))
: Text( : Text(
S.of(context).time(_minutes.toString(), _seconds.toString()), S.of(context).time(_minutes.toString(), _seconds.toString()),
style: TextStyle(fontSize: 14.0, color: widget.color), style: TextStyle(
fontSize: 14.0,
fontWeight: FontWeight.w500,
color: widget.color),
); );
} }

View file

@ -27,7 +27,9 @@ class MoneroAccountListPage extends StatelessWidget {
} }
final MoneroAccountListViewModel accountListViewModel; final MoneroAccountListViewModel accountListViewModel;
final closeIcon = Image.asset('assets/images/close.png'); final closeIcon = Image.asset('assets/images/close.png',
color: Palette.darkBlueCraiola,
);
ScrollController controller; ScrollController controller;
double backgroundHeight; double backgroundHeight;

View file

@ -1,3 +1,4 @@
import 'package:cake_wallet/palette.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:flutter_mobx/flutter_mobx.dart';
@ -6,6 +7,7 @@ import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/core/node_address_validator.dart'; import 'package:cake_wallet/core/node_address_validator.dart';
import 'package:cake_wallet/core/node_port_validator.dart'; import 'package:cake_wallet/core/node_port_validator.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/base_text_form_field.dart';
import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:cake_wallet/src/screens/base_page.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/node_list/node_create_or_edit_view_model.dart'; import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
@ -77,32 +79,11 @@ class NodeCreateOrEditPage extends BasePage {
Row( Row(
children: <Widget>[ children: <Widget>[
Expanded( Expanded(
child: TextFormField( child: BaseTextFormField(
style: TextStyle(
fontSize: 16.0,
color: Theme.of(context)
.primaryTextTheme
.title
.color),
decoration: InputDecoration(
hintStyle: TextStyle(
color: Theme.of(context)
.primaryTextTheme
.caption
.color,
fontSize: 16),
hintText: S.of(context).node_address,
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).dividerColor,
width: 1.0)),
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).dividerColor,
width: 1.0))),
controller: _addressController, controller: _addressController,
hintText: S.of(context).node_address,
validator: NodeAddressValidator(), validator: NodeAddressValidator(),
), )
) )
], ],
), ),
@ -110,34 +91,13 @@ class NodeCreateOrEditPage extends BasePage {
Row( Row(
children: <Widget>[ children: <Widget>[
Expanded( Expanded(
child: TextFormField( child: BaseTextFormField(
style: TextStyle( controller: _portController,
fontSize: 16.0, hintText: S.of(context).node_port,
color: Theme.of(context)
.primaryTextTheme
.title
.color),
keyboardType: TextInputType.numberWithOptions( keyboardType: TextInputType.numberWithOptions(
signed: false, decimal: false), signed: false, decimal: false),
decoration: InputDecoration(
hintStyle: TextStyle(
color: Theme.of(context)
.primaryTextTheme
.caption
.color,
fontSize: 16),
hintText: S.of(context).node_port,
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).dividerColor,
width: 1.0)),
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).dividerColor,
width: 1.0))),
controller: _portController,
validator: NodePortValidator(), validator: NodePortValidator(),
), )
) )
], ],
), ),
@ -146,32 +106,10 @@ class NodeCreateOrEditPage extends BasePage {
Row( Row(
children: <Widget>[ children: <Widget>[
Expanded( Expanded(
child: TextFormField( child: BaseTextFormField(
style: TextStyle(
fontSize: 16.0,
color: Theme.of(context)
.primaryTextTheme
.title
.color),
decoration: InputDecoration(
hintStyle: TextStyle(
color: Theme.of(context)
.primaryTextTheme
.caption
.color,
fontSize: 16),
hintText: S.of(context).login,
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).dividerColor,
width: 1.0)),
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).dividerColor,
width: 1.0))),
controller: _loginController, controller: _loginController,
validator: (value) => null, hintText: S.of(context).login,
), )
) )
], ],
), ),
@ -179,32 +117,10 @@ class NodeCreateOrEditPage extends BasePage {
Row( Row(
children: <Widget>[ children: <Widget>[
Expanded( Expanded(
child: TextFormField( child: BaseTextFormField(
style: TextStyle(
fontSize: 16.0,
color: Theme.of(context)
.primaryTextTheme
.title
.color),
decoration: InputDecoration(
hintStyle: TextStyle(
color: Theme.of(context)
.primaryTextTheme
.caption
.color,
fontSize: 16),
hintText: S.of(context).password,
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).dividerColor,
width: 1.0)),
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).dividerColor,
width: 1.0))),
controller: _passwordController, controller: _passwordController,
validator: (value) => null, hintText: S.of(context).password,
), )
) )
], ],
) )
@ -237,7 +153,7 @@ class NodeCreateOrEditPage extends BasePage {
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
text: S.of(context).save, text: S.of(context).save,
color: Colors.green, color: Palette.blueCraiola,
textColor: Colors.white, textColor: Colors.white,
isDisabled: !nodeCreateOrEditViewModel.isReady, isDisabled: !nodeCreateOrEditViewModel.isReady,
), ),

View file

@ -24,7 +24,7 @@ class NodeListPage extends BasePage {
height: 32, height: 32,
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(16)), borderRadius: BorderRadius.all(Radius.circular(16)),
color: Theme.of(context).accentTextTheme.title.backgroundColor), color: Theme.of(context).accentTextTheme.caption.color),
child: ButtonTheme( child: ButtonTheme(
minWidth: double.minPositive, minWidth: double.minPositive,
child: FlatButton( child: FlatButton(
@ -51,7 +51,7 @@ class NodeListPage extends BasePage {
style: TextStyle( style: TextStyle(
fontSize: 14.0, fontSize: 14.0,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Colors.blue), color: Palette.blueCraiola),
)), )),
), ),
); );
@ -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.value.key}'),
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) {

View file

@ -23,9 +23,9 @@ class NodeListRow extends StandardListRow {
builder: (context, snapshot) { builder: (context, snapshot) {
switch (snapshot.connectionState) { switch (snapshot.connectionState) {
case ConnectionState.done: case ConnectionState.done:
return NodeIndicator(isLive: snapshot.data as bool); return NodeIndicator(isLive: (snapshot.data as bool)??false);
default: default:
return NodeIndicator(); return NodeIndicator(isLive: false);
} }
}); });
} }
@ -38,6 +38,6 @@ class NodeHeaderListRow extends StandardListRow {
@override @override
Widget buildTrailing(BuildContext context) { Widget buildTrailing(BuildContext context) {
return Icon(Icons.add, return Icon(Icons.add,
color: Theme.of(context).primaryTextTheme.title.color, size: 24.0); color: Theme.of(context).accentTextTheme.subhead.color, size: 24.0);
} }
} }

View file

@ -1,28 +1,643 @@
import 'package:cake_wallet/view_model/send_view_model.dart'; import 'dart:ui';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.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/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/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 backgroundLightColor => PaletteDark.nightBlue; Color get titleColor => Colors.white;
@override @override
Color get backgroundDarkColor => PaletteDark.nightBlue; Color get backgroundLightColor => Colors.transparent;
@override @override
bool get resizeToAvoidBottomPadding => false; bool get resizeToAvoidBottomPadding => false;
@override @override
Widget body(BuildContext context) => Widget trailing(context) => TrailButton(
BaseSendWidget(sendViewModel: sendViewModel); 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,
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)));
}
// @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)));
// }
} }

View file

@ -1,9 +1,8 @@
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:cake_wallet/palette.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 {
@ -15,15 +14,29 @@ class SendTemplatePage extends BasePage {
String get title => S.current.exchange_new_template; String get title => S.current.exchange_new_template;
@override @override
Color get backgroundLightColor => PaletteDark.nightBlue; Color get titleColor => Colors.white;
@override @override
Color get backgroundDarkColor => PaletteDark.nightBlue; 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 body(BuildContext context) => BaseSendWidget(
BaseSendWidget(sendViewModel: sendViewModel, isTemplate: true); sendViewModel: sendViewModel,
} leading: leading(context),
middle: middle(context),
isTemplate: true);
@override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomPadding: resizeToAvoidBottomPadding,
body: Container(
color: Theme.of(context).backgroundColor, child: body(context)));
}
}

File diff suppressed because it is too large Load diff

View file

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

View file

@ -39,9 +39,11 @@ class SettingsPage extends BasePage {
if (item is PickerListItem) { if (item is PickerListItem) {
return Observer(builder: (_) { return Observer(builder: (_) {
return SettingsPickerCell<dynamic>( return SettingsPickerCell<dynamic>(
title: item.title, title: item.title,
selectedItem: item.selectedItem(), selectedItem: item.selectedItem(),
items: item.items); items: item.items,
onItemSelected: (dynamic value) => item.onItemSelected(value),
);
}); });
} }

View file

@ -4,25 +4,30 @@ 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(
{@required String title,
this.selectedItem,
this.items,
this.onItemSelected})
: super( : super(
title: title, title: title,
isSelected: false, isSelected: false,
onTap: (BuildContext context) async { onTap: (BuildContext context) async {
final selectedAtIndex = items.indexOf(selectedItem); final selectedAtIndex = items.indexOf(selectedItem);
await showDialog<void>( await showDialog<void>(
context: context, context: context,
builder: (_) => Picker( builder: (_) => Picker(
items: items, items: items,
selectedAtIndex: selectedAtIndex, selectedAtIndex: selectedAtIndex,
title: S.current.please_select, title: S.current.please_select,
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
onItemSelected: (Object _) {})); onItemSelected: (ItemType item) => onItemSelected?.call(item)));
}); });
final ItemType selectedItem; final ItemType selectedItem;
final List<ItemType> items; final List<ItemType> items;
final void Function(ItemType item) onItemSelected;
@override @override
Widget buildTrailing(BuildContext context) { Widget buildTrailing(BuildContext context) {
@ -35,4 +40,4 @@ class SettingsPickerCell<ItemType> extends StandardListRow {
color: Theme.of(context).primaryTextTheme.caption.color), color: Theme.of(context).primaryTextTheme.caption.color),
); );
} }
} }

View file

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

View file

@ -37,11 +37,14 @@ class WalletListBodyState extends State<WalletListBody> {
final bitcoinIcon = final bitcoinIcon =
Image.asset('assets/images/bitcoin.png', height: 24, width: 24); Image.asset('assets/images/bitcoin.png', height: 24, width: 24);
final scrollController = ScrollController(); final scrollController = ScrollController();
final double tileHeight = 60;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final newWalletImage = Image.asset('assets/images/new_wallet.png', final newWalletImage = Image.asset('assets/images/new_wallet.png',
height: 12, width: 12, color: Palette.oceanBlue); height: 12,
width: 12,
color: Theme.of(context).accentTextTheme.headline.decorationColor);
final restoreWalletImage = Image.asset('assets/images/restore_wallet.png', final restoreWalletImage = Image.asset('assets/images/restore_wallet.png',
height: 12, height: 12,
width: 12, width: 12,
@ -58,19 +61,11 @@ class WalletListBodyState extends State<WalletListBody> {
shrinkWrap: true, shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
separatorBuilder: (_, index) => Divider( separatorBuilder: (_, index) => Divider(
color: Theme.of(context).backgroundColor, height: 16), color: Theme.of(context).backgroundColor, height: 32),
itemCount: widget.walletListViewModel.wallets.length, itemCount: widget.walletListViewModel.wallets.length,
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);
@ -80,7 +75,7 @@ class WalletListBodyState extends State<WalletListBody> {
.generateImagesForWalletMenu(wallet.isCurrent); .generateImagesForWalletMenu(wallet.isCurrent);
return Container( return Container(
height: 108, height: tileHeight,
width: double.infinity, width: double.infinity,
child: CustomScrollView( child: CustomScrollView(
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
@ -90,7 +85,7 @@ class WalletListBodyState extends State<WalletListBody> {
pinned: false, pinned: false,
floating: true, floating: true,
delegate: WalletTile( delegate: WalletTile(
min: screenWidth - 228, min: screenWidth - 170,
max: screenWidth, max: screenWidth,
image: _imageFor(type: wallet.type), image: _imageFor(type: wallet.type),
walletName: wallet.name, walletName: wallet.name,
@ -101,10 +96,11 @@ class WalletListBodyState extends State<WalletListBody> {
delegate: delegate:
SliverChildBuilderDelegate((context, index) { SliverChildBuilderDelegate((context, index) {
final item = items[index]; final item = items[index];
final color = colors[index];
final image = images[index]; final image = images[index];
final firstColor = colors[index*2];
final secondColor = colors[index*2 + 1];
final radius = index == 0 ? 12.0 : 0.0; final radius = index == 0 ? 10.0 : 0.0;
return GestureDetector( return GestureDetector(
onTap: () { onTap: () {
@ -117,8 +113,8 @@ class WalletListBodyState extends State<WalletListBody> {
wallet.isCurrent); wallet.isCurrent);
}, },
child: Container( child: Container(
height: 108, height: tileHeight,
width: 108, width: 80,
color: Theme.of(context).backgroundColor, color: Theme.of(context).backgroundColor,
child: Container( child: Container(
padding: EdgeInsets.only(left: 5, right: 5), padding: EdgeInsets.only(left: 5, right: 5),
@ -126,18 +122,27 @@ class WalletListBodyState extends State<WalletListBody> {
borderRadius: BorderRadius.only( borderRadius: BorderRadius.only(
topLeft: Radius.circular(radius), topLeft: Radius.circular(radius),
bottomLeft: Radius.circular(radius)), bottomLeft: Radius.circular(radius)),
color: color), gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
firstColor,
secondColor
]
)
),
child: Center( child: Center(
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: <Widget>[ children: <Widget>[
image, image,
SizedBox(height: 5), SizedBox(height: 2),
Text( Text(
item, item,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 7,
fontWeight: FontWeight.w500,
color: Colors.white), color: Colors.white),
) )
], ],
@ -159,9 +164,8 @@ class WalletListBodyState extends State<WalletListBody> {
Navigator.of(context).pushNamed(Routes.newWalletType), Navigator.of(context).pushNamed(Routes.newWalletType),
image: newWalletImage, image: newWalletImage,
text: S.of(context).wallet_list_create_new_wallet, text: S.of(context).wallet_list_create_new_wallet,
color: Colors.white, color: Theme.of(context).accentTextTheme.subtitle.decorationColor,
textColor: Palette.oceanBlue, textColor: Theme.of(context).accentTextTheme.headline.decorationColor,
borderColor: Palette.oceanBlue,
), ),
SizedBox(height: 10.0), SizedBox(height: 10.0),
PrimaryImageButton( PrimaryImageButton(
@ -169,7 +173,7 @@ class WalletListBodyState extends State<WalletListBody> {
.pushNamed(Routes.restoreWalletType), .pushNamed(Routes.restoreWalletType),
image: restoreWalletImage, image: restoreWalletImage,
text: S.of(context).wallet_list_restore_wallet, text: S.of(context).wallet_list_restore_wallet,
color: Theme.of(context).primaryTextTheme.overline.color, color: Theme.of(context).accentTextTheme.caption.color,
textColor: Theme.of(context).primaryTextTheme.title.color) textColor: Theme.of(context).primaryTextTheme.title.color)
])), ])),
)); ));

View file

@ -6,6 +6,7 @@ import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/stores/wallet_list/wallet_list_store.dart'; import 'package:cake_wallet/src/stores/wallet_list/wallet_list_store.dart';
import 'package:cake_wallet/view_model/wallet_list/wallet_list_item.dart'; import 'package:cake_wallet/view_model/wallet_list/wallet_list_item.dart';
import 'package:cake_wallet/src/screens/auth/auth_page.dart'; import 'package:cake_wallet/src/screens/auth/auth_page.dart';
import 'package:cake_wallet/palette.dart';
class WalletMenu { class WalletMenu {
WalletMenu(this.context, this.walletListViewModel); WalletMenu(this.context, this.walletListViewModel);
@ -20,18 +21,25 @@ class WalletMenu {
S.current.rescan S.current.rescan
]; ];
final List<Color> listColors = [ final List<Color> firstColors = [
Colors.blue, Palette.cornflower,
Colors.orange, Palette.moderateOrangeYellow,
Colors.red, Palette.lightRed,
Colors.green Palette.shineGreen
];
final List<Color> secondColors = [
Palette.royalBlue,
Palette.moderateOrange,
Palette.persianRed,
Palette.moderateGreen
]; ];
final List<Image> listImages = [ final List<Image> listImages = [
Image.asset('assets/images/load.png', height: 32, width: 32, color: Colors.white), Image.asset('assets/images/load.png', height: 24, width: 24, color: Colors.white),
Image.asset('assets/images/eye_action.png', height: 32, width: 32, color: Colors.white), Image.asset('assets/images/eye_action.png', height: 24, width: 24, color: Colors.white),
Image.asset('assets/images/trash.png', height: 32, width: 32, color: Colors.white), Image.asset('assets/images/trash.png', height: 24, width: 24, color: Colors.white),
Image.asset('assets/images/scanner.png', height: 32, width: 32, 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) {
@ -46,18 +54,30 @@ class WalletMenu {
} }
List<Color> generateColorsForWalletMenu(bool isCurrentWallet) { List<Color> generateColorsForWalletMenu(bool isCurrentWallet) {
final colors = List<Color>(); final colors = <Color>[];
if (!isCurrentWallet) colors.add(listColors[0]); if (!isCurrentWallet) {
if (isCurrentWallet) colors.add(listColors[1]); colors.add(firstColors[0]);
if (!isCurrentWallet) colors.add(listColors[2]); colors.add(secondColors[0]);
if (isCurrentWallet) colors.add(listColors[3]); }
if (isCurrentWallet) {
colors.add(firstColors[1]);
colors.add(secondColors[1]);
}
if (!isCurrentWallet) {
colors.add(firstColors[2]);
colors.add(secondColors[2]);
}
if (isCurrentWallet) {
colors.add(firstColors[3]);
colors.add(secondColors[3]);
}
return colors; return colors;
} }
List<Image> generateImagesForWalletMenu(bool isCurrentWallet) { List<Image> generateImagesForWalletMenu(bool isCurrentWallet) {
final images = List<Image>(); final images = <Image>[];
if (!isCurrentWallet) images.add(listImages[0]); if (!isCurrentWallet) images.add(listImages[0]);
if (isCurrentWallet) images.add(listImages[1]); if (isCurrentWallet) images.add(listImages[1]);
@ -109,7 +129,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

View file

@ -17,17 +17,18 @@ class WalletTile extends SliverPersistentHeaderDelegate {
final String walletName; final String walletName;
final String walletAddress; final String walletAddress;
final bool isCurrent; final bool isCurrent;
final double tileHeight = 60;
@override @override
Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) { Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
var opacity = 1 - shrinkOffset / (max - min); var opacity = 1 - shrinkOffset / (max - min);
opacity = opacity >= 0 ? opacity : 0; opacity = opacity >= 0 ? opacity : 0;
var panelWidth = 12 * opacity; var panelWidth = 10 * opacity;
panelWidth = panelWidth < 12 ? 0 : 12; panelWidth = panelWidth < 10 ? 0 : 10;
final currentColor = isCurrent final currentColor = isCurrent
? Theme.of(context).accentTextTheme.caption.color ? Theme.of(context).accentTextTheme.subtitle.decorationColor
: Theme.of(context).backgroundColor; : Theme.of(context).backgroundColor;
return Stack( return Stack(
@ -38,7 +39,7 @@ class WalletTile extends SliverPersistentHeaderDelegate {
top: 0, top: 0,
right: max - 4, right: max - 4,
child: Container( child: Container(
height: 108, height: tileHeight,
width: 4, width: 4,
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.only(topRight: Radius.circular(4), bottomRight: Radius.circular(4)), borderRadius: BorderRadius.only(topRight: Radius.circular(4), bottomRight: Radius.circular(4)),
@ -48,13 +49,30 @@ class WalletTile extends SliverPersistentHeaderDelegate {
), ),
Positioned( Positioned(
top: 0, top: 0,
right: 12, right: 10,
child: Container( child: Container(
height: 108, height: tileHeight,
width: max - 16, width: max - 14,
padding: EdgeInsets.only(left: 20, right: 20), padding: EdgeInsets.only(left: 20, right: 20),
color: Theme.of(context).backgroundColor, color: Theme.of(context).backgroundColor,
child: Column( alignment: Alignment.centerLeft,
child: Row(
//mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
image,
SizedBox(width: 10),
Text(
walletName,
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.w600,
color: Theme.of(context).primaryTextTheme.title.color
),
)
],
),
/*Column(
mainAxisSize: MainAxisSize.max, mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[ children: <Widget>[
@ -92,7 +110,7 @@ class WalletTile extends SliverPersistentHeaderDelegate {
) )
: Offstage() : Offstage()
], ],
), ),*/
), ),
), ),
Positioned( Positioned(
@ -101,29 +119,18 @@ class WalletTile extends SliverPersistentHeaderDelegate {
child: Opacity( child: Opacity(
opacity: opacity, opacity: opacity,
child: Container( child: Container(
height: 108, height: tileHeight,
width: panelWidth, width: panelWidth,
padding: EdgeInsets.only(
top: 1,
left: 1,
bottom: 1
),
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.only(topLeft: Radius.circular(12), bottomLeft: Radius.circular(12)), borderRadius: BorderRadius.only(topLeft: Radius.circular(10), bottomLeft: Radius.circular(10)),
color: Theme.of(context).accentTextTheme.subtitle.color gradient: LinearGradient(
), begin: Alignment.topCenter,
child: Container( end: Alignment.bottomCenter,
decoration: BoxDecoration( colors: [
borderRadius: BorderRadius.only(topLeft: Radius.circular(12), bottomLeft: Radius.circular(12)), Theme.of(context).accentTextTheme.headline.color,
gradient: LinearGradient( Theme.of(context).accentTextTheme.headline.backgroundColor
begin: Alignment.topCenter, ]
end: Alignment.bottomCenter, )
colors: [
Theme.of(context).accentTextTheme.caption.backgroundColor,
Theme.of(context).accentTextTheme.caption.decorationColor
]
)
),
), ),
), ),
) )

View file

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

View file

@ -1,4 +1,3 @@
import 'package:cake_wallet/palette.dart';
import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/routes.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/generated/i18n.dart';
@ -23,6 +22,7 @@ class AddressTextField extends StatelessWidget {
this.isBorderExist = true, this.isBorderExist = true,
this.buttonColor, this.buttonColor,
this.borderColor, this.borderColor,
this.iconColor,
this.textStyle, this.textStyle,
this.hintStyle, this.hintStyle,
this.validator}); this.validator});
@ -40,6 +40,7 @@ class AddressTextField extends StatelessWidget {
final bool isBorderExist; final bool isBorderExist;
final Color buttonColor; final Color buttonColor;
final Color borderColor; final Color borderColor;
final Color iconColor;
final TextStyle textStyle; final TextStyle textStyle;
final TextStyle hintStyle; final TextStyle hintStyle;
FocusNode focusNode; FocusNode focusNode;
@ -55,7 +56,7 @@ class AddressTextField extends StatelessWidget {
focusNode: focusNode, focusNode: focusNode,
style: textStyle ?? TextStyle( style: textStyle ?? TextStyle(
fontSize: 16, fontSize: 16,
color: Colors.white color: Theme.of(context).primaryTextTheme.title.color
), ),
decoration: InputDecoration( decoration: InputDecoration(
suffixIcon: SizedBox( suffixIcon: SizedBox(
@ -64,7 +65,7 @@ class AddressTextField extends StatelessWidget {
), ),
hintStyle: hintStyle ?? TextStyle( hintStyle: hintStyle ?? TextStyle(
fontSize: 16, fontSize: 16,
color: PaletteDark.darkCyanBlue color: Theme.of(context).hintColor
), ),
hintText: placeholder ?? S.current.widgets_address, hintText: placeholder ?? S.current.widgets_address,
focusedBorder: isBorderExist focusedBorder: isBorderExist
@ -112,7 +113,9 @@ class AddressTextField extends StatelessWidget {
borderRadius: borderRadius:
BorderRadius.all(Radius.circular(6))), BorderRadius.all(Radius.circular(6))),
child: Image.asset( child: Image.asset(
'assets/images/duplicate.png')), 'assets/images/duplicate.png',
color: iconColor ?? Theme.of(context).primaryTextTheme.display1.decorationColor,
)),
)), )),
], ],
if (this.options.contains(AddressTextFieldOption.qrCode)) ...[ if (this.options.contains(AddressTextFieldOption.qrCode)) ...[
@ -128,7 +131,9 @@ class AddressTextField extends StatelessWidget {
color: buttonColor ?? Theme.of(context).accentTextTheme.title.color, color: buttonColor ?? Theme.of(context).accentTextTheme.title.color,
borderRadius: borderRadius:
BorderRadius.all(Radius.circular(6))), BorderRadius.all(Radius.circular(6))),
child: Image.asset('assets/images/qr_code_icon.png')), child: Image.asset('assets/images/qr_code_icon.png',
color: iconColor ?? Theme.of(context).primaryTextTheme.display1.decorationColor,
)),
)) ))
], ],
if (this if (this
@ -147,7 +152,9 @@ class AddressTextField extends StatelessWidget {
borderRadius: borderRadius:
BorderRadius.all(Radius.circular(6))), BorderRadius.all(Radius.circular(6))),
child: Image.asset( child: Image.asset(
'assets/images/open_book.png')), 'assets/images/open_book.png',
color: iconColor ?? Theme.of(context).primaryTextTheme.display1.decorationColor,
)),
)) ))
], ],
if (this if (this
@ -166,7 +173,9 @@ class AddressTextField extends StatelessWidget {
borderRadius: borderRadius:
BorderRadius.all(Radius.circular(6))), BorderRadius.all(Radius.circular(6))),
child: Image.asset( child: Image.asset(
'assets/images/receive_icon_raw.png')), 'assets/images/receive_icon_raw.png',
color: iconColor ?? Theme.of(context).primaryTextTheme.display1.decorationColor,
)),
)), )),
], ],
], ],

View file

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:cake_wallet/src/widgets/base_alert_dialog.dart'; import 'package:cake_wallet/src/widgets/base_alert_dialog.dart';
import 'package:cake_wallet/palette.dart';
class AlertWithOneAction extends BaseAlertDialog { class AlertWithOneAction extends BaseAlertDialog {
AlertWithOneAction({ AlertWithOneAction({
@ -31,13 +32,7 @@ class AlertWithOneAction extends BaseAlertDialog {
width: 300, width: 300,
height: 52, height: 52,
padding: EdgeInsets.only(left: 12, right: 12), padding: EdgeInsets.only(left: 12, right: 12),
decoration: BoxDecoration( color: Palette.blueCraiola,
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(24),
bottomRight: Radius.circular(24)
),
color: Colors.white
),
child: ButtonTheme( child: ButtonTheme(
minWidth: double.infinity, minWidth: double.infinity,
child: FlatButton( child: FlatButton(
@ -50,7 +45,7 @@ class AlertWithOneAction extends BaseAlertDialog {
style: TextStyle( style: TextStyle(
fontSize: 15, fontSize: 15,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Colors.blue, color: Colors.white,
decoration: TextDecoration.none, decoration: TextDecoration.none,
), ),
)), )),

View file

@ -17,6 +17,7 @@ class BaseAlertDialog extends StatelessWidget {
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
fontSize: 20, fontSize: 20,
fontFamily: 'Poppins',
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Theme.of(context).primaryTextTheme.title.color, color: Theme.of(context).primaryTextTheme.title.color,
decoration: TextDecoration.none, decoration: TextDecoration.none,
@ -30,7 +31,7 @@ class BaseAlertDialog extends StatelessWidget {
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w600, fontFamily: 'Poppins',
color: Theme.of(context).primaryTextTheme.title.color, color: Theme.of(context).primaryTextTheme.title.color,
decoration: TextDecoration.none, decoration: TextDecoration.none,
), ),
@ -38,55 +39,39 @@ class BaseAlertDialog extends StatelessWidget {
} }
Widget actionButtons(BuildContext context) { Widget actionButtons(BuildContext context) {
return Container( return Row(
width: 300, mainAxisSize: MainAxisSize.max,
height: 52, children: <Widget>[
decoration: BoxDecoration( Flexible(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(24),
bottomRight: Radius.circular(24)
),
color: Colors.white
),
child: Row(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Flexible(
child: Container( child: Container(
padding: EdgeInsets.only(left: 12, right: 12), height: 52,
decoration: BoxDecoration( padding: EdgeInsets.only(left: 6, right: 6),
borderRadius: BorderRadius.only(bottomLeft: Radius.circular(24)), color: Palette.blueCraiola,
),
child: ButtonTheme( child: ButtonTheme(
minWidth: double.infinity, minWidth: double.infinity,
child: FlatButton( child: FlatButton(
onPressed: actionLeft, onPressed: actionLeft,
highlightColor: Colors.transparent, highlightColor: Colors.transparent,
splashColor: Colors.transparent, splashColor: Colors.transparent,
child: Text( child: Text(
leftActionButtonText, leftActionButtonText,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
fontSize: 15, fontSize: 15,
fontWeight: FontWeight.w600, fontFamily: 'Poppins',
color: Colors.blue, fontWeight: FontWeight.w600,
decoration: TextDecoration.none, color: Colors.white,
), decoration: TextDecoration.none,
)), ),
)),
), ),
) )
), ),
Container( Flexible(
height: 52,
width: 1,
color: Colors.grey.withOpacity(0.2),
),
Flexible(
child: Container( child: Container(
padding: EdgeInsets.only(left: 12, right: 12), height: 52,
decoration: BoxDecoration( padding: EdgeInsets.only(left: 6, right: 6),
borderRadius: BorderRadius.only(bottomRight: Radius.circular(24)), color: Palette.alizarinRed,
),
child: ButtonTheme( child: ButtonTheme(
minWidth: double.infinity, minWidth: double.infinity,
child: FlatButton( child: FlatButton(
@ -98,16 +83,16 @@ class BaseAlertDialog extends StatelessWidget {
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
fontSize: 15, fontSize: 15,
fontFamily: 'Poppins',
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Colors.red, color: Colors.white,
decoration: TextDecoration.none, decoration: TextDecoration.none,
), ),
)), )),
), ),
) )
) )
], ],
),
); );
} }
@ -126,44 +111,30 @@ class BaseAlertDialog extends StatelessWidget {
child: Center( child: Center(
child: GestureDetector( child: GestureDetector(
onTap: () => null, onTap: () => null,
child: Container( child: ClipRRect(
width: 300, borderRadius: BorderRadius.all(Radius.circular(30)),
height: 257, child: Container(
decoration: BoxDecoration( width: 300,
borderRadius: BorderRadius.all(Radius.circular(24)), color: Theme.of(context).accentTextTheme.title.decorationColor,
color: Theme.of(context).accentTextTheme.title.backgroundColor child: Column(
), mainAxisSize: MainAxisSize.min,
child: Column( children: <Widget>[
children: <Widget>[ Container(
Container( padding: EdgeInsets.fromLTRB(24, 32, 24, 32),
width: 300, child: Column(
height: 77, crossAxisAlignment: CrossAxisAlignment.center,
padding: EdgeInsets.only(left: 24, right: 24), children: <Widget>[
decoration: BoxDecoration( title(context),
borderRadius: BorderRadius.only( Padding(
topLeft: Radius.circular(24), padding: EdgeInsets.only(top: 8),
topRight: Radius.circular(24) child: content(context),
)
],
), ),
), ),
child: Center( actionButtons(context)
child: title(context), ],
), ),
),
Container(
width: 300,
height: 1,
color: Theme.of(context).dividerColor,
),
Container(
width: 300,
height: 127,
padding: EdgeInsets.all(24),
child: Center(
child: content(context),
),
),
actionButtons(context)
],
), ),
), ),
), ),

View file

@ -21,7 +21,8 @@ class BaseTextFormField extends StatelessWidget {
this.enabled = true, this.enabled = true,
this.validator, this.validator,
this.textStyle, this.textStyle,
this.placeholderTextStyle}); this.placeholderTextStyle,
this.maxLength});
final TextEditingController controller; final TextEditingController controller;
final TextInputType keyboardType; final TextInputType keyboardType;
@ -42,6 +43,7 @@ class BaseTextFormField extends StatelessWidget {
final FormFieldValidator<String> validator; final FormFieldValidator<String> validator;
final TextStyle placeholderTextStyle; final TextStyle placeholderTextStyle;
final TextStyle textStyle; final TextStyle textStyle;
final int maxLength;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -54,6 +56,7 @@ class BaseTextFormField extends StatelessWidget {
maxLines: maxLines, maxLines: maxLines,
inputFormatters: inputFormatters, inputFormatters: inputFormatters,
enabled: enabled, enabled: enabled,
maxLength: maxLength,
style: textStyle ?? TextStyle( style: textStyle ?? TextStyle(
fontSize: 16.0, fontSize: 16.0,
color: textColor ?? Theme.of(context).primaryTextTheme.title.color), color: textColor ?? Theme.of(context).primaryTextTheme.title.color),

View file

@ -0,0 +1,81 @@
import 'dart:ui';
import 'package:cake_wallet/palette.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class CheckboxWidget extends StatefulWidget {
CheckboxWidget({
@required this.value,
@required this.caption,
@required this.onChanged});
final bool value;
final String caption;
final Function(bool) onChanged;
@override
CheckboxWidgetState createState() => CheckboxWidgetState(value, caption, onChanged);
}
class CheckboxWidgetState extends State<CheckboxWidget> {
CheckboxWidgetState(this.value, this.caption, this.onChanged);
bool value;
String caption;
Function(bool) onChanged;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
value = !value;
onChanged(value);
setState(() {});
},
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Container(
height: 16,
width: 16,
decoration: BoxDecoration(
color: value
? Palette.blueCraiola
: Theme.of(context).accentTextTheme.subhead.decorationColor,
borderRadius: BorderRadius.all(Radius.circular(2)),
border: Border.all(
color: value
? Palette.blueCraiola
: Theme.of(context).accentTextTheme.overline.color,
width: 1
)
),
child: value
? Center(
child: Icon(
Icons.done,
color: Colors.white,
size: 14,
),
)
: Offstage(),
),
Padding(
padding: EdgeInsets.only(left: 16),
child: Text(
caption,
style: TextStyle(
color: Theme.of(context).primaryTextTheme.title.color,
fontSize: 18,
fontFamily: 'Poppins',
fontWeight: FontWeight.w500,
decoration: TextDecoration.none
),
),
)
],
),
);
}
}

View file

@ -1,15 +1,20 @@
import 'dart:ui'; import 'dart:ui';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:cake_wallet/src/widgets/alert_background.dart';
import 'package:cake_wallet/src/widgets/cake_scrollbar.dart';
import 'package:cake_wallet/src/widgets/alert_close_button.dart';
import 'package:cake_wallet/palette.dart'; import 'package:cake_wallet/palette.dart';
class Picker<Item extends Object> extends StatelessWidget { class Picker<Item extends Object> extends StatefulWidget {
Picker({ Picker({
@required this.selectedAtIndex, @required this.selectedAtIndex,
@required this.items, @required this.items,
this.images, this.images,
@required this.title, @required this.title,
@required this.onItemSelected, @required this.onItemSelected,
this.mainAxisAlignment = MainAxisAlignment.start this.mainAxisAlignment = MainAxisAlignment.start,
this.isAlwaysShowScrollThumb = false
}); });
final int selectedAtIndex; final int selectedAtIndex;
@ -18,59 +23,87 @@ class Picker<Item extends Object> extends StatelessWidget {
final String title; final String title;
final Function(Item) onItemSelected; final Function(Item) onItemSelected;
final MainAxisAlignment mainAxisAlignment; final MainAxisAlignment mainAxisAlignment;
final bool isAlwaysShowScrollThumb;
@override
PickerState createState() => PickerState<Item>(items, images, onItemSelected);
}
class PickerState<Item> extends State<Picker> {
PickerState(this.items, this.images, this.onItemSelected);
final Function(Item) onItemSelected;
final List<Item> items;
final List<Image> images;
final closeButton = Image.asset('assets/images/close.png',
color: Palette.darkBlueCraiola,
);
ScrollController controller = ScrollController();
final double backgroundHeight = 193;
final double thumbHeight = 72;
double fromTop = 0;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GestureDetector( controller.addListener(() {
onTap: () => Navigator.of(context).pop(), fromTop = controller.hasClients
child: Container( ? (controller.offset / controller.position.maxScrollExtent * (backgroundHeight - thumbHeight))
color: Colors.transparent, : 0;
child: BackdropFilter( setState(() {});
filter: ImageFilter.blur(sigmaX: 3.0, sigmaY: 3.0), });
child: Container(
decoration: BoxDecoration(color: PaletteDark.darkNightBlue.withOpacity(0.75)), return AlertBackground(
child: Center( child: Stack(
child: Column( alignment: Alignment.center,
mainAxisSize: MainAxisSize.min, children: <Widget>[
children: <Widget>[ Column(
Container( mainAxisSize: MainAxisSize.min,
padding: EdgeInsets.only(left: 24, right: 24), children: <Widget>[
child: Text( Container(
title, padding: EdgeInsets.only(left: 24, right: 24),
textAlign: TextAlign.center, child: Text(
style: TextStyle( widget.title,
fontSize: 18, textAlign: TextAlign.center,
fontWeight: FontWeight.bold, style: TextStyle(
decoration: TextDecoration.none, fontSize: 18,
color: Colors.white fontFamily: 'Poppins',
), fontWeight: FontWeight.bold,
), decoration: TextDecoration.none,
color: Colors.white
), ),
Padding( ),
padding: EdgeInsets.only(left: 24, right: 24, top: 24), ),
child: GestureDetector( Padding(
onTap: () => null, padding: EdgeInsets.only(left: 24, right: 24, top: 24),
child: ClipRRect( child: GestureDetector(
borderRadius: BorderRadius.all(Radius.circular(14)), onTap: () => null,
child: Container( child: ClipRRect(
height: 233, borderRadius: BorderRadius.all(Radius.circular(14)),
color: Theme.of(context).accentTextTheme.title.backgroundColor, child: Container(
child: ListView.separated( height: 233,
color: Theme.of(context).accentTextTheme.title.color,
child: Stack(
alignment: Alignment.center,
children: <Widget>[
ListView.separated(
controller: controller,
separatorBuilder: (context, index) => Divider( separatorBuilder: (context, index) => Divider(
color: Theme.of(context).dividerColor, color: Theme.of(context).accentTextTheme.title.backgroundColor,
height: 1, height: 1,
), ),
itemCount: items == null ? 0 : items.length, itemCount: items == null ? 0 : items.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final item = items[index]; final item = items[index];
final image = images != null? images[index] : null; final image = images != null? images[index] : null;
final isItemSelected = index == selectedAtIndex; final isItemSelected = index == widget.selectedAtIndex;
final color = isItemSelected final color = isItemSelected
? Theme.of(context).accentTextTheme.subtitle.decorationColor ? Theme.of(context).textTheme.body2.color
: Colors.transparent; : Theme.of(context).accentTextTheme.title.color;
final textColor = isItemSelected final textColor = isItemSelected
? Colors.blue ? Palette.blueCraiola
: Theme.of(context).primaryTextTheme.title.color; : Theme.of(context).primaryTextTheme.title.color;
return GestureDetector( return GestureDetector(
@ -87,21 +120,22 @@ class Picker<Item extends Object> extends StatelessWidget {
color: color, color: color,
child: Row( child: Row(
mainAxisSize: MainAxisSize.max, mainAxisSize: MainAxisSize.max,
mainAxisAlignment: mainAxisAlignment, mainAxisAlignment: widget.mainAxisAlignment,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[ children: <Widget>[
image != null image != null
? image ? image
: Offstage(), : Offstage(),
Padding( Padding(
padding: EdgeInsets.only( padding: EdgeInsets.only(
left: image != null ? 12 : 0 left: image != null ? 12 : 0
), ),
child: Text( child: Text(
item.toString(), item.toString(),
style: TextStyle( style: TextStyle(
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.bold, fontFamily: 'Poppins',
fontWeight: FontWeight.w600,
color: textColor, color: textColor,
decoration: TextDecoration.none, decoration: TextDecoration.none,
), ),
@ -112,17 +146,25 @@ class Picker<Item extends Object> extends StatelessWidget {
), ),
); );
}, },
),
widget.isAlwaysShowScrollThumb
? CakeScrollbar(
backgroundHeight: backgroundHeight,
thumbHeight: thumbHeight,
fromTop: fromTop
) )
), : Offstage(),
), ],
)
), ),
) ),
], ),
), )
) ],
), ),
), AlertCloseButton(image: closeButton)
), ],
)
); );
} }
} }

View file

@ -1,3 +1,4 @@
import 'package:cake_wallet/palette.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -39,7 +40,7 @@ class StandardListRow extends StatelessWidget {
Text(title, Text(title,
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w500, fontWeight: FontWeight.normal,
color: _titleColor(context))) color: _titleColor(context)))
])); ]));
} }
@ -47,21 +48,24 @@ class StandardListRow extends StatelessWidget {
Widget buildTrailing(BuildContext context) => null; Widget buildTrailing(BuildContext context) => null;
Color _titleColor(BuildContext context) => isSelected Color _titleColor(BuildContext context) => isSelected
? Color.fromRGBO(20, 200, 71, 1) ? Palette.blueCraiola
: Theme.of(context).primaryTextTheme.title.color; : Theme.of(context).primaryTextTheme.title.color;
Color _backgroundColor(BuildContext context) { Color _backgroundColor(BuildContext context) {
// return Theme.of(context).accentTextTheme.subtitle.decorationColor; // return Theme.of(context).accentTextTheme.subtitle.decorationColor;
return Theme.of(context).accentTextTheme.title.backgroundColor; return Theme.of(context).backgroundColor;
} }
} }
class SectionHeaderListRow extends StatelessWidget { class SectionHeaderListRow extends StatelessWidget {
@override @override
Widget build(BuildContext context) => Column(children: [ Widget build(BuildContext context) => Column(children: [
StandardListSeparator(), StandardListSeparator(padding: EdgeInsets.only(left: 24)),
Container(width: double.infinity, height: 40, color: Colors.white), Container(
StandardListSeparator() width: double.infinity,
height: 40,
color: Theme.of(context).backgroundColor),
//StandardListSeparator(padding: EdgeInsets.only(left: 24))
]); ]);
} }
@ -75,8 +79,10 @@ class StandardListSeparator extends StatelessWidget {
return Container( return Container(
height: 1, height: 1,
padding: padding, padding: padding,
color: Theme.of(context).accentTextTheme.title.backgroundColor, color: Theme.of(context).backgroundColor,
child: Container(height: 1, color: Color.fromRGBO(219, 227, 243, 1))); child: Container(
height: 1,
color: Theme.of(context).primaryTextTheme.title.backgroundColor));
} }
} }
@ -126,9 +132,9 @@ class SectionStandardList extends StatelessWidget {
final items = <Widget>[]; final items = <Widget>[];
for (var sectionIndex = 0; sectionIndex < sectionCount; sectionIndex++) { for (var sectionIndex = 0; sectionIndex < sectionCount; sectionIndex++) {
if (sectionIndex == 0) { /*if (sectionIndex == 0) {
items.add(StandardListSeparator()); items.add(StandardListSeparator());
} }*/
final itemCount = itemCounter(sectionIndex); final itemCount = itemCounter(sectionIndex);
@ -140,7 +146,7 @@ class SectionStandardList extends StatelessWidget {
items.add(sectionIndex + 1 != sectionCount items.add(sectionIndex + 1 != sectionCount
? SectionHeaderListRow() ? SectionHeaderListRow()
: StandardListSeparator()); : StandardListSeparator(padding: EdgeInsets.only(left: 24)));
} }
return items; return items;

View file

@ -1,13 +1,20 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class StandartListRow extends StatelessWidget { class StandartListRow extends StatelessWidget {
StandartListRow( StandartListRow(
{this.title, {this.title,
this.value, this.value,
this.titleFontSize = 14,
this.valueFontSize = 16,
this.image,
this.isDrawBottom = false}); this.isDrawBottom = false});
final String title; final String title;
final String value; final String value;
final double titleFontSize;
final double valueFontSize;
final Image image;
final bool isDrawBottom; final bool isDrawBottom;
@override @override
@ -19,27 +26,42 @@ class StandartListRow extends StatelessWidget {
color: Theme.of(context).backgroundColor, color: Theme.of(context).backgroundColor,
child: Padding( child: Padding(
padding: padding:
const EdgeInsets.only(left: 24, top: 16, bottom: 16, right: 24), const EdgeInsets.only(left: 24, top: 16, bottom: 16, right: 24),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
Text(title, Text(title,
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: titleFontSize,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: color:
Theme.of(context).primaryTextTheme.overline.color), Theme.of(context).primaryTextTheme.overline.color),
textAlign: TextAlign.left), textAlign: TextAlign.left),
Padding( Padding(
padding: const EdgeInsets.only(top: 12), padding: const EdgeInsets.only(top: 12),
child: Text(value, child: Row(
style: TextStyle( mainAxisSize: MainAxisSize.max,
fontSize: 16, mainAxisAlignment: MainAxisAlignment.spaceBetween,
fontWeight: FontWeight.w500, crossAxisAlignment: CrossAxisAlignment.start,
color: Theme.of(context) children: <Widget>[
.primaryTextTheme Expanded(
.title child: Text(value,
.color)), style: TextStyle(
fontSize: valueFontSize,
fontWeight: FontWeight.w500,
color: Theme.of(context)
.primaryTextTheme
.title
.color)),
),
image != null
? Padding(
padding: EdgeInsets.only(left: 24),
child: image,
)
: Offstage()
],
),
) )
]), ]),
), ),

View file

@ -47,8 +47,7 @@ class TemplateTileState extends State<TemplateTile> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
//final color = isRemovable ? Colors.white : Theme.of(context).primaryTextTheme.title.color; final color = isRemovable ? Colors.white : Theme.of(context).primaryTextTheme.title.color;
final color = Colors.white;
final toIcon = Image.asset('assets/images/to_icon.png', color: color); final toIcon = Image.asset('assets/images/to_icon.png', color: color);
final content = Row( final content = Row(
@ -106,7 +105,7 @@ class TemplateTileState extends State<TemplateTile> {
child: Container( child: Container(
height: 40, height: 40,
padding: EdgeInsets.only(left: 24, right: 24), padding: EdgeInsets.only(left: 24, right: 24),
color: PaletteDark.darkVioletBlue, color: Theme.of(context).primaryTextTheme.display3.decorationColor,
child: content, child: content,
), ),
), ),

View file

@ -2,25 +2,28 @@ import 'package:flutter/material.dart';
class TopPanel extends StatefulWidget { class TopPanel extends StatefulWidget {
TopPanel({ TopPanel({
@required this.color,
@required this.widget, @required this.widget,
this.edgeInsets = const EdgeInsets.all(24) this.edgeInsets = const EdgeInsets.all(24),
this.color,
this.gradient
}); });
final Color color; final Color color;
final Widget widget; final Widget widget;
final EdgeInsets edgeInsets; final EdgeInsets edgeInsets;
final Gradient gradient;
@override @override
TopPanelState createState() => TopPanelState(color, widget, edgeInsets); TopPanelState createState() => TopPanelState(widget, edgeInsets, color, gradient);
} }
class TopPanelState extends State<TopPanel> { class TopPanelState extends State<TopPanel> {
TopPanelState(this._color, this._widget, this._edgeInsets); TopPanelState(this._widget, this._edgeInsets, this._color, this._gradient);
final Color _color; final Color _color;
final Widget _widget; final Widget _widget;
final EdgeInsets _edgeInsets; final EdgeInsets _edgeInsets;
final Gradient _gradient;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -32,7 +35,8 @@ class TopPanelState extends State<TopPanel> {
bottomLeft: Radius.circular(24), bottomLeft: Radius.circular(24),
bottomRight: Radius.circular(24) bottomRight: Radius.circular(24)
), ),
color: _color color: _color,
gradient: _gradient
), ),
child: _widget, child: _widget,
); );

View file

@ -1,5 +1,4 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:cake_wallet/palette.dart';
class TrailButton extends StatelessWidget { class TrailButton extends StatelessWidget {
TrailButton({ TrailButton({
@ -22,7 +21,7 @@ class TrailButton extends StatelessWidget {
child: Text( child: Text(
caption, caption,
style: TextStyle( style: TextStyle(
color: PaletteDark.lightBlueGrey, color: Theme.of(context).textTheme.subhead.decorationColor,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
fontSize: 14), fontSize: 14),
), ),

View file

@ -11,8 +11,7 @@ abstract class TradeFilterStoreBase with Store {
TradeFilterStoreBase( TradeFilterStoreBase(
{this.displayXMRTO = true, {this.displayXMRTO = true,
this.displayChangeNow = true, this.displayChangeNow = true,
this.displayMorphToken = true, this.displayMorphToken = true});
this.wallet});
@observable @observable
bool displayXMRTO; bool displayXMRTO;
@ -23,8 +22,6 @@ abstract class TradeFilterStoreBase with Store {
@observable @observable
bool displayMorphToken; bool displayMorphToken;
WalletBase wallet;
@action @action
void toggleDisplayExchange(ExchangeProviderDescription provider) { void toggleDisplayExchange(ExchangeProviderDescription provider) {
switch (provider) { switch (provider) {
@ -40,13 +37,13 @@ abstract class TradeFilterStoreBase with Store {
} }
} }
List<TradeListItem> filtered({List<TradeListItem> trades}) { List<TradeListItem> filtered({List<TradeListItem> trades, WalletBase wallet}) {
final _trades = final _trades =
trades.where((item) => item.trade.walletId == wallet.id).toList(); trades.where((item) => item.trade.walletId == wallet.id).toList();
final needToFilter = !displayChangeNow || !displayXMRTO || !displayMorphToken; final needToFilter = !displayChangeNow || !displayXMRTO || !displayMorphToken;
return needToFilter return needToFilter
? trades ? _trades
.where((item) => .where((item) =>
(displayXMRTO && (displayXMRTO &&
item.trade.provider == ExchangeProviderDescription.xmrto) || item.trade.provider == ExchangeProviderDescription.xmrto) ||

View file

@ -27,6 +27,12 @@ abstract class TradesStoreBase with Store {
@observable @observable
List<TradeListItem> trades; List<TradeListItem> trades;
@observable
Trade trade;
@action
void setTrade(Trade trade) => this.trade = trade;
@action @action
Future updateTradeList() async => trades = Future updateTradeList() async => trades =
tradesSource.values.map((trade) => TradeListItem( tradesSource.values.map((trade) => TradeListItem(

View file

@ -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,13 @@ abstract class SettingsStoreBase with Store {
languageCode = initialLanguageCode; languageCode = initialLanguageCode;
currentLocale = initialCurrentLocale; currentLocale = initialCurrentLocale;
itemHeaders = {}; itemHeaders = {};
// actionlistDisplayMode.observe(
// (dynamic _) => _sharedPreferences.setInt(displayActionListModeKey,
// serializeActionlistDisplayModes(actionlistDisplayMode)),
// fireImmediately: false);
_sharedPreferences = sharedPreferences; _sharedPreferences = sharedPreferences;
_nodeSource = nodeSource; _nodeSource = nodeSource;
_nodes = nodes;
} }
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,8 +62,8 @@ 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 // @observable
Node node; // Node node;
@observable @observable
FiatCurrency fiatCurrency; FiatCurrency fiatCurrency;
@ -103,6 +101,26 @@ abstract class SettingsStoreBase with Store {
SharedPreferences _sharedPreferences; SharedPreferences _sharedPreferences;
Box<Node> _nodeSource; Box<Node> _nodeSource;
Map<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 +150,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,

View file

@ -0,0 +1,13 @@
import 'package:cake_wallet/theme_changer.dart';
import 'package:mobx/mobx.dart';
part 'theme_changer_store.g.dart';
class ThemeChangerStore = ThemeChangerStoreBase with _$ThemeChangerStore;
abstract class ThemeChangerStoreBase with Store {
ThemeChangerStoreBase({this.themeChanger});
@observable
ThemeChanger themeChanger;
}

View file

@ -80,61 +80,92 @@ class Themes {
color: Palette.darkGray, // transaction/trade details titles color: Palette.darkGray, // transaction/trade details titles
decorationColor: Colors.white.withOpacity(0.5), // placeholder decorationColor: Colors.white.withOpacity(0.5), // placeholder
), ),
subhead: TextStyle( subhead: TextStyle(
color: Colors.white.withOpacity(0.5) // send, exchange, buy buttons on dashboard page color: Palette.blueCraiola, // first gradient color (send page)
decorationColor: Palette.pinkFlamingo // second gradient color (send page)
), ),
headline: TextStyle( headline: TextStyle(
color: Palette.lightBlueGrey // historyPanelText color: Colors.white.withOpacity(0.5), // text field border color (send page)
decorationColor: Colors.white.withOpacity(0.5), // text field hint color (send page)
), ),
display1: TextStyle( display1: TextStyle(
color: Colors.white // menuList color: Colors.white.withOpacity(0.2), // text field button color (send page)
decorationColor: Colors.white // text field button icon color (send page)
), ),
display2: TextStyle( display2: TextStyle(
color: Palette.lavender // menuHeader color: Colors.white.withOpacity(0.5), // estimated fee (send page)
decorationColor: Palette.shadowWhite // template dotted border (send page)
), ),
display3: TextStyle( display3: TextStyle(
color: Palette.lavender // historyPanelButton color: Palette.darkBlueCraiola, // template new text (send page)
decorationColor: Palette.shadowWhite // template background color (send page)
), ),
display4: TextStyle( display4: TextStyle(
color: Palette.oceanBlue // QR code color: Palette.darkBlueCraiola, // template title (send page)
decorationColor: Palette.niagara // receive amount text (exchange page)
),
subtitle: TextStyle(
color: Palette.blueCraiola, // first gradient color top panel (exchange page)
decorationColor: Palette.pinkFlamingo // second gradient color top panel (exchange page)
),
body1: TextStyle(
color: Palette.blueCraiola.withOpacity(0.7), // first gradient color bottom panel (exchange page)
decorationColor: Palette.pinkFlamingo.withOpacity(0.7) // second gradient color bottom panel (exchange page)
),
body2: TextStyle(
color: Colors.white.withOpacity(0.5), // text field border on top panel (exchange page)
decorationColor: Colors.white.withOpacity(0.5), // text field border on bottom panel (exchange page)
)
),
focusColor: Colors.white.withOpacity(0.2), // text field button (exchange page)
accentTextTheme: TextTheme(
title: TextStyle(
color: Colors.white, // picker background
backgroundColor: Palette.periwinkleCraiola, // picker divider
decorationColor: Colors.white // dialog background
),
caption: TextStyle(
color: Palette.moderateLavender, // container (confirm exchange)
backgroundColor: Palette.moderateLavender, // button background (confirm exchange)
decorationColor: Palette.darkBlueCraiola, // text color (information page)
),
subtitle: TextStyle(
color: Palette.darkBlueCraiola, // QR code (exchange trade page)
backgroundColor: Palette.wildPeriwinkle, // divider (exchange trade page)
decorationColor: Palette.blueCraiola // crete new wallet button background (wallet list page)
),
headline: TextStyle(
color: Palette.moderateLavender, // first gradient color of wallet action buttons (wallet list page)
backgroundColor: Palette.moderateLavender, // second gradient color of wallet action buttons (wallet list page)
decorationColor: Colors.white // restore wallet button text color (wallet list page)
),
subhead: TextStyle(
color: Palette.darkGray, // titles color (filter widget)
backgroundColor: Palette.periwinkle, // divider color (filter widget)
decorationColor: Colors.white // checkbox background (filter widget)
),
overline: TextStyle(
color: Palette.wildPeriwinkle, // checkbox bounds (filter widget)
decorationColor: Colors.white, // menu subname
),
display1: TextStyle(
color: Palette.blueCraiola, // first gradient color (menu header)
decorationColor: Palette.pinkFlamingo // second gradient color(menu header)
),
display2: TextStyle(
color: Palette.shadowWhite, // action button color (address text field)
), ),
), ),
focusColor: Colors.white, // wallet card border
cardColor: Palette.blueAlice, cardColor: Palette.blueAlice,
cardTheme: CardTheme( cardTheme: CardTheme(
color: Colors.white, // synced card start color: Colors.white, // synced card start
), ),
accentTextTheme: TextTheme(
title: TextStyle(
color: Palette.darkLavender, // top panel
backgroundColor: Palette.lavender, // bottom panel
decorationColor: PaletteDark.distantBlue // select button background color
),
caption: TextStyle(
color: Palette.blue, // current wallet label
backgroundColor: Colors.white, // gradient start, wallet label
decorationColor: Palette.lavender, // gradient end, wallet label
),
subtitle: TextStyle(
color: Palette.lightBlueGrey, // border color, wallet label
backgroundColor: Palette.lavender, // address field, wallet card
decorationColor: Palette.darkLavender // selected item
),
headline: TextStyle(
color: Palette.darkLavender, // faq background
backgroundColor: Palette.lavender // faq extension
)
),
); );
@ -182,7 +213,7 @@ class Themes {
decorationColor: PaletteDark.nightBlue // background of tiles (receive page) decorationColor: PaletteDark.nightBlue // background of tiles (receive page)
), ),
display3: TextStyle( display3: TextStyle(
color: Colors.blue, // text color of current tile (receive page) color: Palette.blueCraiola, // text color of current tile (receive page)
decorationColor: PaletteDark.lightOceanBlue // background of current tile (receive page) decorationColor: PaletteDark.lightOceanBlue // background of current tile (receive page)
), ),
display4: TextStyle( display4: TextStyle(
@ -215,32 +246,85 @@ class Themes {
color: PaletteDark.lightBlueGrey, // transaction/trade details titles color: PaletteDark.lightBlueGrey, // transaction/trade details titles
decorationColor: Colors.grey, // placeholder decorationColor: Colors.grey, // placeholder
), ),
subhead: TextStyle( subhead: TextStyle(
color: PaletteDark.lightDistantBlue // send, exchange, buy buttons on dashboard page color: PaletteDark.darkNightBlue, // first gradient color (send page)
decorationColor: PaletteDark.darkNightBlue // second gradient color (send page)
), ),
headline: TextStyle( headline: TextStyle(
color: PaletteDark.pigeonBlue // historyPanelText color: PaletteDark.lightVioletBlue, // text field border color (send page)
decorationColor: PaletteDark.darkCyanBlue, // text field hint color (send page)
), ),
display1: TextStyle( display1: TextStyle(
color: PaletteDark.lightNightBlue // menuList color: PaletteDark.buttonNightBlue, // text field button color (send page)
decorationColor: PaletteDark.gray // text field button icon color (send page)
), ),
display2: TextStyle( display2: TextStyle(
color: PaletteDark.headerNightBlue // menuHeader color: Colors.white, // estimated fee (send page)
decorationColor: PaletteDark.darkCyanBlue // template dotted border (send page)
), ),
display3: TextStyle( display3: TextStyle(
color: PaletteDark.moderateNightBlue // historyPanelButton color: PaletteDark.darkCyanBlue, // template new text (send page)
decorationColor: PaletteDark.darkVioletBlue // template background color (send page)
), ),
display4: TextStyle( display4: TextStyle(
color: PaletteDark.gray // QR code color: PaletteDark.cyanBlue, // template title (send page)
decorationColor: PaletteDark.darkCyanBlue // receive amount text (exchange page)
),
subtitle: TextStyle(
color: PaletteDark.wildVioletBlue, // first gradient color top panel (exchange page)
decorationColor: PaletteDark.wildVioletBlue // second gradient color top panel (exchange page)
),
body1: TextStyle(
color: PaletteDark.darkNightBlue, // first gradient color bottom panel (exchange page)
decorationColor: PaletteDark.darkNightBlue // second gradient color bottom panel (exchange page)
),
body2: TextStyle(
color: PaletteDark.blueGrey, // text field border on top panel (exchange page)
decorationColor: PaletteDark.moderateVioletBlue, // text field border on bottom panel (exchange page)
)
),
focusColor: PaletteDark.moderateBlue, // text field button (exchange page)
accentTextTheme: TextTheme(
title: TextStyle(
color: PaletteDark.nightBlue, // picker background
backgroundColor: PaletteDark.dividerColor, // picker divider
decorationColor: PaletteDark.darkNightBlue // dialog background
),
caption: TextStyle(
color: PaletteDark.nightBlue, // container (confirm exchange)
backgroundColor: PaletteDark.deepVioletBlue, // button background (confirm exchange)
decorationColor: Palette.darkLavender, // text color (information page)
),
subtitle: TextStyle(
color: PaletteDark.lightBlueGrey, // QR code (exchange trade page)
backgroundColor: PaletteDark.deepVioletBlue, // divider (exchange trade page)
decorationColor: Colors.white // crete new wallet button background (wallet list page)
),
headline: TextStyle(
color: PaletteDark.distantBlue, // first gradient color of wallet action buttons (wallet list page)
backgroundColor: PaletteDark.distantNightBlue, // second gradient color of wallet action buttons (wallet list page)
decorationColor: Palette.darkBlueCraiola // restore wallet button text color (wallet list page)
),
subhead: TextStyle(
color: Colors.white, // titles color (filter widget)
backgroundColor: PaletteDark.darkOceanBlue, // divider color (filter widget)
decorationColor: PaletteDark.wildVioletBlue.withOpacity(0.3) // checkbox background (filter widget)
),
overline: TextStyle(
color: PaletteDark.wildVioletBlue, // checkbox bounds (filter widget)
decorationColor: PaletteDark.darkCyanBlue, // menu subname
),
display1: TextStyle(
color: PaletteDark.deepPurpleBlue, // first gradient color (menu header)
decorationColor: PaletteDark.deepPurpleBlue // second gradient color(menu header)
),
display2: TextStyle(
color: PaletteDark.nightBlue, // action button color (address text field)
), ),
), ),
focusColor: PaletteDark.lightDistantBlue, // wallet card border
cardColor: PaletteDark.darkNightBlue, cardColor: PaletteDark.darkNightBlue,
cardTheme: CardTheme( cardTheme: CardTheme(
color: PaletteDark.moderateBlue, // synced card start color: PaletteDark.moderateBlue, // synced card start
@ -248,27 +332,7 @@ class Themes {
accentTextTheme: TextTheme(
title: TextStyle(
color: PaletteDark.moderateBlue, // top panel
backgroundColor: PaletteDark.lightNightBlue, // bottom panel
decorationColor: Colors.white // select button background color
),
caption: TextStyle(
color: Colors.white, // current wallet label
backgroundColor: PaletteDark.distantBlue, // gradient start, wallet label
decorationColor: PaletteDark.nightBlue, // gradient end, wallet label
),
subtitle: TextStyle(
color: PaletteDark.darkNightBlue, // border color, wallet label
backgroundColor: PaletteDark.violetBlue, // address field, wallet card
decorationColor: PaletteDark.headerNightBlue // selected item
),
headline: TextStyle(
color: PaletteDark.lightNightBlue, // faq background
backgroundColor: PaletteDark.headerNightBlue // faq extension
)
),
); );
} }

46
lib/utils/mobx.dart Normal file
View file

@ -0,0 +1,46 @@
import 'package:mobx/mobx.dart';
void connectDifferent<T, Y>(
ObservableList<T> source, ObservableList<Y> dest, Y Function(T) transform,
{bool Function(T) filter}) {
source.observe((ListChange<T> change) {
// switch (change.type) {
// case OperationType.add:
// final _values = change.added;
// Iterable<T> values;
// if (filter != null) {
// values = _values.where(filter);
// }
// dest.addAll(values.map((e) => transform(e)));
// break;
// case OperationType.remove:
// change.removed.forEach((element) {
// dest.remove(element);
// });
// // dest.removeAt(change.index);
// break;
// case OperationType.update:
// // change.index
// break;
// }
});
}
void connect<T>(ObservableList<T> source, ObservableList<T> dest) {
source.observe((ListChange<T> change) {
// switch (change.type) {
// case OperationType.add:
// dest.addAll(change.added);
// break;
// case OperationType.remove:
// dest.removeAt(change.index);
// break;
// case OperationType.update:
// // change.index
// break;
// }
});
}

View file

@ -16,7 +16,7 @@ abstract class ContactViewModelBase with Store {
_contact = contact { _contact = contact {
name = _contact?.name; name = _contact?.name;
address = _contact?.address; address = _contact?.address;
currency = _contact?.type ?? _wallet.currency; currency = _contact?.type; //_wallet.currency;
} }
@observable @observable
@ -33,7 +33,8 @@ abstract class ContactViewModelBase with Store {
@computed @computed
bool get isReady => bool get isReady =>
(name?.isNotEmpty ?? false) && (address?.isNotEmpty ?? false); (name?.isNotEmpty ?? false) && (currency?.toString()?.isNotEmpty ?? false)
&& (address?.isNotEmpty ?? false);
final List<CryptoCurrency> currencies; final List<CryptoCurrency> currencies;
final ContactService _contactService; final ContactService _contactService;
@ -44,7 +45,8 @@ abstract class ContactViewModelBase with Store {
void reset() { void reset() {
address = ''; address = '';
name = ''; name = '';
currency = _wallet.currency; //currency = _wallet.currency;
currency = null;
} }
Future save() async { Future save() async {

View file

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

View file

@ -8,6 +8,7 @@ import 'package:cake_wallet/src/domain/common/transaction_info.dart';
import 'package:cake_wallet/src/domain/exchange/exchange_provider_description.dart'; import 'package:cake_wallet/src/domain/exchange/exchange_provider_description.dart';
import 'package:cake_wallet/src/domain/exchange/trade.dart'; import 'package:cake_wallet/src/domain/exchange/trade.dart';
import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart'; import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart';
import 'package:cake_wallet/view_model/dashboard/filter_item.dart';
import 'package:cake_wallet/view_model/dashboard/trade_list_item.dart'; import 'package:cake_wallet/view_model/dashboard/trade_list_item.dart';
import 'package:cake_wallet/view_model/dashboard/transaction_list_item.dart'; import 'package:cake_wallet/view_model/dashboard/transaction_list_item.dart';
import 'package:cake_wallet/view_model/dashboard/action_list_item.dart'; import 'package:cake_wallet/view_model/dashboard/action_list_item.dart';
@ -28,23 +29,57 @@ 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 = {
S.current.transactions: [
FilterItem(
value: transactionFilterStore.displayIncoming,
caption: S.current.incoming,
onChanged: (value) => transactionFilterStore.toggleIncoming()),
FilterItem(
value: transactionFilterStore.displayOutgoing,
caption: S.current.outgoing,
onChanged: (value) => transactionFilterStore.toggleOutgoing()),
FilterItem(
value: false,
caption: S.current.transactions_by_date,
onChanged: null),
],
S.current.trades: [
FilterItem(
value: tradeFilterStore.displayXMRTO,
caption: 'XMR.TO',
onChanged: (value) => tradeFilterStore
.toggleDisplayExchange(ExchangeProviderDescription.xmrto)),
FilterItem(
value: tradeFilterStore.displayChangeNow,
caption: 'Change.NOW',
onChanged: (value) => tradeFilterStore
.toggleDisplayExchange(ExchangeProviderDescription.changeNow)),
FilterItem(
value: tradeFilterStore.displayMorphToken,
caption: 'MorphToken',
onChanged: (value) => tradeFilterStore
.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,
fiatCurrency: appStore.settingsStore.fiatCurrency, fiatCurrency: appStore.settingsStore.fiatCurrency,
displayMode: balanceDisplayMode))); displayMode: balanceDisplayMode)));
_reaction = reaction((_) => appStore.wallet, _onWalletChange); _reaction = reaction((_) => appStore.wallet, _onWalletChange);
@ -78,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;
@ -106,9 +137,8 @@ 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));
return formattedItemsList(_items); return formattedItemsList(_items);
} }
@ -125,16 +155,23 @@ abstract class DashboardViewModelBase with Store {
TransactionFilterStore transactionFilterStore; TransactionFilterStore transactionFilterStore;
Map<String, List<FilterItem>> filterItems;
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,
displayMode: balanceDisplayMode))); displayMode: balanceDisplayMode)));
} }
} }

View file

@ -0,0 +1,7 @@
class FilterItem {
FilterItem({this.value, this.caption, this.onChanged});
bool value;
String caption;
Function(bool) onChanged;
}

View file

@ -0,0 +1,93 @@
import 'dart:async';
import 'package:cake_wallet/core/wallet_base.dart';
import 'package:cake_wallet/src/domain/exchange/changenow/changenow_exchange_provider.dart';
import 'package:cake_wallet/src/domain/exchange/exchange_provider.dart';
import 'package:cake_wallet/src/domain/exchange/exchange_provider_description.dart';
import 'package:cake_wallet/src/domain/exchange/morphtoken/morphtoken_exchange_provider.dart';
import 'package:cake_wallet/src/domain/exchange/trade.dart';
import 'package:cake_wallet/src/domain/exchange/xmrto/xmrto_exchange_provider.dart';
import 'package:cake_wallet/store/dashboard/trades_store.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/src/screens/exchange_trade/exchange_trade_item.dart';
import 'package:cake_wallet/generated/i18n.dart';
part 'exchange_trade_view_model.g.dart';
class ExchangeTradeViewModel = ExchangeTradeViewModelBase with _$ExchangeTradeViewModel;
abstract class ExchangeTradeViewModelBase with Store {
ExchangeTradeViewModelBase({this.wallet, this.trades, this.tradesStore}) {
trade = tradesStore.trade;
isSendable = trade.from == wallet.currency ||
trade.provider == ExchangeProviderDescription.xmrto;
switch (trade.provider) {
case ExchangeProviderDescription.xmrto:
_provider = XMRTOExchangeProvider();
break;
case ExchangeProviderDescription.changeNow:
_provider = ChangeNowExchangeProvider();
break;
case ExchangeProviderDescription.morphToken:
_provider = MorphTokenExchangeProvider(trades: trades);
break;
}
items = ObservableList<ExchangeTradeItem>();
items.addAll([
ExchangeTradeItem(
title: S.current.id,
data: '${trade.id}',
isCopied: true),
ExchangeTradeItem(
title: S.current.amount,
data: '${trade.amount}',
isCopied: false),
ExchangeTradeItem(
title: S.current.status,
data: '${trade.state}',
isCopied: false),
ExchangeTradeItem(
title: S.current.widgets_address + ':',
data: trade.inputAddress,
isCopied: true),
]);
_updateTrade();
_timer = Timer.periodic(Duration(seconds: 20), (_) async => _updateTrade());
}
final WalletBase wallet;
final Box<Trade> trades;
final TradesStore tradesStore;
@observable
Trade trade;
@observable
bool isSendable;
@observable
ObservableList<ExchangeTradeItem> items;
ExchangeProvider _provider;
Timer _timer;
@action
Future<void> _updateTrade() async {
try {
final updatedTrade = await _provider.findTradeById(id: trade.id);
if (updatedTrade.createdAt == null && trade.createdAt != null) {
updatedTrade.createdAt = trade.createdAt;
}
trade = updatedTrade;
} catch (e) {
print(e.toString());
}
}
}

View file

@ -1,9 +1,11 @@
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';
import 'package:cake_wallet/src/stores/exchange/limits_state.dart'; import 'package:cake_wallet/src/stores/exchange/limits_state.dart';
import 'package:cake_wallet/store/dashboard/trades_store.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/generated/i18n.dart';
@ -24,17 +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({this.wallet, this.trades, this.exchangeTemplateStore}) { ExchangeViewModelBase(
{this.wallet,
this.trades,
this.exchangeTemplateStore,
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 = '';
@ -44,12 +47,14 @@ 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();
} }
final WalletBase wallet; final WalletBase wallet;
final Box<Trade> trades; final Box<Trade> trades;
final ExchangeTemplateStore exchangeTemplateStore; final ExchangeTemplateStore exchangeTemplateStore;
final TradesStore tradesStore;
@observable @observable
ExchangeProvider provider; ExchangeProvider provider;
@ -133,8 +138,11 @@ 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);
} }
@ -151,8 +159,11 @@ abstract class ExchangeViewModelBase with Store {
final _amount = double.parse(amount); final _amount = double.parse(amount);
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);
} }
@ -210,16 +221,19 @@ 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(
'${limits.min}', currency.toString())); error: S.current.error_text_minimal_limit('${provider.description}',
'${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(
'${limits.max}', currency.toString())); error: S.current.error_text_maximum_limit('${provider.description}',
'${limits.max}', currency.toString()));
} else { } else {
try { try {
tradeState = TradeIsCreating(); tradeState = TradeIsCreating();
final trade = await provider.createTrade(request: request); final trade = await provider.createTrade(request: request);
trade.walletId = wallet.id; trade.walletId = wallet.id;
tradesStore.setTrade(trade);
await trades.add(trade); await trades.add(trade);
tradeState = TradeIsCreatedSuccessfully(trade: trade); tradeState = TradeIsCreatedSuccessfully(trade: trade);
} catch (e) { } catch (e) {
@ -227,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
@ -253,9 +268,9 @@ abstract class ExchangeViewModelBase with Store {
{CryptoCurrency from, CryptoCurrency to}) { {CryptoCurrency from, CryptoCurrency to}) {
final providers = providerList final providers = providerList
.where((provider) => provider.pairList .where((provider) => provider.pairList
.where((pair) => .where((pair) =>
pair.from == depositCurrency && pair.to == receiveCurrency) pair.from == depositCurrency && pair.to == receiveCurrency)
.isNotEmpty) .isNotEmpty)
.toList(); .toList();
return providers; return providers;
@ -264,12 +279,12 @@ abstract class ExchangeViewModelBase with Store {
void _onPairChange() { void _onPairChange() {
final isPairExist = provider.pairList final isPairExist = provider.pairList
.where((pair) => .where((pair) =>
pair.from == depositCurrency && pair.to == receiveCurrency) pair.from == depositCurrency && pair.to == receiveCurrency)
.isNotEmpty; .isNotEmpty;
if (!isPairExist) { if (!isPairExist) {
final provider = final provider =
_providerForPair(from: depositCurrency, to: receiveCurrency); _providerForPair(from: depositCurrency, to: receiveCurrency);
if (provider != null) { if (provider != null) {
changeProvider(provider: provider); changeProvider(provider: provider);
@ -287,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;
}
}
}

View file

@ -1,26 +1,92 @@
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 { class ItemCell<Item> {
NodeListViewModelBase(this._nodeListStore, this._nodeSource, this._wallet); ItemCell(this.value, {@required this.isSelected});
@computed final Item value;
ObservableList<Node> get nodes => ObservableList<Node>.of( final bool isSelected;
_nodeListStore.nodes.where((node) => node.type == _wallet.type)); }
abstract class NodeListViewModelBase with Store {
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)));
connectDifferent(
_nodeListStore.nodes,
nodes,
(Node val) =>
ItemCell<Node>(val, isSelected: val.key == currentNode.key),
filter: (Node val) {
return val.type == _wallet.type;
});
}
ObservableList<ItemCell<Node>> nodes;
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 _wallet.connectToNode(node: node);
}
Future<void> delete(Node node) async => _nodeSource.delete(node.key);
Future<void> setAsCurrent(Node node) async {
await _wallet.connectToNode(node: node);
await _settingsStore.setCurrentNode(node, _wallet.type);
_updateCurrentNode();
}
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);
}
}
}
} }

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