diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 987e086fc..539756848 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -373,7 +373,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = 9; + CURRENT_PROJECT_VERSION = 12; DEVELOPMENT_TEAM = 32J6BB6VUS; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -509,7 +509,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = 9; + CURRENT_PROJECT_VERSION = 12; DEVELOPMENT_TEAM = 32J6BB6VUS; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -540,7 +540,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = 9; + CURRENT_PROJECT_VERSION = 12; DEVELOPMENT_TEAM = 32J6BB6VUS; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( diff --git a/lib/bitcoin/bitcoin_transaction_info.dart b/lib/bitcoin/bitcoin_transaction_info.dart index d3c56b530..4198ca9e2 100644 --- a/lib/bitcoin/bitcoin_transaction_info.dart +++ b/lib/bitcoin/bitcoin_transaction_info.dart @@ -16,12 +16,13 @@ class BitcoinTransactionInfo extends TransactionInfo { @required TransactionDirection direction, @required bool isPending, @required DateTime date, - @required this.confirmations}) { + @required int confirmations}) { this.height = height; this.amount = amount; this.direction = direction; this.date = date; this.isPending = isPending; + this.confirmations = confirmations; } factory BitcoinTransactionInfo.fromElectrumVerbose(Map obj, @@ -119,7 +120,6 @@ class BitcoinTransactionInfo extends TransactionInfo { } final String id; - int confirmations; String _fiatAmount; diff --git a/lib/bitcoin/bitcoin_wallet.dart b/lib/bitcoin/bitcoin_wallet.dart index 4f67fa59f..b69bccb9b 100644 --- a/lib/bitcoin/bitcoin_wallet.dart +++ b/lib/bitcoin/bitcoin_wallet.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:typed_data'; import 'dart:convert'; import 'package:cake_wallet/bitcoin/bitcoin_transaction_credentials.dart'; @@ -31,9 +32,12 @@ import 'package:cake_wallet/src/domain/common/node.dart'; import 'package:cake_wallet/core/wallet_base.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'; + class BitcoinWallet = BitcoinWalletBase with _$BitcoinWallet; abstract class BitcoinWalletBase extends WalletBase with Store { @@ -217,6 +221,11 @@ abstract class BitcoinWalletBase extends WalletBase with Store { try { syncStatus = ConnectingSyncStatus(); await eclient.connectToUri(node.uri); + eclient.onConnectionStatusChange = (bool isConnected) { + if (!isConnected) { + syncStatus = LostConnectionSyncStatus(); + } + }; syncStatus = ConnectedSyncStatus(); } catch (e) { print(e.toString()); @@ -331,7 +340,6 @@ abstract class BitcoinWalletBase extends WalletBase with Store { await _scripthashesUpdateSubject[sh]?.close(); _scripthashesUpdateSubject[sh] = eclient.scripthashUpdate(sh); _scripthashesUpdateSubject[sh].listen((event) async { - print('event $event'); transactionHistory.updateAsync(); await _updateBalance(); }); diff --git a/lib/bitcoin/electrum.dart b/lib/bitcoin/electrum.dart index d92c69405..5476bb459 100644 --- a/lib/bitcoin/electrum.dart +++ b/lib/bitcoin/electrum.dart @@ -32,6 +32,7 @@ class ElectrumClient { bool get isConnected => _isConnected; Socket socket; + void Function(bool) onConnectionStatusChange; int _id; final Map _tasks; bool _isConnected; @@ -45,50 +46,49 @@ class ElectrumClient { } Future connect({@required String host, @required int port}) async { - await socket?.close(); - final start = DateTime.now(); + try { + await socket?.close(); + } catch (_) {} socket = await SecureSocket.connect(host, port, timeout: connectionTimeout); - - _isConnected = true; - + _setIsConnected(true); socket.listen((List event) { try { final jsoned = json.decode(utf8.decode(event)) as Map; -// print(jsoned); + print(jsoned); final method = jsoned['method']; + final id = jsoned['id'] as String; + final params = jsoned['result']; if (method is String) { _methodHandler(method: method, request: jsoned); return; } - final id = jsoned['id'] as String; - final params = jsoned['result']; - _finish(id, params); } catch (e) { print(e); } - }, onError: (Object error) { - print('ElectrumClient error: ${error.toString()}'); - }, onDone: () { - final end = DateTime.now(); - final diff = end.millisecondsSinceEpoch - start.millisecondsSinceEpoch; - print('On done: $diff'); - }); - - print('Connected to ${socket.remoteAddress}'); + }, + onError: (_) => _setIsConnected(false), + onDone: () => _setIsConnected(false)); keepAlive(); } void keepAlive() { _aliveTimer?.cancel(); // FIXME: Unnamed constant. - _aliveTimer = Timer.periodic(Duration(seconds: 30), (_) async => ping()); + _aliveTimer = Timer.periodic(Duration(seconds: 2), (_) async => ping()); } - Future ping() => call(method: 'server.ping'); + Future ping() async { + try { + await callWithTimeout(method: 'server.ping'); + _setIsConnected(true); + } on RequestFailedTimeoutException catch (_) { + _setIsConnected(false); + } + } Future> version() => call(method: 'server.version').then((dynamic result) { @@ -205,7 +205,6 @@ class ElectrumClient { {@required String transactionRaw}) async => call(method: 'blockchain.transaction.broadcast', params: [transactionRaw]) .then((dynamic result) { - print('result $result'); if (result is String) { return result; } @@ -264,6 +263,25 @@ class ElectrumClient { return completer.future; } + Future callWithTimeout( + {String method, + List params = const [], + int timeout = 2000}) async { + final completer = Completer(); + _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 params = const []}) { _id += 1; socket.write(jsonrpc(method: method, id: _id, params: params)); @@ -280,7 +298,9 @@ class ElectrumClient { return; } - _tasks[id]?.completer?.complete(data); + if (!(_tasks[id]?.completer?.isCompleted ?? false)) { + _tasks[id]?.completer?.complete(data); + } if (!(_tasks[id]?.isSubscription ?? false)) { _tasks[id] = null; @@ -303,4 +323,19 @@ class ElectrumClient { 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; } diff --git a/lib/reactions/bootstrap.dart b/lib/reactions/bootstrap.dart index 76990eb55..6e91c3c50 100644 --- a/lib/reactions/bootstrap.dart +++ b/lib/reactions/bootstrap.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:cake_wallet/core/key_service.dart'; import 'package:cake_wallet/src/domain/common/sync_status.dart'; import 'package:mobx/mobx.dart'; @@ -49,6 +51,7 @@ ReactionDisposer _initialAuthReaction; ReactionDisposer _onCurrentWalletChangeReaction; ReactionDisposer _onWalletSyncStatusChangeReaction; ReactionDisposer _onCurrentFiatCurrencyChangeDisposer; +Timer _reconnectionTimer; Future bootstrap( {FiatConvertationService fiatConvertationService}) async { @@ -74,10 +77,21 @@ Future bootstrap( _onCurrentWalletChangeReaction ??= reaction((_) => getIt.get().wallet, (WalletBase wallet) async { _onWalletSyncStatusChangeReaction?.reaction?.dispose(); + _reconnectionTimer?.cancel(); _onWalletSyncStatusChangeReaction = reaction( (_) => wallet.syncStatus is ConnectedSyncStatus, (_) 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 .get() .setString('current_wallet_name', wallet.name); diff --git a/lib/src/domain/common/sync_status.dart b/lib/src/domain/common/sync_status.dart index 3e2eae4a2..3ee2c6b83 100644 --- a/lib/src/domain/common/sync_status.dart +++ b/lib/src/domain/common/sync_status.dart @@ -73,3 +73,11 @@ class ConnectedSyncStatus extends SyncStatus { @override 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; +} \ No newline at end of file diff --git a/lib/src/domain/common/transaction_info.dart b/lib/src/domain/common/transaction_info.dart index 06a84316d..e5da3bed6 100644 --- a/lib/src/domain/common/transaction_info.dart +++ b/lib/src/domain/common/transaction_info.dart @@ -7,6 +7,7 @@ abstract class TransactionInfo extends Object { bool isPending; DateTime date; int height; + int confirmations; String amountFormatted(); String fiatAmount(); void changeFiatAmount(String amount); diff --git a/lib/src/screens/send/send_page.dart b/lib/src/screens/send/send_page.dart index 8bb4cd2b9..dad4952c2 100644 --- a/lib/src/screens/send/send_page.dart +++ b/lib/src/screens/send/send_page.dart @@ -159,7 +159,8 @@ class SendFormState extends State { decoration: InputDecoration( prefixIcon: Padding( padding: EdgeInsets.only(top: 12), - child: Text('${widget.sendViewModel.currency.toString()}:', + child: Text( + '${widget.sendViewModel.currency.toString()}:', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w500, @@ -213,7 +214,14 @@ class SendFormState extends State { borderSide: BorderSide( color: Theme.of(context).dividerColor, width: 1.0))), - validator: widget.sendViewModel.amountValidator), + validator: (String value) { + if (widget.sendViewModel.all) { + return null; + } + + return widget.sendViewModel.amountValidator + .call(value); + }), ), Padding( padding: const EdgeInsets.only(top: 20), diff --git a/lib/src/screens/transaction_details/transaction_details_page.dart b/lib/src/screens/transaction_details/transaction_details_page.dart index 4351089ed..7b3573e03 100644 --- a/lib/src/screens/transaction_details/transaction_details_page.dart +++ b/lib/src/screens/transaction_details/transaction_details_page.dart @@ -1,3 +1,4 @@ + import 'package:intl/intl.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -48,6 +49,9 @@ class TransactionDetailsPage extends BasePage { StandartListItem( title: S.current.transaction_details_date, value: dateFormat.format(tx.date)), + StandartListItem( + title: 'Confirmations', + value: tx.confirmations?.toString()), StandartListItem( title: S.current.transaction_details_height, value: '${tx.height}'), StandartListItem( diff --git a/lib/view_model/dashboard/dashboard_view_model.dart b/lib/view_model/dashboard/dashboard_view_model.dart index acb0d3d0a..d59792a8d 100644 --- a/lib/view_model/dashboard/dashboard_view_model.dart +++ b/lib/view_model/dashboard/dashboard_view_model.dart @@ -86,7 +86,7 @@ abstract class DashboardViewModelBase with Store { statusText = S.current.Blocks_remaining(status.toString()); } - if (status is FailedSyncStatus) { + if (status is FailedSyncStatus || status is LostConnectionSyncStatus) { statusText = S.current.please_try_to_connect_to_another_node; } diff --git a/lib/view_model/send/send_view_model.dart b/lib/view_model/send/send_view_model.dart index 75926a0b2..81be508e6 100644 --- a/lib/view_model/send/send_view_model.dart +++ b/lib/view_model/send/send_view_model.dart @@ -116,6 +116,11 @@ abstract class SendViewModelBase with Store { @action void setCryptoAmount(String amount) { + // FIXME: hardcoded value. + if (amount.toUpperCase() != 'ALL') { + all = false; + } + cryptoAmount = amount; _updateFiatAmount(); }