mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2025-04-04 13:27:35 +00:00
WIP desktop fee dropdown
This commit is contained in:
parent
a48975940d
commit
5211617082
2 changed files with 407 additions and 34 deletions
lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets
|
@ -0,0 +1,369 @@
|
|||
import 'package:decimal/decimal.dart';
|
||||
import 'package:dropdown_button2/dropdown_button2.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:stackwallet/models/models.dart';
|
||||
import 'package:stackwallet/pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart';
|
||||
import 'package:stackwallet/providers/global/wallets_provider.dart';
|
||||
import 'package:stackwallet/providers/ui/fee_rate_type_state_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/utilities/assets.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/format.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/utilities/theme/stack_colors.dart';
|
||||
import 'package:stackwallet/widgets/animated_text.dart';
|
||||
|
||||
class DesktopFeeDropDown extends ConsumerStatefulWidget {
|
||||
const DesktopFeeDropDown({
|
||||
Key? key,
|
||||
required this.walletId,
|
||||
}) : super(key: key);
|
||||
|
||||
final String walletId;
|
||||
|
||||
@override
|
||||
ConsumerState<DesktopFeeDropDown> createState() => _DesktopFeeDropDownState();
|
||||
}
|
||||
|
||||
class _DesktopFeeDropDownState extends ConsumerState<DesktopFeeDropDown> {
|
||||
late final String walletId;
|
||||
|
||||
FeeObject? feeObject;
|
||||
FeeRateType feeRateType = FeeRateType.average;
|
||||
|
||||
final stringsToLoopThrough = [
|
||||
"Calculating",
|
||||
"Calculating.",
|
||||
"Calculating..",
|
||||
"Calculating...",
|
||||
];
|
||||
|
||||
Future<Decimal> feeFor({
|
||||
required int amount,
|
||||
required FeeRateType feeRateType,
|
||||
required int feeRate,
|
||||
required Coin coin,
|
||||
}) async {
|
||||
switch (feeRateType) {
|
||||
case FeeRateType.fast:
|
||||
if (ref.read(feeSheetSessionCacheProvider).fast[amount] == null) {
|
||||
final manager =
|
||||
ref.read(walletsChangeNotifierProvider).getManager(walletId);
|
||||
|
||||
if ((coin == Coin.firo || coin == Coin.firoTestNet) &&
|
||||
ref.read(publicPrivateBalanceStateProvider.state).state !=
|
||||
"Private") {
|
||||
ref.read(feeSheetSessionCacheProvider).fast[amount] =
|
||||
Format.satoshisToAmount(await (manager.wallet as FiroWallet)
|
||||
.estimateFeeForPublic(amount, feeRate));
|
||||
} else {
|
||||
ref.read(feeSheetSessionCacheProvider).fast[amount] =
|
||||
Format.satoshisToAmount(
|
||||
await manager.estimateFeeFor(amount, feeRate));
|
||||
}
|
||||
}
|
||||
return ref.read(feeSheetSessionCacheProvider).fast[amount]!;
|
||||
|
||||
case FeeRateType.average:
|
||||
if (ref.read(feeSheetSessionCacheProvider).average[amount] == null) {
|
||||
final manager =
|
||||
ref.read(walletsChangeNotifierProvider).getManager(walletId);
|
||||
|
||||
if ((coin == Coin.firo || coin == Coin.firoTestNet) &&
|
||||
ref.read(publicPrivateBalanceStateProvider.state).state !=
|
||||
"Private") {
|
||||
ref.read(feeSheetSessionCacheProvider).average[amount] =
|
||||
Format.satoshisToAmount(await (manager.wallet as FiroWallet)
|
||||
.estimateFeeForPublic(amount, feeRate));
|
||||
} else {
|
||||
ref.read(feeSheetSessionCacheProvider).average[amount] =
|
||||
Format.satoshisToAmount(
|
||||
await manager.estimateFeeFor(amount, feeRate));
|
||||
}
|
||||
}
|
||||
return ref.read(feeSheetSessionCacheProvider).average[amount]!;
|
||||
|
||||
case FeeRateType.slow:
|
||||
if (ref.read(feeSheetSessionCacheProvider).slow[amount] == null) {
|
||||
final manager =
|
||||
ref.read(walletsChangeNotifierProvider).getManager(walletId);
|
||||
|
||||
if ((coin == Coin.firo || coin == Coin.firoTestNet) &&
|
||||
ref.read(publicPrivateBalanceStateProvider.state).state !=
|
||||
"Private") {
|
||||
ref.read(feeSheetSessionCacheProvider).slow[amount] =
|
||||
Format.satoshisToAmount(await (manager.wallet as FiroWallet)
|
||||
.estimateFeeForPublic(amount, feeRate));
|
||||
} else {
|
||||
ref.read(feeSheetSessionCacheProvider).slow[amount] =
|
||||
Format.satoshisToAmount(
|
||||
await manager.estimateFeeFor(amount, feeRate));
|
||||
}
|
||||
}
|
||||
return ref.read(feeSheetSessionCacheProvider).slow[amount]!;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
void initState() {
|
||||
walletId = widget.walletId;
|
||||
super.initState();
|
||||
}
|
||||
|
||||
String? labelSlow;
|
||||
String? labelAverage;
|
||||
String? labelFast;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
debugPrint("BUILD: $runtimeType");
|
||||
|
||||
final manager = ref.watch(walletsChangeNotifierProvider
|
||||
.select((value) => value.getManager(walletId)));
|
||||
|
||||
return FutureBuilder(
|
||||
future: manager.fees,
|
||||
builder: (context, AsyncSnapshot<FeeObject> snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done &&
|
||||
snapshot.hasData) {
|
||||
feeObject = snapshot.data!;
|
||||
}
|
||||
return DropdownButtonHideUnderline(
|
||||
child: DropdownButton2(
|
||||
offset: const Offset(0, -10),
|
||||
isExpanded: true,
|
||||
dropdownElevation: 0,
|
||||
value: ref.watch(feeRateTypeStateProvider.state).state,
|
||||
// selectedItemBuilder: (s) {
|
||||
// return [
|
||||
// ...FeeRateType.values.map(
|
||||
// (e) => DropdownMenuItem(
|
||||
// value: e,
|
||||
// child: FeeDropDownChild(
|
||||
// feeObject: feeObject,
|
||||
// feeRateType: e,
|
||||
// walletId: walletId,
|
||||
// amount: amount,
|
||||
// feeFor: feeFor,
|
||||
// isSelected: true,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ];
|
||||
// },
|
||||
items: [
|
||||
...FeeRateType.values.map(
|
||||
(e) => DropdownMenuItem(
|
||||
value: e,
|
||||
child: FeeDropDownChild(
|
||||
feeObject: feeObject,
|
||||
feeRateType: e,
|
||||
walletId: walletId,
|
||||
feeFor: feeFor,
|
||||
isSelected: false,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
onChanged: (newRateType) {
|
||||
if (newRateType is FeeRateType) {
|
||||
ref.read(feeRateTypeStateProvider.state).state = newRateType;
|
||||
}
|
||||
},
|
||||
icon: SvgPicture.asset(
|
||||
Assets.svg.chevronDown,
|
||||
width: 12,
|
||||
height: 6,
|
||||
color: Theme.of(context).extension<StackColors>()!.textDark3,
|
||||
),
|
||||
buttonPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 8,
|
||||
),
|
||||
buttonDecoration: BoxDecoration(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFieldDefaultBG,
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
),
|
||||
dropdownDecoration: BoxDecoration(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFieldDefaultBG,
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
final sendAmountProvider =
|
||||
StateProvider.autoDispose<Decimal>((_) => Decimal.zero);
|
||||
|
||||
class FeeDropDownChild extends ConsumerWidget {
|
||||
const FeeDropDownChild({
|
||||
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<Decimal> Function({
|
||||
required int amount,
|
||||
required FeeRateType feeRateType,
|
||||
required int feeRate,
|
||||
required Coin coin,
|
||||
}) feeFor;
|
||||
final bool isSelected;
|
||||
|
||||
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, WidgetRef ref) {
|
||||
debugPrint("BUILD: $runtimeType : $feeRateType");
|
||||
|
||||
final manager = ref.watch(walletsChangeNotifierProvider
|
||||
.select((value) => value.getManager(walletId)));
|
||||
|
||||
if (feeObject == null) {
|
||||
return AnimatedText(
|
||||
stringsToLoopThrough: stringsToLoopThrough,
|
||||
style: STextStyles.desktopTextExtraExtraSmall(context).copyWith(
|
||||
color:
|
||||
Theme.of(context).extension<StackColors>()!.textFieldActiveText,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return FutureBuilder(
|
||||
future: feeFor(
|
||||
coin: manager.coin,
|
||||
feeRateType: FeeRateType.fast,
|
||||
feeRate: feeRateType == FeeRateType.fast
|
||||
? feeObject!.fast
|
||||
: feeRateType == FeeRateType.slow
|
||||
? feeObject!.slow
|
||||
: feeObject!.medium,
|
||||
amount: Format.decimalAmountToSatoshis(
|
||||
ref.watch(sendAmountProvider.state).state,
|
||||
),
|
||||
),
|
||||
builder: (_, AsyncSnapshot<Decimal> snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done &&
|
||||
snapshot.hasData) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
"${feeRateType.prettyName} (~${snapshot.data!} ${manager.coin.ticker})",
|
||||
style:
|
||||
STextStyles.desktopTextExtraExtraSmall(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFieldActiveText,
|
||||
),
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
if (feeObject != null)
|
||||
Text(
|
||||
estimatedTimeToBeIncludedInNextBlock(
|
||||
Constants.targetBlockTimeInSeconds(manager.coin),
|
||||
feeRateType == FeeRateType.fast
|
||||
? feeObject!.numberOfBlocksFast
|
||||
: feeRateType == FeeRateType.slow
|
||||
? feeObject!.numberOfBlocksSlow
|
||||
: feeObject!.numberOfBlocksAverage,
|
||||
),
|
||||
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,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,6 +13,7 @@ import 'package:stackwallet/pages/send_view/sub_widgets/building_transaction_dia
|
|||
import 'package:stackwallet/pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart';
|
||||
import 'package:stackwallet/pages_desktop_specific/home/desktop_home_view.dart';
|
||||
import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/address_book_address_chooser/address_book_address_chooser.dart';
|
||||
import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_fee_dropdown.dart';
|
||||
import 'package:stackwallet/providers/providers.dart';
|
||||
import 'package:stackwallet/providers/ui/fee_rate_type_state_provider.dart';
|
||||
import 'package:stackwallet/providers/ui/preview_tx_button_state_provider.dart';
|
||||
|
@ -94,11 +95,6 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
|||
late VoidCallback onCryptoAmountChanged;
|
||||
|
||||
Future<void> previewSend() async {
|
||||
// wait for keyboard to disappear
|
||||
FocusScope.of(context).unfocus();
|
||||
await Future<void>.delayed(
|
||||
const Duration(milliseconds: 100),
|
||||
);
|
||||
final manager =
|
||||
ref.read(walletsChangeNotifierProvider).getManager(walletId);
|
||||
|
||||
|
@ -794,35 +790,25 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
|||
_addressToggleFlag = true;
|
||||
}
|
||||
|
||||
// _cryptoFocus.addListener(() {
|
||||
// if (!_cryptoFocus.hasFocus && !_baseFocus.hasFocus) {
|
||||
// if (_amountToSend == null) {
|
||||
// setState(() {
|
||||
// _calculateFeesFuture = calculateFees(0);
|
||||
// });
|
||||
// } else {
|
||||
// setState(() {
|
||||
// _calculateFeesFuture =
|
||||
// calculateFees(Format.decimalAmountToSatoshis(_amountToSend!));
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
//
|
||||
// _baseFocus.addListener(() {
|
||||
// if (!_cryptoFocus.hasFocus && !_baseFocus.hasFocus) {
|
||||
// if (_amountToSend == null) {
|
||||
// setState(() {
|
||||
// _calculateFeesFuture = calculateFees(0);
|
||||
// });
|
||||
// } else {
|
||||
// setState(() {
|
||||
// _calculateFeesFuture =
|
||||
// calculateFees(Format.decimalAmountToSatoshis(_amountToSend!));
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
_cryptoFocus.addListener(() {
|
||||
if (!_cryptoFocus.hasFocus && !_baseFocus.hasFocus) {
|
||||
if (_amountToSend == null) {
|
||||
ref.refresh(sendAmountProvider);
|
||||
} else {
|
||||
ref.read(sendAmountProvider.state).state = _amountToSend!;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
_baseFocus.addListener(() {
|
||||
if (!_cryptoFocus.hasFocus && !_baseFocus.hasFocus) {
|
||||
if (_amountToSend == null) {
|
||||
ref.refresh(sendAmountProvider);
|
||||
} else {
|
||||
ref.read(sendAmountProvider.state).state = _amountToSend!;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
@ -1371,6 +1357,24 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
|||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Text(
|
||||
"Transaction fee (estimated)",
|
||||
style: STextStyles.desktopTextExtraSmall(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFieldActiveSearchIconRight,
|
||||
),
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
DesktopFeeDropDown(
|
||||
walletId: walletId,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 36,
|
||||
),
|
||||
|
|
Loading…
Reference in a new issue