WIP desktop fee dropdown

This commit is contained in:
julian 2022-11-15 14:56:05 -06:00
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

View file

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

View file

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