From 35aabcd24839fbdb5d5fc533dc51fb60c09f7f1e Mon Sep 17 00:00:00 2001 From: M Date: Sat, 13 Feb 2021 00:38:34 +0200 Subject: [PATCH] Estimated fee rates. --- ios/Runner.xcodeproj/project.pbxproj | 12 +- lib/bitcoin/bitcoin_transaction_priority.dart | 18 +- lib/bitcoin/bitcoin_wallet.dart | 25 +- lib/bitcoin/electrum.dart | 66 +++-- lib/src/screens/send/send_page.dart | 1 + lib/src/screens/settings/settings.dart | 1 + .../widgets/settings_picker_cell.dart | 5 +- lib/src/widgets/picker.dart | 259 +++++++++--------- lib/view_model/send/send_view_model.dart | 18 +- lib/view_model/settings/picker_list_item.dart | 2 + .../settings/settings_view_model.dart | 11 + pubspec.yaml | 2 +- 12 files changed, 252 insertions(+), 168 deletions(-) diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index d8c77a0e..abc42ae9 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -357,7 +357,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CURRENT_PROJECT_VERSION = 25; + CURRENT_PROJECT_VERSION = 26; DEVELOPMENT_TEAM = 32J6BB6VUS; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -374,7 +374,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - MARKETING_VERSION = 4.1.1; + MARKETING_VERSION = 4.1.2; PRODUCT_BUNDLE_IDENTIFIER = com.fotolockr.cakewallet; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -498,7 +498,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CURRENT_PROJECT_VERSION = 25; + CURRENT_PROJECT_VERSION = 26; DEVELOPMENT_TEAM = 32J6BB6VUS; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -515,7 +515,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - MARKETING_VERSION = 4.1.1; + MARKETING_VERSION = 4.1.2; PRODUCT_BUNDLE_IDENTIFIER = com.fotolockr.cakewallet; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -533,7 +533,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CURRENT_PROJECT_VERSION = 25; + CURRENT_PROJECT_VERSION = 26; DEVELOPMENT_TEAM = 32J6BB6VUS; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -550,7 +550,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - MARKETING_VERSION = 4.1.1; + MARKETING_VERSION = 4.1.2; PRODUCT_BUNDLE_IDENTIFIER = com.fotolockr.cakewallet; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; diff --git a/lib/bitcoin/bitcoin_transaction_priority.dart b/lib/bitcoin/bitcoin_transaction_priority.dart index cedadf49..71002819 100644 --- a/lib/bitcoin/bitcoin_transaction_priority.dart +++ b/lib/bitcoin/bitcoin_transaction_priority.dart @@ -2,32 +2,30 @@ import 'package:cake_wallet/entities/transaction_priority.dart'; import 'package:cake_wallet/generated/i18n.dart'; class BitcoinTransactionPriority extends TransactionPriority { - const BitcoinTransactionPriority(this.rate, {String title, int raw}) + const BitcoinTransactionPriority({String title, int raw}) : super(title: title, raw: raw); - static const List all = [slow, medium, fast]; + static const List all = [fast, medium, slow]; static const BitcoinTransactionPriority slow = - BitcoinTransactionPriority(11, title: 'Slow', raw: 0); + BitcoinTransactionPriority(title: 'Slow', raw: 0); static const BitcoinTransactionPriority medium = - BitcoinTransactionPriority(90, title: 'Medium', raw: 1); + BitcoinTransactionPriority(title: 'Medium', raw: 1); static const BitcoinTransactionPriority fast = - BitcoinTransactionPriority(98, title: 'Fast', raw: 2); + BitcoinTransactionPriority(title: 'Fast', raw: 2); static BitcoinTransactionPriority deserialize({int raw}) { switch (raw) { case 0: return slow; - case 2: + case 1: return medium; - case 3: + case 2: return fast; default: return null; } } - final int rate; - @override String toString() { var label = ''; @@ -46,6 +44,6 @@ class BitcoinTransactionPriority extends TransactionPriority { break; } - return '$label ($rate sat/byte)'; + return label; } } diff --git a/lib/bitcoin/bitcoin_wallet.dart b/lib/bitcoin/bitcoin_wallet.dart index cab28e80..97ad2ac1 100644 --- a/lib/bitcoin/bitcoin_wallet.dart +++ b/lib/bitcoin/bitcoin_wallet.dart @@ -53,6 +53,7 @@ abstract class BitcoinWalletBase extends WalletBase with Store { syncStatus = NotConnectedSyncStatus(), _password = password, _accountIndex = accountIndex, + _feeRates = [], super(walletInfo) { _unspent = []; _scripthashesUpdateSubject = {}; @@ -118,10 +119,6 @@ abstract class BitcoinWalletBase extends WalletBase with Store { walletInfo: walletInfo); } - static int feeAmountForPriority(BitcoinTransactionPriority priority, - int inputsCount, int outputsCount) => - priority.rate * estimatedTransactionSize(inputsCount, outputsCount); - static int estimatedTransactionSize(int inputsCount, int outputsCounts) => inputsCount * 146 + outputsCounts * 33 + 8; @@ -161,6 +158,7 @@ abstract class BitcoinWalletBase extends WalletBase with Store { wif: hd.wif, privateKey: hd.privKey, publicKey: hd.pubKey); final String _password; + List _feeRates; int _accountIndex; Map> _scripthashesUpdateSubject; @@ -233,6 +231,11 @@ abstract class BitcoinWalletBase extends WalletBase with Store { _subscribeForUpdates(); await _updateBalance(); await _updateUnspent(); + _feeRates = await eclient.feeRates(); + + Timer.periodic(const Duration(minutes: 1), + (timer) async => _feeRates = await eclient.feeRates()); + syncStatus = SyncedSyncStatus(); } catch (e) { print(e.toString()); @@ -332,7 +335,7 @@ abstract class BitcoinWalletBase extends WalletBase with Store { addressToOutputScript(transactionCredentials.address), amount); final estimatedSize = estimatedTransactionSize(inputs.length, 2); - final feeAmount = transactionCredentials.priority.rate * estimatedSize; + final feeAmount = feeRate(transactionCredentials.priority) * estimatedSize; final changeValue = totalInputAmount - amount - feeAmount; if (changeValue > minAmount) { @@ -362,6 +365,18 @@ abstract class BitcoinWalletBase extends WalletBase with Store { 'balance': balance?.toJSON() }); + int feeRate(TransactionPriority priority) { + if (priority is BitcoinTransactionPriority) { + return _feeRates[priority.raw]; + } + + return 0; + } + + int feeAmountForPriority(BitcoinTransactionPriority priority, int inputsCount, + int outputsCount) => + feeRate(priority) * estimatedTransactionSize(inputsCount, outputsCount); + @override int calculateEstimatedFee(TransactionPriority priority, int amount) { if (priority is BitcoinTransactionPriority) { diff --git a/lib/bitcoin/electrum.dart b/lib/bitcoin/electrum.dart index 45e9c6b0..29e85afa 100644 --- a/lib/bitcoin/electrum.dart +++ b/lib/bitcoin/electrum.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'dart:typed_data'; +import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart'; import 'package:cake_wallet/bitcoin/script_hash.dart'; import 'package:flutter/foundation.dart'; import 'package:rxdart/rxdart.dart'; @@ -22,9 +23,8 @@ String jsonrpcparams(List params) { } String jsonrpc( - {String method, List params, int id, double version = 2.0}) => - '{"jsonrpc": "$version", "method": "$method", "id": "$id", "params": ${json - .encode(params)}}\n'; + {String method, List params, int id, double version = 2.0}) => + '{"jsonrpc": "$version", "method": "$method", "id": "$id", "params": ${json.encode(params)}}\n'; class SocketTask { SocketTask({this.completer, this.isSubscription, this.subject}); @@ -77,7 +77,7 @@ class ElectrumClient { socket.listen((Uint8List event) { try { final response = - json.decode(utf8.decode(event.toList())) as Map; + json.decode(utf8.decode(event.toList())) as Map; _handleResponse(response); } on FormatException catch (e) { final msg = e.message.toLowerCase(); @@ -93,7 +93,7 @@ class ElectrumClient { if (isJSONStringCorrect(unterminatedString)) { final response = - json.decode(unterminatedString) as Map; + json.decode(unterminatedString) as Map; _handleResponse(response); unterminatedString = ''; } @@ -107,7 +107,7 @@ class ElectrumClient { if (isJSONStringCorrect(unterminatedString)) { final response = - json.decode(unterminatedString) as Map; + json.decode(unterminatedString) as Map; _handleResponse(response); unterminatedString = null; } @@ -173,7 +173,7 @@ class ElectrumClient { }); Future>> getListUnspentWithAddress( - String address) => + String address) => call( method: 'blockchain.scripthash.listunspent', params: [scriptHash(address)]).then((dynamic result) { @@ -253,7 +253,7 @@ class ElectrumClient { } Future broadcastTransaction( - {@required String transactionRaw}) async => + {@required String transactionRaw}) async => call(method: 'blockchain.transaction.broadcast', params: [transactionRaw]) .then((dynamic result) { if (result is String) { @@ -264,14 +264,14 @@ class ElectrumClient { }); Future> getMerkle( - {@required String hash, @required int height}) async => + {@required String hash, @required int height}) async => await call( method: 'blockchain.transaction.get_merkle', params: [hash, height]) as Map; Future> getHeader({@required int height}) async => await call(method: 'blockchain.block.get_header', params: [height]) - as Map; + as Map; Future estimatefee({@required int p}) => call(method: 'blockchain.estimatefee', params: [p]) @@ -287,6 +287,32 @@ class ElectrumClient { return 0; }); + Future>> feeHistogram() => + call(method: 'mempool.get_fee_histogram').then((dynamic result) { + if (result is List) { + return result.map((dynamic e) { + if (e is List) { + return e.map((dynamic ee) => ee is int ? ee : null).toList(); + } + + return null; + }).toList(); + } + + return []; + }); + + Future> feeRates() async { + final topDoubleString = await estimatefee(p: 1); + final middleDoubleString = await estimatefee(p: 20); + final bottomDoubleString = await estimatefee(p: 150); + final top = (stringDoubleToBitcoinAmount(topDoubleString.toString()) / 1000).round(); + final middle = (stringDoubleToBitcoinAmount(middleDoubleString.toString()) / 1000).round(); + final bottom = (stringDoubleToBitcoinAmount(bottomDoubleString.toString()) / 1000).round(); + + return [bottom, middle, top]; + } + BehaviorSubject scripthashUpdate(String scripthash) { _id += 1; return subscribe( @@ -295,9 +321,10 @@ class ElectrumClient { params: [scripthash]); } - BehaviorSubject subscribe({@required String id, - @required String method, - List params = const []}) { + BehaviorSubject subscribe( + {@required String id, + @required String method, + List params = const []}) { final subscription = BehaviorSubject(); _regisrySubscription(id, subscription); socket.write(jsonrpc(method: method, id: _id, params: params)); @@ -315,9 +342,10 @@ class ElectrumClient { return completer.future; } - Future callWithTimeout({String method, - List params = const [], - int timeout = 2000}) async { + Future callWithTimeout( + {String method, + List params = const [], + int timeout = 2000}) async { final completer = Completer(); _id += 1; final id = _id; @@ -329,7 +357,6 @@ class ElectrumClient { } }); - return completer.future; } @@ -339,9 +366,8 @@ class ElectrumClient { onConnectionStatusChange = null; } - void _registryTask(int id, Completer completer) => - _tasks[id.toString()] = - SocketTask(completer: completer, isSubscription: false); + void _registryTask(int id, Completer completer) => _tasks[id.toString()] = + SocketTask(completer: completer, isSubscription: false); void _regisrySubscription(String id, BehaviorSubject subject) => _tasks[id] = SocketTask(subject: subject, isSubscription: true); diff --git a/lib/src/screens/send/send_page.dart b/lib/src/screens/send/send_page.dart index 22505170..9fae0ec2 100644 --- a/lib/src/screens/send/send_page.dart +++ b/lib/src/screens/send/send_page.dart @@ -761,6 +761,7 @@ class SendPage extends BasePage { await showPopUp( builder: (_) => Picker( items: items, + displayItem: sendViewModel.displayFeeRate, selectedAtIndex: selectedItem, title: S.of(context).please_select, mainAxisAlignment: MainAxisAlignment.center, diff --git a/lib/src/screens/settings/settings.dart b/lib/src/screens/settings/settings.dart index d4e70aec..feda1cf6 100644 --- a/lib/src/screens/settings/settings.dart +++ b/lib/src/screens/settings/settings.dart @@ -41,6 +41,7 @@ class SettingsPage extends BasePage { if (item is PickerListItem) { return Observer(builder: (_) { return SettingsPickerCell( + displayItem: item.displayItem, title: item.title, selectedItem: item.selectedItem(), items: item.items, diff --git a/lib/src/screens/settings/widgets/settings_picker_cell.dart b/lib/src/screens/settings/widgets/settings_picker_cell.dart index c27ea753..691b7383 100644 --- a/lib/src/screens/settings/widgets/settings_picker_cell.dart +++ b/lib/src/screens/settings/widgets/settings_picker_cell.dart @@ -7,6 +7,7 @@ import 'package:cake_wallet/generated/i18n.dart'; class SettingsPickerCell extends StandardListRow { SettingsPickerCell( {@required String title, + @required this.displayItem, this.selectedItem, this.items, this.onItemSelected}) @@ -20,6 +21,7 @@ class SettingsPickerCell extends StandardListRow { context: context, builder: (_) => Picker( items: items, + displayItem: displayItem, selectedAtIndex: selectedAtIndex, title: S.current.please_select, mainAxisAlignment: MainAxisAlignment.center, @@ -30,11 +32,12 @@ class SettingsPickerCell extends StandardListRow { final ItemType selectedItem; final List items; final void Function(ItemType item) onItemSelected; + final String Function(ItemType item) displayItem; @override Widget buildTrailing(BuildContext context) { return Text( - selectedItem.toString(), + displayItem?.call(selectedItem) ?? selectedItem.toString(), textAlign: TextAlign.right, style: TextStyle( fontSize: 14.0, diff --git a/lib/src/widgets/picker.dart b/lib/src/widgets/picker.dart index c157c3dc..3748bf28 100644 --- a/lib/src/widgets/picker.dart +++ b/lib/src/widgets/picker.dart @@ -10,10 +10,11 @@ class Picker extends StatefulWidget { Picker({ @required this.selectedAtIndex, @required this.items, - this.images, @required this.title, - this.description, @required this.onItemSelected, + this.displayItem, + this.images, + this.description, this.mainAxisAlignment = MainAxisAlignment.start, }); @@ -24,6 +25,7 @@ class Picker extends StatefulWidget { final String description; final Function(Item) onItemSelected; final MainAxisAlignment mainAxisAlignment; + final String Function(Item) displayItem; @override PickerState createState() => PickerState(items, images, onItemSelected); @@ -36,7 +38,8 @@ class PickerState extends State { final List items; final List images; - final closeButton = Image.asset('assets/images/close.png', + final closeButton = Image.asset( + 'assets/images/close.png', color: Palette.darkBlueCraiola, ); ScrollController controller = ScrollController(); @@ -49,7 +52,9 @@ class PickerState extends State { Widget build(BuildContext context) { controller.addListener(() { fromTop = controller.hasClients - ? (controller.offset / controller.position.maxScrollExtent * (backgroundHeight - thumbHeight)) + ? (controller.offset / + controller.position.maxScrollExtent * + (backgroundHeight - thumbHeight)) : 0; setState(() {}); }); @@ -58,134 +63,142 @@ class PickerState extends State { return AlertBackground( child: Stack( - alignment: Alignment.center, + alignment: Alignment.center, + children: [ + Column( + mainAxisSize: MainAxisSize.min, children: [ - Column( - mainAxisSize: MainAxisSize.min, - children: [ - Container( - padding: EdgeInsets.only(left: 24, right: 24), - child: Text( - widget.title, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 18, - fontFamily: 'Lato', - fontWeight: FontWeight.bold, - decoration: TextDecoration.none, - color: Colors.white - ), - ), - ), - Padding( - padding: EdgeInsets.only(left: 24, right: 24, top: 24), - child: GestureDetector( - onTap: () => null, - child: ClipRRect( - borderRadius: BorderRadius.all(Radius.circular(14)), - child: Container( - height: 233, - color: Theme.of(context).accentTextTheme.title.color, - child: Stack( - alignment: Alignment.center, - children: [ - ListView.separated( - padding: EdgeInsets.all(0), - controller: controller, - separatorBuilder: (context, index) => Divider( - color: Theme.of(context).accentTextTheme.title.backgroundColor, - height: 1, - ), - itemCount: items == null ? 0 : items.length, - itemBuilder: (context, index) { - final item = items[index]; - final image = images != null? images[index] : null; - final isItemSelected = index == widget.selectedAtIndex; + Container( + padding: EdgeInsets.only(left: 24, right: 24), + child: Text( + widget.title, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 18, + fontFamily: 'Lato', + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + color: Colors.white), + ), + ), + Padding( + padding: EdgeInsets.only(left: 24, right: 24, top: 24), + child: GestureDetector( + onTap: () => null, + child: ClipRRect( + borderRadius: BorderRadius.all(Radius.circular(14)), + child: Container( + height: 233, + color: Theme.of(context).accentTextTheme.title.color, + child: Stack( + alignment: Alignment.center, + children: [ + ListView.separated( + padding: EdgeInsets.all(0), + controller: controller, + separatorBuilder: (context, index) => Divider( + color: Theme.of(context) + .accentTextTheme + .title + .backgroundColor, + height: 1, + ), + itemCount: items == null ? 0 : items.length, + itemBuilder: (context, index) { + final item = items[index]; + final image = + images != null ? images[index] : null; + final isItemSelected = + index == widget.selectedAtIndex; - final color = isItemSelected - ? Theme.of(context).textTheme.body2.color - : Theme.of(context).accentTextTheme.title.color; - final textColor = isItemSelected - ? Palette.blueCraiola - : Theme.of(context).primaryTextTheme.title.color; + final color = isItemSelected + ? Theme.of(context).textTheme.body2.color + : Theme.of(context) + .accentTextTheme + .title + .color; + final textColor = isItemSelected + ? Palette.blueCraiola + : Theme.of(context) + .primaryTextTheme + .title + .color; - return GestureDetector( - onTap: () { - if (onItemSelected == null) { - return; - } - Navigator.of(context).pop(); - onItemSelected(item); - }, - child: Container( - height: 77, - padding: EdgeInsets.only(left: 24, right: 24), - color: color, - child: Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: widget.mainAxisAlignment, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - image ?? Offstage(), - Padding( - padding: EdgeInsets.only( - left: image != null ? 12 : 0 - ), - child: Text( - item.toString(), - style: TextStyle( - fontSize: 18, - fontFamily: 'Lato', - fontWeight: FontWeight.w600, - color: textColor, - decoration: TextDecoration.none, - ), - ), - ) - ], - ), - ), - ); + return GestureDetector( + onTap: () { + if (onItemSelected == null) { + return; + } + Navigator.of(context).pop(); + onItemSelected(item); }, - ), - ((widget.description != null) - &&(widget.description.isNotEmpty)) - ? Positioned( - bottom: 24, - left: 24, - right: 24, - child: Text( - widget.description, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w500, - fontFamily: 'Lato', - decoration: TextDecoration.none, - color: Theme.of(context).primaryTextTheme - .title.color + child: Container( + height: 77, + padding: EdgeInsets.only(left: 24, right: 24), + color: color, + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: widget.mainAxisAlignment, + crossAxisAlignment: + CrossAxisAlignment.center, + children: [ + image ?? Offstage(), + Padding( + padding: EdgeInsets.only( + left: image != null ? 12 : 0), + child: Text( + widget.displayItem?.call(item) ?? + item.toString(), + style: TextStyle( + fontSize: 18, + fontFamily: 'Lato', + fontWeight: FontWeight.w600, + color: textColor, + decoration: TextDecoration.none, + ), + ), + ) + ], ), - ) - ) + ), + ); + }, + ), + ((widget.description != null) && + (widget.description.isNotEmpty)) + ? Positioned( + bottom: 24, + left: 24, + right: 24, + child: Text( + widget.description, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w500, + fontFamily: 'Lato', + decoration: TextDecoration.none, + color: Theme.of(context) + .primaryTextTheme + .title + .color), + )) : Offstage(), - isShowScrollThumb + isShowScrollThumb ? CakeScrollbar( backgroundHeight: backgroundHeight, thumbHeight: thumbHeight, - fromTop: fromTop - ) + fromTop: fromTop) : Offstage(), - ], - ) - ), - ), - ), - ) - ], - ), - AlertCloseButton(image: closeButton) + ], + )), + ), + ), + ) ], - ) - ); + ), + AlertCloseButton(image: closeButton) + ], + )); } -} \ No newline at end of file +} diff --git a/lib/view_model/send/send_view_model.dart b/lib/view_model/send/send_view_model.dart index 581bea1e..1872e845 100644 --- a/lib/view_model/send/send_view_model.dart +++ b/lib/view_model/send/send_view_model.dart @@ -97,7 +97,8 @@ abstract class SendViewModelBase with Store { } } - final fee = _wallet.calculateEstimatedFee(_settingsStore.priority[_wallet.type], amount); + final fee = _wallet.calculateEstimatedFee( + _settingsStore.priority[_wallet.type], amount); if (_wallet is BitcoinWallet) { return bitcoinAmountToDouble(amount: fee); @@ -298,7 +299,8 @@ abstract class SendViewModelBase with Store { final amount = !sendAll ? _amount : null; final priority = _settingsStore.priority[_wallet.type]; - return BitcoinTransactionCredentials(address, amount, priority as BitcoinTransactionPriority); + return BitcoinTransactionCredentials( + address, amount, priority as BitcoinTransactionPriority); case WalletType.monero: final amount = !sendAll ? _amount : null; final priority = _settingsStore.priority[_wallet.type]; @@ -345,4 +347,16 @@ abstract class SendViewModelBase with Store { void removeTemplate({Template template}) => _sendTemplateStore.remove(template: template); + + String displayFeeRate(dynamic priority) { + final _priority = priority as TransactionPriority; + final wallet = _wallet; + + if (wallet is BitcoinWallet) { + final rate = wallet.feeRate(_priority); + return '${priority.toString()} ($rate sat/byte)'; + } + + return priority.toString(); + } } diff --git a/lib/view_model/settings/picker_list_item.dart b/lib/view_model/settings/picker_list_item.dart index 1dfc9c06..0785232b 100644 --- a/lib/view_model/settings/picker_list_item.dart +++ b/lib/view_model/settings/picker_list_item.dart @@ -6,12 +6,14 @@ class PickerListItem extends SettingsListItem { {@required String title, @required this.selectedItem, @required this.items, + this.displayItem, void Function(ItemType item) onItemSelected}) : _onItemSelected = onItemSelected, super(title); final ItemType Function() selectedItem; final List items; + final String Function(ItemType item) displayItem; final void Function(ItemType item) _onItemSelected; void onItemSelected(dynamic item) { diff --git a/lib/view_model/settings/settings_view_model.dart b/lib/view_model/settings/settings_view_model.dart index 20d2a171..0211b227 100644 --- a/lib/view_model/settings/settings_view_model.dart +++ b/lib/view_model/settings/settings_view_model.dart @@ -1,4 +1,5 @@ import 'package:cake_wallet/bitcoin/bitcoin_transaction_priority.dart'; +import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart'; import 'package:cake_wallet/entities/balance.dart'; import 'package:cake_wallet/entities/transaction_priority.dart'; import 'package:cake_wallet/themes/theme_base.dart'; @@ -74,6 +75,16 @@ abstract class SettingsViewModelBase with Store { PickerListItem( title: S.current.settings_fee_priority, items: priorityForWalletType(wallet.type), + displayItem: (dynamic priority) { + final _priority = priority as TransactionPriority; + + if (wallet is BitcoinWallet) { + final rate = wallet.feeRate(_priority); + return '${priority.toString()} ($rate sat/byte)'; + } + + return priority.toString(); + }, selectedItem: () => transactionPriority, onItemSelected: (TransactionPriority priority) => _settingsStore.priority[wallet.type] = priority), diff --git a/pubspec.yaml b/pubspec.yaml index 827d9ba9..9498bf38 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,7 +11,7 @@ description: Cake Wallet. # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 4.1.1+40 +version: 4.1.2+41 environment: sdk: ">=2.7.0 <3.0.0"