mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2025-01-25 11:45:59 +00:00
feat: WIP desktop custom fee selection for supported coins
This commit is contained in:
parent
04e0446aaf
commit
fae0c778ef
3 changed files with 629 additions and 82 deletions
|
@ -931,6 +931,43 @@ class _ConfirmTransactionViewState
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (isDesktop &&
|
||||||
|
!widget.isPaynymTransaction &&
|
||||||
|
transactionInfo["fee"] is int &&
|
||||||
|
transactionInfo["vSize"] is int)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
left: 32,
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
"sats/vByte",
|
||||||
|
style: STextStyles.desktopTextExtraExtraSmall(context),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (isDesktop &&
|
||||||
|
!widget.isPaynymTransaction &&
|
||||||
|
transactionInfo["fee"] is int &&
|
||||||
|
transactionInfo["vSize"] is int)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
top: 10,
|
||||||
|
left: 32,
|
||||||
|
right: 32,
|
||||||
|
),
|
||||||
|
child: RoundedContainer(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 16,
|
||||||
|
vertical: 18,
|
||||||
|
),
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.textFieldDefaultBG,
|
||||||
|
child: Text(
|
||||||
|
"~${(transactionInfo["fee"] / transactionInfo["vSize"]).toInt()}",
|
||||||
|
style: STextStyles.itemSubtitle(context),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
if (!isDesktop) const Spacer(),
|
if (!isDesktop) const Spacer(),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: isDesktop ? 23 : 12,
|
height: isDesktop ? 23 : 12,
|
||||||
|
|
|
@ -12,6 +12,7 @@ import 'dart:async';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:bip47/bip47.dart';
|
import 'package:bip47/bip47.dart';
|
||||||
|
import 'package:cw_core/monero_transaction_priority.dart';
|
||||||
import 'package:decimal/decimal.dart';
|
import 'package:decimal/decimal.dart';
|
||||||
import 'package:dropdown_button2/dropdown_button2.dart';
|
import 'package:dropdown_button2/dropdown_button2.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
@ -45,16 +46,20 @@ import 'package:stackwallet/utilities/barcode_scanner_interface.dart';
|
||||||
import 'package:stackwallet/utilities/clipboard_interface.dart';
|
import 'package:stackwallet/utilities/clipboard_interface.dart';
|
||||||
import 'package:stackwallet/utilities/constants.dart';
|
import 'package:stackwallet/utilities/constants.dart';
|
||||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||||
|
import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart';
|
||||||
import 'package:stackwallet/utilities/logger.dart';
|
import 'package:stackwallet/utilities/logger.dart';
|
||||||
import 'package:stackwallet/utilities/prefs.dart';
|
import 'package:stackwallet/utilities/prefs.dart';
|
||||||
import 'package:stackwallet/utilities/text_styles.dart';
|
import 'package:stackwallet/utilities/text_styles.dart';
|
||||||
import 'package:stackwallet/utilities/util.dart';
|
import 'package:stackwallet/utilities/util.dart';
|
||||||
import 'package:stackwallet/widgets/animated_text.dart';
|
import 'package:stackwallet/widgets/animated_text.dart';
|
||||||
|
import 'package:stackwallet/widgets/conditional_parent.dart';
|
||||||
import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart';
|
import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart';
|
||||||
import 'package:stackwallet/widgets/desktop/desktop_dialog.dart';
|
import 'package:stackwallet/widgets/desktop/desktop_dialog.dart';
|
||||||
import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart';
|
import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart';
|
||||||
|
import 'package:stackwallet/widgets/desktop/desktop_fee_dialog.dart';
|
||||||
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
||||||
import 'package:stackwallet/widgets/desktop/secondary_button.dart';
|
import 'package:stackwallet/widgets/desktop/secondary_button.dart';
|
||||||
|
import 'package:stackwallet/widgets/fee_slider.dart';
|
||||||
import 'package:stackwallet/widgets/icon_widgets/addressbook_icon.dart';
|
import 'package:stackwallet/widgets/icon_widgets/addressbook_icon.dart';
|
||||||
import 'package:stackwallet/widgets/icon_widgets/clipboard_icon.dart';
|
import 'package:stackwallet/widgets/icon_widgets/clipboard_icon.dart';
|
||||||
import 'package:stackwallet/widgets/icon_widgets/x_icon.dart';
|
import 'package:stackwallet/widgets/icon_widgets/x_icon.dart';
|
||||||
|
@ -115,6 +120,17 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
||||||
|
|
||||||
bool get isPaynymSend => widget.accountLite != null;
|
bool get isPaynymSend => widget.accountLite != null;
|
||||||
|
|
||||||
|
bool isCustomFee = false;
|
||||||
|
int customFeeRate = 1;
|
||||||
|
(FeeRateType, String?, String?)? feeSelectionResult;
|
||||||
|
|
||||||
|
final stringsToLoopThrough = [
|
||||||
|
"Calculating",
|
||||||
|
"Calculating.",
|
||||||
|
"Calculating..",
|
||||||
|
"Calculating...",
|
||||||
|
];
|
||||||
|
|
||||||
Future<void> previewSend() async {
|
Future<void> previewSend() async {
|
||||||
final manager =
|
final manager =
|
||||||
ref.read(walletsChangeNotifierProvider).getManager(walletId);
|
ref.read(walletsChangeNotifierProvider).getManager(walletId);
|
||||||
|
@ -283,6 +299,7 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
||||||
isSegwit: widget.accountLite!.segwit,
|
isSegwit: widget.accountLite!.segwit,
|
||||||
amount: amount,
|
amount: amount,
|
||||||
args: {
|
args: {
|
||||||
|
"satsPerVByte": isCustomFee ? customFeeRate : null,
|
||||||
"feeRate": feeRate,
|
"feeRate": feeRate,
|
||||||
"UTXOs": (manager.hasCoinControlSupport &&
|
"UTXOs": (manager.hasCoinControlSupport &&
|
||||||
coinControlEnabled &&
|
coinControlEnabled &&
|
||||||
|
@ -299,6 +316,7 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
||||||
amount: amount,
|
amount: amount,
|
||||||
args: {
|
args: {
|
||||||
"feeRate": ref.read(feeRateTypeStateProvider),
|
"feeRate": ref.read(feeRateTypeStateProvider),
|
||||||
|
"satsPerVByte": isCustomFee ? customFeeRate : null,
|
||||||
"UTXOs": (manager.hasCoinControlSupport &&
|
"UTXOs": (manager.hasCoinControlSupport &&
|
||||||
coinControlEnabled &&
|
coinControlEnabled &&
|
||||||
ref.read(desktopUseUTXOs).isNotEmpty)
|
ref.read(desktopUseUTXOs).isNotEmpty)
|
||||||
|
@ -312,6 +330,7 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
||||||
amount: amount,
|
amount: amount,
|
||||||
args: {
|
args: {
|
||||||
"feeRate": ref.read(feeRateTypeStateProvider),
|
"feeRate": ref.read(feeRateTypeStateProvider),
|
||||||
|
"satsPerVByte": isCustomFee ? customFeeRate : null,
|
||||||
"UTXOs": (manager.hasCoinControlSupport &&
|
"UTXOs": (manager.hasCoinControlSupport &&
|
||||||
coinControlEnabled &&
|
coinControlEnabled &&
|
||||||
ref.read(desktopUseUTXOs).isNotEmpty)
|
ref.read(desktopUseUTXOs).isNotEmpty)
|
||||||
|
@ -561,12 +580,7 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return AnimatedText(
|
return AnimatedText(
|
||||||
stringsToLoopThrough: const [
|
stringsToLoopThrough: stringsToLoopThrough,
|
||||||
"Loading balance",
|
|
||||||
"Loading balance.",
|
|
||||||
"Loading balance..",
|
|
||||||
"Loading balance...",
|
|
||||||
],
|
|
||||||
style: STextStyles.itemSubtitle(context),
|
style: STextStyles.itemSubtitle(context),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1359,80 +1373,50 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
// const SizedBox(
|
|
||||||
// height: 20,
|
|
||||||
// ),
|
|
||||||
// Text(
|
|
||||||
// "Note (optional)",
|
|
||||||
// style: STextStyles.desktopTextExtraSmall(context).copyWith(
|
|
||||||
// color: Theme.of(context)
|
|
||||||
// .extension<StackColors>()!
|
|
||||||
// .textFieldActiveSearchIconRight,
|
|
||||||
// ),
|
|
||||||
// textAlign: TextAlign.left,
|
|
||||||
// ),
|
|
||||||
// const SizedBox(
|
|
||||||
// height: 10,
|
|
||||||
// ),
|
|
||||||
// ClipRRect(
|
|
||||||
// borderRadius: BorderRadius.circular(
|
|
||||||
// Constants.size.circularBorderRadius,
|
|
||||||
// ),
|
|
||||||
// child: TextField(
|
|
||||||
// minLines: 1,
|
|
||||||
// maxLines: 5,
|
|
||||||
// autocorrect: Util.isDesktop ? false : true,
|
|
||||||
// enableSuggestions: Util.isDesktop ? false : true,
|
|
||||||
// controller: noteController,
|
|
||||||
// focusNode: _noteFocusNode,
|
|
||||||
// style: STextStyles.desktopTextExtraSmall(context).copyWith(
|
|
||||||
// color: Theme.of(context)
|
|
||||||
// .extension<StackColors>()!
|
|
||||||
// .textFieldActiveText,
|
|
||||||
// height: 1.8,
|
|
||||||
// ),
|
|
||||||
// onChanged: (_) => setState(() {}),
|
|
||||||
// decoration: standardInputDecoration(
|
|
||||||
// "Type something...",
|
|
||||||
// _noteFocusNode,
|
|
||||||
// context,
|
|
||||||
// desktopMed: true,
|
|
||||||
// ).copyWith(
|
|
||||||
// contentPadding: const EdgeInsets.only(
|
|
||||||
// left: 16,
|
|
||||||
// top: 11,
|
|
||||||
// bottom: 12,
|
|
||||||
// right: 5,
|
|
||||||
// ),
|
|
||||||
// suffixIcon: noteController.text.isNotEmpty
|
|
||||||
// ? Padding(
|
|
||||||
// padding: const EdgeInsets.only(right: 0),
|
|
||||||
// child: UnconstrainedBox(
|
|
||||||
// child: Row(
|
|
||||||
// children: [
|
|
||||||
// TextFieldIconButton(
|
|
||||||
// child: const XIcon(),
|
|
||||||
// onTap: () async {
|
|
||||||
// setState(() {
|
|
||||||
// noteController.text = "";
|
|
||||||
// });
|
|
||||||
// },
|
|
||||||
// ),
|
|
||||||
// ],
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// )
|
|
||||||
// : null,
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
if (!isPaynymSend)
|
if (!isPaynymSend)
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 20,
|
height: 20,
|
||||||
),
|
),
|
||||||
if (!([Coin.nano, Coin.banano, Coin.epicCash].contains(coin)))
|
if (!([Coin.nano, Coin.banano, Coin.epicCash].contains(coin)))
|
||||||
Text(
|
ConditionalParent(
|
||||||
"Transaction fee (${coin == Coin.ethereum ? "max" : "estimated"})",
|
condition: coin.isElectrumXCoin,
|
||||||
|
builder: (child) => Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
child,
|
||||||
|
CustomTextButton(
|
||||||
|
text: "Edit",
|
||||||
|
onTap: () async {
|
||||||
|
feeSelectionResult = await showDialog<
|
||||||
|
(
|
||||||
|
FeeRateType,
|
||||||
|
String?,
|
||||||
|
String?,
|
||||||
|
)?>(
|
||||||
|
context: context,
|
||||||
|
builder: (_) => DesktopFeeDialog(
|
||||||
|
walletId: walletId,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (feeSelectionResult != null) {
|
||||||
|
if (isCustomFee &&
|
||||||
|
feeSelectionResult!.$1 != FeeRateType.custom) {
|
||||||
|
isCustomFee = false;
|
||||||
|
} else if (!isCustomFee &&
|
||||||
|
feeSelectionResult!.$1 == FeeRateType.custom) {
|
||||||
|
isCustomFee = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
"Transaction fee"
|
||||||
|
"${isCustomFee ? "" : " (${coin == Coin.ethereum ? "max" : "estimated"})"}",
|
||||||
style: STextStyles.desktopTextExtraSmall(context).copyWith(
|
style: STextStyles.desktopTextExtraSmall(context).copyWith(
|
||||||
color: Theme.of(context)
|
color: Theme.of(context)
|
||||||
.extension<StackColors>()!
|
.extension<StackColors>()!
|
||||||
|
@ -1440,13 +1424,127 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
||||||
),
|
),
|
||||||
textAlign: TextAlign.left,
|
textAlign: TextAlign.left,
|
||||||
),
|
),
|
||||||
|
),
|
||||||
if (!([Coin.nano, Coin.banano, Coin.epicCash].contains(coin)))
|
if (!([Coin.nano, Coin.banano, Coin.epicCash].contains(coin)))
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 10,
|
height: 10,
|
||||||
),
|
),
|
||||||
if (!([Coin.nano, Coin.banano, Coin.epicCash].contains(coin)))
|
if (!([Coin.nano, Coin.banano, Coin.epicCash].contains(coin)))
|
||||||
DesktopFeeDropDown(
|
if (!isCustomFee)
|
||||||
|
(feeSelectionResult?.$2 == null)
|
||||||
|
? FutureBuilder(
|
||||||
|
future: ref.watch(
|
||||||
|
walletsChangeNotifierProvider.select(
|
||||||
|
(value) => value.getManager(walletId).fees,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.connectionState == ConnectionState.done &&
|
||||||
|
snapshot.hasData) {
|
||||||
|
return DesktopFeeItem(
|
||||||
|
feeObject: snapshot.data,
|
||||||
|
feeRateType: FeeRateType.average,
|
||||||
walletId: walletId,
|
walletId: walletId,
|
||||||
|
feeFor: ({
|
||||||
|
required Amount amount,
|
||||||
|
required FeeRateType feeRateType,
|
||||||
|
required int feeRate,
|
||||||
|
required Coin coin,
|
||||||
|
}) async {
|
||||||
|
if (ref
|
||||||
|
.read(feeSheetSessionCacheProvider)
|
||||||
|
.average[amount] ==
|
||||||
|
null) {
|
||||||
|
final manager = ref
|
||||||
|
.read(walletsChangeNotifierProvider)
|
||||||
|
.getManager(walletId);
|
||||||
|
|
||||||
|
if (coin == Coin.monero || coin == Coin.wownero) {
|
||||||
|
final fee = await manager.estimateFeeFor(amount,
|
||||||
|
MoneroTransactionPriority.regular.raw!);
|
||||||
|
ref
|
||||||
|
.read(feeSheetSessionCacheProvider)
|
||||||
|
.average[amount] = fee;
|
||||||
|
} else if ((coin == Coin.firo ||
|
||||||
|
coin == Coin.firoTestNet) &&
|
||||||
|
ref
|
||||||
|
.read(
|
||||||
|
publicPrivateBalanceStateProvider
|
||||||
|
.state)
|
||||||
|
.state !=
|
||||||
|
"Private") {
|
||||||
|
ref
|
||||||
|
.read(feeSheetSessionCacheProvider)
|
||||||
|
.average[amount] =
|
||||||
|
await (manager.wallet as FiroWallet)
|
||||||
|
.estimateFeeForPublic(amount, feeRate);
|
||||||
|
} else {
|
||||||
|
ref
|
||||||
|
.read(feeSheetSessionCacheProvider)
|
||||||
|
.average[amount] =
|
||||||
|
await manager.estimateFeeFor(
|
||||||
|
amount, feeRate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ref
|
||||||
|
.read(feeSheetSessionCacheProvider)
|
||||||
|
.average[amount]!;
|
||||||
|
},
|
||||||
|
isSelected: true,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
AnimatedText(
|
||||||
|
stringsToLoopThrough: stringsToLoopThrough,
|
||||||
|
style: STextStyles.desktopTextExtraExtraSmall(
|
||||||
|
context)
|
||||||
|
.copyWith(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.textFieldActiveText,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
feeSelectionResult?.$2 ?? "",
|
||||||
|
style: STextStyles.desktopTextExtraExtraSmall(context)
|
||||||
|
.copyWith(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.textFieldActiveText,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.left,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
feeSelectionResult?.$3 ?? "",
|
||||||
|
style: STextStyles.desktopTextExtraExtraSmall(context)
|
||||||
|
.copyWith(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.textFieldActiveSearchIconRight,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (isCustomFee)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
bottom: 12,
|
||||||
|
top: 16,
|
||||||
|
),
|
||||||
|
child: FeeSlider(
|
||||||
|
onSatVByteChanged: (rate) {
|
||||||
|
customFeeRate = rate;
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 36,
|
height: 36,
|
||||||
|
|
412
lib/widgets/desktop/desktop_fee_dialog.dart
Normal file
412
lib/widgets/desktop/desktop_fee_dialog.dart
Normal file
|
@ -0,0 +1,412 @@
|
||||||
|
import 'package:cw_core/monero_transaction_priority.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:stackwallet/models/models.dart';
|
||||||
|
import 'package:stackwallet/pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart';
|
||||||
|
import 'package:stackwallet/pages/token_view/token_view.dart';
|
||||||
|
import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_fee_dropdown.dart';
|
||||||
|
import 'package:stackwallet/providers/global/wallets_provider.dart';
|
||||||
|
import 'package:stackwallet/providers/wallet/public_private_balance_state_provider.dart';
|
||||||
|
import 'package:stackwallet/services/coins/firo/firo_wallet.dart';
|
||||||
|
import 'package:stackwallet/themes/stack_colors.dart';
|
||||||
|
import 'package:stackwallet/utilities/amount/amount.dart';
|
||||||
|
import 'package:stackwallet/utilities/amount/amount_formatter.dart';
|
||||||
|
import 'package:stackwallet/utilities/constants.dart';
|
||||||
|
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||||
|
import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart';
|
||||||
|
import 'package:stackwallet/utilities/text_styles.dart';
|
||||||
|
import 'package:stackwallet/widgets/animated_text.dart';
|
||||||
|
import 'package:stackwallet/widgets/desktop/desktop_dialog.dart';
|
||||||
|
import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart';
|
||||||
|
|
||||||
|
class DesktopFeeDialog extends ConsumerStatefulWidget {
|
||||||
|
const DesktopFeeDialog({
|
||||||
|
Key? key,
|
||||||
|
required this.walletId,
|
||||||
|
this.isToken = false,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final String walletId;
|
||||||
|
final bool isToken;
|
||||||
|
|
||||||
|
@override
|
||||||
|
ConsumerState<DesktopFeeDialog> createState() => _DesktopFeeDialogState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DesktopFeeDialogState extends ConsumerState<DesktopFeeDialog> {
|
||||||
|
late final String walletId;
|
||||||
|
|
||||||
|
FeeObject? feeObject;
|
||||||
|
FeeRateType feeRateType = FeeRateType.average;
|
||||||
|
|
||||||
|
Future<Amount> feeFor({
|
||||||
|
required Amount amount,
|
||||||
|
required FeeRateType feeRateType,
|
||||||
|
required int feeRate,
|
||||||
|
required Coin coin,
|
||||||
|
}) async {
|
||||||
|
switch (feeRateType) {
|
||||||
|
case FeeRateType.fast:
|
||||||
|
if (ref
|
||||||
|
.read(widget.isToken
|
||||||
|
? tokenFeeSessionCacheProvider
|
||||||
|
: feeSheetSessionCacheProvider)
|
||||||
|
.fast[amount] ==
|
||||||
|
null) {
|
||||||
|
if (widget.isToken == false) {
|
||||||
|
final manager =
|
||||||
|
ref.read(walletsChangeNotifierProvider).getManager(walletId);
|
||||||
|
|
||||||
|
if (coin == Coin.monero || coin == Coin.wownero) {
|
||||||
|
final fee = await manager.estimateFeeFor(
|
||||||
|
amount, MoneroTransactionPriority.fast.raw!);
|
||||||
|
ref.read(feeSheetSessionCacheProvider).fast[amount] = fee;
|
||||||
|
} else if ((coin == Coin.firo || coin == Coin.firoTestNet) &&
|
||||||
|
ref.read(publicPrivateBalanceStateProvider.state).state !=
|
||||||
|
"Private") {
|
||||||
|
ref.read(feeSheetSessionCacheProvider).fast[amount] =
|
||||||
|
await (manager.wallet as FiroWallet)
|
||||||
|
.estimateFeeForPublic(amount, feeRate);
|
||||||
|
} else {
|
||||||
|
ref.read(feeSheetSessionCacheProvider).fast[amount] =
|
||||||
|
await manager.estimateFeeFor(amount, feeRate);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
final tokenWallet = ref.read(tokenServiceProvider)!;
|
||||||
|
final fee = tokenWallet.estimateFeeFor(feeRate);
|
||||||
|
ref.read(tokenFeeSessionCacheProvider).fast[amount] = fee;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ref
|
||||||
|
.read(widget.isToken
|
||||||
|
? tokenFeeSessionCacheProvider
|
||||||
|
: feeSheetSessionCacheProvider)
|
||||||
|
.fast[amount]!;
|
||||||
|
|
||||||
|
case FeeRateType.average:
|
||||||
|
if (ref
|
||||||
|
.read(widget.isToken
|
||||||
|
? tokenFeeSessionCacheProvider
|
||||||
|
: feeSheetSessionCacheProvider)
|
||||||
|
.average[amount] ==
|
||||||
|
null) {
|
||||||
|
if (widget.isToken == false) {
|
||||||
|
final manager =
|
||||||
|
ref.read(walletsChangeNotifierProvider).getManager(walletId);
|
||||||
|
|
||||||
|
if (coin == Coin.monero || coin == Coin.wownero) {
|
||||||
|
final fee = await manager.estimateFeeFor(
|
||||||
|
amount, MoneroTransactionPriority.regular.raw!);
|
||||||
|
ref.read(feeSheetSessionCacheProvider).average[amount] = fee;
|
||||||
|
} else if ((coin == Coin.firo || coin == Coin.firoTestNet) &&
|
||||||
|
ref.read(publicPrivateBalanceStateProvider.state).state !=
|
||||||
|
"Private") {
|
||||||
|
ref.read(feeSheetSessionCacheProvider).average[amount] =
|
||||||
|
await (manager.wallet as FiroWallet)
|
||||||
|
.estimateFeeForPublic(amount, feeRate);
|
||||||
|
} else {
|
||||||
|
ref.read(feeSheetSessionCacheProvider).average[amount] =
|
||||||
|
await manager.estimateFeeFor(amount, feeRate);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
final tokenWallet = ref.read(tokenServiceProvider)!;
|
||||||
|
final fee = tokenWallet.estimateFeeFor(feeRate);
|
||||||
|
ref.read(tokenFeeSessionCacheProvider).average[amount] = fee;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ref
|
||||||
|
.read(widget.isToken
|
||||||
|
? tokenFeeSessionCacheProvider
|
||||||
|
: feeSheetSessionCacheProvider)
|
||||||
|
.average[amount]!;
|
||||||
|
|
||||||
|
case FeeRateType.slow:
|
||||||
|
if (ref
|
||||||
|
.read(widget.isToken
|
||||||
|
? tokenFeeSessionCacheProvider
|
||||||
|
: feeSheetSessionCacheProvider)
|
||||||
|
.slow[amount] ==
|
||||||
|
null) {
|
||||||
|
if (widget.isToken == false) {
|
||||||
|
final manager =
|
||||||
|
ref.read(walletsChangeNotifierProvider).getManager(walletId);
|
||||||
|
|
||||||
|
if (coin == Coin.monero || coin == Coin.wownero) {
|
||||||
|
final fee = await manager.estimateFeeFor(
|
||||||
|
amount, MoneroTransactionPriority.slow.raw!);
|
||||||
|
ref.read(feeSheetSessionCacheProvider).slow[amount] = fee;
|
||||||
|
} else if ((coin == Coin.firo || coin == Coin.firoTestNet) &&
|
||||||
|
ref.read(publicPrivateBalanceStateProvider.state).state !=
|
||||||
|
"Private") {
|
||||||
|
ref.read(feeSheetSessionCacheProvider).slow[amount] =
|
||||||
|
await (manager.wallet as FiroWallet)
|
||||||
|
.estimateFeeForPublic(amount, feeRate);
|
||||||
|
} else {
|
||||||
|
ref.read(feeSheetSessionCacheProvider).slow[amount] =
|
||||||
|
await manager.estimateFeeFor(amount, feeRate);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
final tokenWallet = ref.read(tokenServiceProvider)!;
|
||||||
|
final fee = tokenWallet.estimateFeeFor(feeRate);
|
||||||
|
ref.read(tokenFeeSessionCacheProvider).slow[amount] = fee;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ref
|
||||||
|
.read(widget.isToken
|
||||||
|
? tokenFeeSessionCacheProvider
|
||||||
|
: feeSheetSessionCacheProvider)
|
||||||
|
.slow[amount]!;
|
||||||
|
default:
|
||||||
|
return Amount.zero;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
walletId = widget.walletId;
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return DesktopDialog(
|
||||||
|
maxWidth: 450,
|
||||||
|
maxHeight: double.infinity,
|
||||||
|
child: FutureBuilder(
|
||||||
|
future: ref.watch(
|
||||||
|
walletsChangeNotifierProvider.select(
|
||||||
|
(value) => value.getManager(walletId).fees,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.connectionState == ConnectionState.done &&
|
||||||
|
snapshot.hasData) {
|
||||||
|
feeObject = snapshot.data!;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 32),
|
||||||
|
child: Text(
|
||||||
|
"Choose fee",
|
||||||
|
style: STextStyles.desktopH3(context),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const DesktopDialogCloseButton(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
...FeeRateType.values.map(
|
||||||
|
(e) => Padding(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
left: 32,
|
||||||
|
right: 32,
|
||||||
|
bottom: 16,
|
||||||
|
),
|
||||||
|
child: DesktopFeeItem(
|
||||||
|
feeObject: feeObject,
|
||||||
|
feeRateType: e,
|
||||||
|
walletId: walletId,
|
||||||
|
feeFor: feeFor,
|
||||||
|
isSelected: false,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 16,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DesktopFeeItem extends ConsumerStatefulWidget {
|
||||||
|
const DesktopFeeItem({
|
||||||
|
Key? key,
|
||||||
|
required this.feeObject,
|
||||||
|
required this.feeRateType,
|
||||||
|
required this.walletId,
|
||||||
|
required this.feeFor,
|
||||||
|
required this.isSelected,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final FeeObject? feeObject;
|
||||||
|
final FeeRateType feeRateType;
|
||||||
|
final String walletId;
|
||||||
|
final Future<Amount> Function({
|
||||||
|
required Amount amount,
|
||||||
|
required FeeRateType feeRateType,
|
||||||
|
required int feeRate,
|
||||||
|
required Coin coin,
|
||||||
|
}) feeFor;
|
||||||
|
final bool isSelected;
|
||||||
|
|
||||||
|
@override
|
||||||
|
ConsumerState<DesktopFeeItem> createState() => _DesktopFeeItemState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DesktopFeeItemState extends ConsumerState<DesktopFeeItem> {
|
||||||
|
String? feeString;
|
||||||
|
String? timeString;
|
||||||
|
|
||||||
|
static const stringsToLoopThrough = [
|
||||||
|
"Calculating",
|
||||||
|
"Calculating.",
|
||||||
|
"Calculating..",
|
||||||
|
"Calculating...",
|
||||||
|
];
|
||||||
|
|
||||||
|
String estimatedTimeToBeIncludedInNextBlock(
|
||||||
|
int targetBlockTime, int estimatedNumberOfBlocks) {
|
||||||
|
int time = targetBlockTime * estimatedNumberOfBlocks;
|
||||||
|
|
||||||
|
int hours = (time / 3600).floor();
|
||||||
|
if (hours > 1) {
|
||||||
|
return "~$hours hours";
|
||||||
|
} else if (hours == 1) {
|
||||||
|
return "~$hours hour";
|
||||||
|
}
|
||||||
|
|
||||||
|
// less than an hour
|
||||||
|
|
||||||
|
final string = (time / 60).toStringAsFixed(1);
|
||||||
|
|
||||||
|
if (string == "1.0") {
|
||||||
|
return "~1 minute";
|
||||||
|
} else {
|
||||||
|
if (string.endsWith(".0")) {
|
||||||
|
return "~${(time / 60).floor()} minutes";
|
||||||
|
}
|
||||||
|
return "~$string minutes";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
debugPrint("BUILD: $runtimeType : ${widget.feeRateType}");
|
||||||
|
|
||||||
|
return MaterialButton(
|
||||||
|
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop(
|
||||||
|
(
|
||||||
|
widget.feeRateType,
|
||||||
|
feeString,
|
||||||
|
timeString,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Builder(
|
||||||
|
builder: (_) {
|
||||||
|
if (widget.feeRateType == FeeRateType.custom) {
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
widget.feeRateType.prettyName,
|
||||||
|
style:
|
||||||
|
STextStyles.desktopTextExtraExtraSmall(context).copyWith(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.textFieldActiveText,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.left,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final manager = ref.watch(walletsChangeNotifierProvider
|
||||||
|
.select((value) => value.getManager(widget.walletId)));
|
||||||
|
|
||||||
|
if (widget.feeObject == null) {
|
||||||
|
return AnimatedText(
|
||||||
|
stringsToLoopThrough: stringsToLoopThrough,
|
||||||
|
style: STextStyles.desktopTextExtraExtraSmall(context).copyWith(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.textFieldActiveText,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return FutureBuilder(
|
||||||
|
future: widget.feeFor(
|
||||||
|
coin: manager.coin,
|
||||||
|
feeRateType: widget.feeRateType,
|
||||||
|
feeRate: widget.feeRateType == FeeRateType.fast
|
||||||
|
? widget.feeObject!.fast
|
||||||
|
: widget.feeRateType == FeeRateType.slow
|
||||||
|
? widget.feeObject!.slow
|
||||||
|
: widget.feeObject!.medium,
|
||||||
|
amount: ref.watch(sendAmountProvider.state).state,
|
||||||
|
),
|
||||||
|
builder: (_, AsyncSnapshot<Amount> snapshot) {
|
||||||
|
if (snapshot.connectionState == ConnectionState.done &&
|
||||||
|
snapshot.hasData) {
|
||||||
|
feeString = "${widget.feeRateType.prettyName} "
|
||||||
|
"(~${ref.watch(pAmountFormatter(manager.coin)).format(
|
||||||
|
snapshot.data!,
|
||||||
|
indicatePrecisionLoss: false,
|
||||||
|
)})";
|
||||||
|
|
||||||
|
timeString = manager.coin == Coin.ethereum
|
||||||
|
? ""
|
||||||
|
: estimatedTimeToBeIncludedInNextBlock(
|
||||||
|
Constants.targetBlockTimeInSeconds(manager.coin),
|
||||||
|
widget.feeRateType == FeeRateType.fast
|
||||||
|
? widget.feeObject!.numberOfBlocksFast
|
||||||
|
: widget.feeRateType == FeeRateType.slow
|
||||||
|
? widget.feeObject!.numberOfBlocksSlow
|
||||||
|
: widget.feeObject!.numberOfBlocksAverage,
|
||||||
|
);
|
||||||
|
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
feeString!,
|
||||||
|
style: STextStyles.desktopTextExtraExtraSmall(context)
|
||||||
|
.copyWith(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.textFieldActiveText,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.left,
|
||||||
|
),
|
||||||
|
if (widget.feeObject != null)
|
||||||
|
Text(
|
||||||
|
timeString!,
|
||||||
|
style: STextStyles.desktopTextExtraExtraSmall(context)
|
||||||
|
.copyWith(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.textFieldActiveSearchIconRight,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return AnimatedText(
|
||||||
|
stringsToLoopThrough: stringsToLoopThrough,
|
||||||
|
style: STextStyles.desktopTextExtraExtraSmall(context)
|
||||||
|
.copyWith(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.textFieldActiveText,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue