From ec4f7d80e012199c94161c281bf381fdc97caec5 Mon Sep 17 00:00:00 2001 From: dethe <76167420+detherminal@users.noreply.github.com> Date: Tue, 8 Apr 2025 23:23:01 +0300 Subject: [PATCH 1/2] feat: add block height option to scanned coins --- .../name_your_wallet_view.dart | 2 +- .../restore_options_view.dart | 277 ++++++++++++++++-- .../restore_view_only_wallet_view.dart | 21 +- .../restore_wallet_view.dart | 39 +-- lib/route_generator.dart | 8 +- 5 files changed, 261 insertions(+), 86 deletions(-) diff --git a/lib/pages/add_wallet_views/name_your_wallet_view/name_your_wallet_view.dart b/lib/pages/add_wallet_views/name_your_wallet_view/name_your_wallet_view.dart index f17ed06ff..7f867d505 100644 --- a/lib/pages/add_wallet_views/name_your_wallet_view/name_your_wallet_view.dart +++ b/lib/pages/add_wallet_views/name_your_wallet_view/name_your_wallet_view.dart @@ -322,7 +322,7 @@ class _NameYourWalletViewState extends ConsumerState { ) : Semantics( label: - "Generate Random Wallet Name Button. Generates A Random Name For Wallet.", + "Clear Block Height Field Button. Clears the block height field.", excludeSemantics: true, child: XIcon( width: isDesktop ? 21 : 18, diff --git a/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart b/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart index 384cacb0c..b4a7d3231 100644 --- a/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart +++ b/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart @@ -12,6 +12,7 @@ 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:logger/logger.dart'; import 'package:tuple/tuple.dart'; import '../../../../pages_desktop_specific/my_stack_view/exit_to_my_stack_button.dart'; @@ -20,6 +21,7 @@ import '../../../../themes/stack_colors.dart'; import '../../../../utilities/assets.dart'; import '../../../../utilities/constants.dart'; import '../../../../utilities/format.dart'; +import '../../../../utilities/logger.dart'; import '../../../../utilities/text_styles.dart'; import '../../../../utilities/util.dart'; import '../../../../wallets/crypto_currency/crypto_currency.dart'; @@ -27,13 +29,16 @@ import '../../../../wallets/crypto_currency/interfaces/view_only_option_currency import '../../../../wallets/crypto_currency/intermediate/cryptonote_currency.dart'; import '../../../../widgets/conditional_parent.dart'; import '../../../../widgets/custom_buttons/app_bar_icon_button.dart'; +import '../../../../widgets/custom_buttons/blue_text_button.dart'; import '../../../../widgets/custom_buttons/checkbox_text_button.dart'; import '../../../../widgets/date_picker/date_picker.dart'; import '../../../../widgets/desktop/desktop_app_bar.dart'; import '../../../../widgets/desktop/desktop_scaffold.dart'; import '../../../../widgets/expandable.dart'; +import '../../../../widgets/icon_widgets/x_icon.dart'; import '../../../../widgets/rounded_white_container.dart'; import '../../../../widgets/stack_text_field.dart'; +import '../../../../widgets/textfield_icon_button.dart'; import '../../../../widgets/toggle.dart'; import '../../create_or_restore_wallet_view/sub_widgets/coin_image.dart'; import '../restore_view_only_wallet_view.dart'; @@ -44,6 +49,8 @@ import 'sub_widgets/restore_from_date_picker.dart'; import 'sub_widgets/restore_options_next_button.dart'; import 'sub_widgets/restore_options_platform_layout.dart'; +import 'package:cs_monero/src/deprecated/get_height_by_date.dart' as cs_monero_deprecated; + class RestoreOptionsView extends ConsumerStatefulWidget { const RestoreOptionsView({ super.key, @@ -66,6 +73,9 @@ class _RestoreOptionsViewState extends ConsumerState { late final bool isDesktop; late TextEditingController _dateController; + late TextEditingController _blockHeightController; + late FocusNode _blockHeightFocusNode; + final ValueNotifier _isUsingDateNotifier = ValueNotifier(true); late FocusNode textFieldFocusNode; late final FocusNode passwordFocusNode; late final TextEditingController passwordController; @@ -89,6 +99,9 @@ class _RestoreOptionsViewState extends ConsumerState { textFieldFocusNode = FocusNode(); passwordController = TextEditingController(); passwordFocusNode = FocusNode(); + _blockHeightController = TextEditingController(); + _blockHeightFocusNode = FocusNode(); + _isUsingDateNotifier.value = true; super.initState(); } @@ -96,6 +109,7 @@ class _RestoreOptionsViewState extends ConsumerState { @override void dispose() { _dateController.dispose(); + _blockHeightController.dispose(); textFieldFocusNode.dispose(); passwordController.dispose(); passwordFocusNode.dispose(); @@ -116,6 +130,12 @@ class _RestoreOptionsViewState extends ConsumerState { } if (mounted) { + int height = 0; + if (_isUsingDateNotifier.value) { + height = getBlockHeightFromDate(_restoreFromDate); + } else { + height = int.tryParse(_blockHeightController.text) ?? 0; + } if (!_showViewOnlyOption) { await Navigator.of(context).pushNamed( RestoreWalletView.routeName, @@ -123,7 +143,7 @@ class _RestoreOptionsViewState extends ConsumerState { walletName, coin, ref.read(mnemonicWordCountStateProvider.state).state, - _restoreFromDate, + height, passwordController.text, enableLelantusScanning, ), @@ -134,7 +154,7 @@ class _RestoreOptionsViewState extends ConsumerState { arguments: ( walletName: walletName, coin: coin, - restoreFromDate: _restoreFromDate, + restoreBlockHeight: height, enableLelantusScanning: enableLelantusScanning, ), ); @@ -186,6 +206,48 @@ class _RestoreOptionsViewState extends ConsumerState { ); } + int getBlockHeightFromDate(DateTime? date) { + try { + int height = 0; + if (date != null) { + if (widget.coin is Monero) { + height = cs_monero_deprecated.getMoneroHeightByDate( + date: date, + ); + } + if (widget.coin is Wownero) { + height = cs_monero_deprecated.getWowneroHeightByDate( + date: date, + ); + } + if (height < 0) { + height = 0; + } + + if (widget.coin is Epiccash) { + final int secondsSinceEpoch = + date.millisecondsSinceEpoch ~/ 1000; + const int epicCashFirstBlock = 1565370278; + const double overestimateSecondsPerBlock = 61; + final int chosenSeconds = secondsSinceEpoch - epicCashFirstBlock; + final int approximateHeight = + chosenSeconds ~/ overestimateSecondsPerBlock; + + height = approximateHeight; + if (height < 0) { + height = 0; + } + } + } else { + height = 0; + } + return height; + } catch (e) { + Logging.instance.log(Level.info, "Error getting block height from date: $e"); + return 0; + } + } + bool _showViewOnlyOption = false; @override @@ -281,10 +343,16 @@ class _RestoreOptionsViewState extends ConsumerState { dateController: _dateController, dateChooserFunction: isDesktop ? chooseDesktopDate : chooseDate, + blockHeightController: _blockHeightController, + blockHeightFocusNode: _blockHeightFocusNode, + isUsingDateNotifier: _isUsingDateNotifier, ) : SeedRestoreOption( coin: coin, dateController: _dateController, + blockHeightController: _blockHeightController, + blockHeightFocusNode: _blockHeightFocusNode, + isUsingDateNotifier: _isUsingDateNotifier, pwController: passwordController, pwFocusNode: passwordFocusNode, supportsMnemonicPassphrase: supportsMnemonicPassphrase, @@ -324,6 +392,9 @@ class SeedRestoreOption extends ConsumerStatefulWidget { super.key, required this.coin, required this.dateController, + required this.blockHeightController, + required this.blockHeightFocusNode, + required this.isUsingDateNotifier, required this.pwController, required this.pwFocusNode, required this.supportsMnemonicPassphrase, @@ -334,6 +405,9 @@ class SeedRestoreOption extends ConsumerStatefulWidget { final CryptoCurrency coin; final TextEditingController dateController; + final TextEditingController blockHeightController; + final FocusNode blockHeightFocusNode; + final ValueNotifier isUsingDateNotifier; final TextEditingController pwController; final FocusNode pwFocusNode; final bool supportsMnemonicPassphrase; @@ -350,6 +424,7 @@ class _SeedRestoreOptionState extends ConsumerState { bool _hidePassword = true; bool _expandedAdvanced = false; bool _enableLelantusScanning = false; + bool _blockFieldEmpty = true; @override Widget build(BuildContext context) { @@ -363,25 +438,86 @@ class _SeedRestoreOptionState extends ConsumerState { return Column( children: [ if (isMoneroAnd25 || widget.coin is Epiccash || isWowneroAnd25) - Text( - "Choose start date", - style: Util.isDesktop - ? STextStyles.desktopTextExtraSmall(context).copyWith( - color: - Theme.of(context).extension()!.textDark3, - ) - : STextStyles.smallMed12(context), - textAlign: TextAlign.left, + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + widget.isUsingDateNotifier.value + ? "Choose start date" + : "Block height", + style: Util.isDesktop + ? STextStyles.desktopTextExtraSmall(context).copyWith( + color: + Theme.of(context).extension()!.textDark3, + ) + : STextStyles.smallMed12(context), + textAlign: TextAlign.left, + ), + CustomTextButton( + text: widget.isUsingDateNotifier.value + ? "Use block height" + : "Use date", + onTap: () { + setState(() { + widget.isUsingDateNotifier.value = + !widget.isUsingDateNotifier.value; + }); + }, + ), + ], ), if (isMoneroAnd25 || widget.coin is Epiccash || isWowneroAnd25) SizedBox( height: Util.isDesktop ? 16 : 8, ), if (isMoneroAnd25 || widget.coin is Epiccash || isWowneroAnd25) + widget.isUsingDateNotifier.value ? RestoreFromDatePicker( onTap: widget.dateChooserFunction, controller: widget.dateController, - ), + ) : + TextField( + focusNode: widget.blockHeightFocusNode, + controller: widget.blockHeightController, + keyboardType: TextInputType.number, + textInputAction: TextInputAction.done, + style: Util.isDesktop + ? STextStyles.desktopTextMedium(context).copyWith( + height: 2, + ) + : STextStyles.field(context), + onChanged: (value) { + setState(() { + _blockFieldEmpty = value.isEmpty; + }); + }, + decoration: standardInputDecoration( + "Start scanning from...", + widget.blockHeightFocusNode, + context, + ).copyWith( + suffixIcon: UnconstrainedBox( + child: TextFieldIconButton(child: + Semantics( + label: "Clear Block Height Field Button. Clears the block height field", + excludeSemantics: true, + child: !_blockFieldEmpty + ? XIcon( + width: Util.isDesktop ? 24 : 16, + height: Util.isDesktop ? 24 : 16, + ) + : const SizedBox.shrink(), + ), + onTap: () { + widget.blockHeightController.text = ""; + setState(() { + _blockFieldEmpty = true; + }); + }, + ), + ), + ), + ), if (isMoneroAnd25 || widget.coin is Epiccash || isWowneroAnd25) const SizedBox( height: 8, @@ -390,7 +526,9 @@ class _SeedRestoreOptionState extends ConsumerState { RoundedWhiteContainer( child: Center( child: Text( - "Choose the date you made the wallet (approximate is fine)", + widget.isUsingDateNotifier.value + ? "Choose the date you made the wallet (approximate is fine)" + : "Enter the initial block height of the wallet", style: Util.isDesktop ? STextStyles.desktopTextExtraSmall(context).copyWith( color: Theme.of(context) @@ -635,6 +773,15 @@ class _SeedRestoreOptionState extends ConsumerState { ], ); } + + @override + void initState() { + super.initState(); + + setState(() { + _blockFieldEmpty = widget.blockHeightController.text.isEmpty; + }); + } } class ViewOnlyRestoreOption extends StatefulWidget { @@ -643,10 +790,16 @@ class ViewOnlyRestoreOption extends StatefulWidget { required this.coin, required this.dateController, required this.dateChooserFunction, + required this.blockHeightController, + required this.blockHeightFocusNode, + required this.isUsingDateNotifier, }); final CryptoCurrency coin; final TextEditingController dateController; + final TextEditingController blockHeightController; + final FocusNode blockHeightFocusNode; + final ValueNotifier isUsingDateNotifier; final Future Function() dateChooserFunction; @@ -655,31 +808,90 @@ class ViewOnlyRestoreOption extends StatefulWidget { } class _ViewOnlyRestoreOptionState extends State { + bool _blockFieldEmpty = true; + @override Widget build(BuildContext context) { final showDateOption = widget.coin is CryptonoteCurrency; return Column( children: [ if (showDateOption) - Text( - "Choose start date", - style: Util.isDesktop - ? STextStyles.desktopTextExtraSmall(context).copyWith( - color: - Theme.of(context).extension()!.textDark3, - ) - : STextStyles.smallMed12(context), - textAlign: TextAlign.left, + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + widget.isUsingDateNotifier.value + ? "Choose start date" + : "Block height", + style: Util.isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context).copyWith( + color: + Theme.of(context).extension()!.textDark3, + ) + : STextStyles.smallMed12(context), + textAlign: TextAlign.left, + ), + CustomTextButton( + text: widget.isUsingDateNotifier.value + ? "Use block height" + : "Use date", + onTap: () { + setState(() { + widget.isUsingDateNotifier.value = + !widget.isUsingDateNotifier.value; + }); + }, + ), + ], ), if (showDateOption) SizedBox( height: Util.isDesktop ? 16 : 8, ), if (showDateOption) - RestoreFromDatePicker( - onTap: widget.dateChooserFunction, - controller: widget.dateController, - ), + widget.isUsingDateNotifier.value + ? RestoreFromDatePicker( + onTap: widget.dateChooserFunction, + controller: widget.dateController, + ) + : TextField( + focusNode: widget.blockHeightFocusNode, + controller: widget.blockHeightController, + keyboardType: TextInputType.number, + textInputAction: TextInputAction.done, + style: Util.isDesktop + ? STextStyles.desktopTextMedium(context).copyWith( + height: 2, + ) + : STextStyles.field(context), + decoration: standardInputDecoration( + "Start scanning from...", + widget.blockHeightFocusNode, + context, + ).copyWith( + suffixIcon: UnconstrainedBox( + child: TextFieldIconButton( + child: Semantics( + label: + "Clear Block Height Field Button. Clears the block height field", + excludeSemantics: true, + child: !_blockFieldEmpty + ? XIcon( + width: Util.isDesktop ? 24 : 16, + height: Util.isDesktop ? 24 : 16, + ) + : const SizedBox.shrink(), + ), + onTap: () { + widget.blockHeightController.text = ""; + setState(() { + _blockFieldEmpty = true; + }); + }, + ), + ), + ), + ), if (showDateOption) const SizedBox( height: 8, @@ -688,7 +900,9 @@ class _ViewOnlyRestoreOptionState extends State { RoundedWhiteContainer( child: Center( child: Text( - "Choose the date you made the wallet (approximate is fine)", + widget.isUsingDateNotifier.value + ? "Choose the date you made the wallet (approximate is fine)" + : "Enter the initial block height of the wallet", style: Util.isDesktop ? STextStyles.desktopTextExtraSmall(context).copyWith( color: Theme.of(context) @@ -708,4 +922,13 @@ class _ViewOnlyRestoreOptionState extends State { ], ); } + + @override + void initState() { + super.initState(); + + setState(() { + _blockFieldEmpty = widget.blockHeightController.text.isEmpty; + }); + } } diff --git a/lib/pages/add_wallet_views/restore_wallet_view/restore_view_only_wallet_view.dart b/lib/pages/add_wallet_views/restore_wallet_view/restore_view_only_wallet_view.dart index 9123b2f46..b4b7d03e3 100644 --- a/lib/pages/add_wallet_views/restore_wallet_view/restore_view_only_wallet_view.dart +++ b/lib/pages/add_wallet_views/restore_wallet_view/restore_view_only_wallet_view.dart @@ -51,7 +51,7 @@ class RestoreViewOnlyWalletView extends ConsumerStatefulWidget { super.key, required this.walletName, required this.coin, - required this.restoreFromDate, + required this.restoreBlockHeight, this.enableLelantusScanning = false, this.barcodeScanner = const BarcodeScannerWrapper(), this.clipboard = const ClipboardWrapper(), @@ -61,7 +61,7 @@ class RestoreViewOnlyWalletView extends ConsumerStatefulWidget { final String walletName; final CryptoCurrency coin; - final DateTime? restoreFromDate; + final int restoreBlockHeight; final bool enableLelantusScanning; final BarcodeScannerInterface barcodeScanner; final ClipboardInterface clipboard; @@ -114,7 +114,6 @@ class _RestoreViewOnlyWalletViewState } Future _attemptRestore() async { - int height = 0; final Map otherDataJson = { WalletInfoKeys.isViewOnlyKey: true, }; @@ -134,20 +133,6 @@ class _RestoreViewOnlyWalletViewState ? ViewOnlyWalletType.addressOnly : ViewOnlyWalletType.xPub; } else if (widget.coin is CryptonoteCurrency) { - if (widget.restoreFromDate != null) { - if (widget.coin is Monero) { - height = cs_monero_deprecated.getMoneroHeightByDate( - date: widget.restoreFromDate!, - ); - } - if (widget.coin is Wownero) { - height = cs_monero_deprecated.getWowneroHeightByDate( - date: widget.restoreFromDate!, - ); - } - if (height < 0) height = 0; - } - viewOnlyWalletType = ViewOnlyWalletType.cryptonote; } else { throw Exception( @@ -163,7 +148,7 @@ class _RestoreViewOnlyWalletViewState final info = WalletInfo.createNew( coin: widget.coin, name: widget.walletName, - restoreHeight: height, + restoreHeight: widget.restoreBlockHeight, otherDataJsonString: jsonEncode(otherDataJson), ); diff --git a/lib/pages/add_wallet_views/restore_wallet_view/restore_wallet_view.dart b/lib/pages/add_wallet_views/restore_wallet_view/restore_wallet_view.dart index 972012c00..59d479112 100644 --- a/lib/pages/add_wallet_views/restore_wallet_view/restore_wallet_view.dart +++ b/lib/pages/add_wallet_views/restore_wallet_view/restore_wallet_view.dart @@ -17,8 +17,6 @@ import 'dart:math'; import 'package:bip39/bip39.dart' as bip39; import 'package:bip39/src/wordlists/english.dart' as bip39wordlist; import 'package:cs_monero/cs_monero.dart' as lib_monero; -import 'package:cs_monero/src/deprecated/get_height_by_date.dart' - as cs_monero_deprecated; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -79,7 +77,7 @@ class RestoreWalletView extends ConsumerStatefulWidget { required this.coin, required this.seedWordsLength, required this.mnemonicPassphrase, - required this.restoreFromDate, + required this.restoreBlockHeight, this.enableLelantusScanning = false, this.barcodeScanner = const BarcodeScannerWrapper(), this.clipboard = const ClipboardWrapper(), @@ -91,7 +89,7 @@ class RestoreWalletView extends ConsumerStatefulWidget { final CryptoCurrency coin; final String mnemonicPassphrase; final int seedWordsLength; - final DateTime? restoreFromDate; + final int restoreBlockHeight; final bool enableLelantusScanning; final BarcodeScannerInterface barcodeScanner; @@ -233,42 +231,11 @@ class _RestoreWalletViewState extends ConsumerState { } mnemonic = mnemonic.trim(); - int height = 0; + final int height = widget.restoreBlockHeight; String? otherDataJsonString; - if (widget.restoreFromDate != null) { - if (widget.coin is Monero) { - height = cs_monero_deprecated.getMoneroHeightByDate( - date: widget.restoreFromDate!, - ); - } - if (widget.coin is Wownero) { - height = cs_monero_deprecated.getWowneroHeightByDate( - date: widget.restoreFromDate!, - ); - } - if (height < 0) { - height = 0; - } - } - // TODO: make more robust estimate of date maybe using https://explorer.epic.tech/api-index if (widget.coin is Epiccash) { - if (widget.restoreFromDate != null) { - final int secondsSinceEpoch = - widget.restoreFromDate!.millisecondsSinceEpoch ~/ 1000; - const int epicCashFirstBlock = 1565370278; - const double overestimateSecondsPerBlock = 61; - final int chosenSeconds = secondsSinceEpoch - epicCashFirstBlock; - final int approximateHeight = - chosenSeconds ~/ overestimateSecondsPerBlock; - - height = approximateHeight; - } - if (height < 0) { - height = 0; - } - otherDataJsonString = jsonEncode( { WalletInfoKeys.epiccashData: jsonEncode( diff --git a/lib/route_generator.dart b/lib/route_generator.dart index 6695a53b8..eaa8c1c06 100644 --- a/lib/route_generator.dart +++ b/lib/route_generator.dart @@ -1554,14 +1554,14 @@ class RouteGenerator { case RestoreWalletView.routeName: if (args - is Tuple6) { + is Tuple6) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, builder: (_) => RestoreWalletView( walletName: args.item1, coin: args.item2, seedWordsLength: args.item3, - restoreFromDate: args.item4, + restoreBlockHeight: args.item4, mnemonicPassphrase: args.item5, enableLelantusScanning: args.item6 ?? false, ), @@ -1576,7 +1576,7 @@ class RouteGenerator { if (args is ({ String walletName, CryptoCurrency coin, - DateTime? restoreFromDate, + int restoreBlockHeight, bool enableLelantusScanning, })) { return getRoute( @@ -1584,7 +1584,7 @@ class RouteGenerator { builder: (_) => RestoreViewOnlyWalletView( walletName: args.walletName, coin: args.coin, - restoreFromDate: args.restoreFromDate, + restoreBlockHeight: args.restoreBlockHeight, enableLelantusScanning: args.enableLelantusScanning, ), settings: RouteSettings( From 634439047697dbfd53db3c9f1dacb3e5f89dd83f Mon Sep 17 00:00:00 2001 From: dethe <76167420+detherminal@users.noreply.github.com> Date: Fri, 25 Apr 2025 19:50:09 +0300 Subject: [PATCH 2/2] refactor: states and views of restore options --- .../name_your_wallet_view.dart | 2 +- .../restore_options_view.dart | 164 ++++++++++-------- 2 files changed, 92 insertions(+), 74 deletions(-) diff --git a/lib/pages/add_wallet_views/name_your_wallet_view/name_your_wallet_view.dart b/lib/pages/add_wallet_views/name_your_wallet_view/name_your_wallet_view.dart index 7f867d505..91aa555f6 100644 --- a/lib/pages/add_wallet_views/name_your_wallet_view/name_your_wallet_view.dart +++ b/lib/pages/add_wallet_views/name_your_wallet_view/name_your_wallet_view.dart @@ -322,7 +322,7 @@ class _NameYourWalletViewState extends ConsumerState { ) : Semantics( label: - "Clear Block Height Field Button. Clears the block height field.", + "Clear Wallet Name Field Button. Clears the wallet name field.", excludeSemantics: true, child: XIcon( width: isDesktop ? 21 : 18, diff --git a/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart b/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart index b4a7d3231..a7fe47d6e 100644 --- a/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart +++ b/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart @@ -10,6 +10,7 @@ import 'package:dropdown_button2/dropdown_button2.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:logger/logger.dart'; @@ -476,44 +477,52 @@ class _SeedRestoreOptionState extends ConsumerState { onTap: widget.dateChooserFunction, controller: widget.dateController, ) : - TextField( - focusNode: widget.blockHeightFocusNode, - controller: widget.blockHeightController, - keyboardType: TextInputType.number, - textInputAction: TextInputAction.done, - style: Util.isDesktop - ? STextStyles.desktopTextMedium(context).copyWith( - height: 2, - ) - : STextStyles.field(context), - onChanged: (value) { - setState(() { - _blockFieldEmpty = value.isEmpty; - }); - }, - decoration: standardInputDecoration( - "Start scanning from...", - widget.blockHeightFocusNode, - context, - ).copyWith( - suffixIcon: UnconstrainedBox( - child: TextFieldIconButton(child: - Semantics( + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + focusNode: widget.blockHeightFocusNode, + controller: widget.blockHeightController, + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly, + ], + textInputAction: TextInputAction.done, + style: Util.isDesktop + ? STextStyles.desktopTextMedium(context).copyWith( + height: 2, + ) + : STextStyles.field(context), + onChanged: (value) { + setState(() { + _blockFieldEmpty = value.isEmpty; + }); + }, + decoration: standardInputDecoration( + "Start scanning from...", + widget.blockHeightFocusNode, + context, + ).copyWith( + suffixIcon: UnconstrainedBox( + child: TextFieldIconButton(child: + Semantics( label: "Clear Block Height Field Button. Clears the block height field", excludeSemantics: true, child: !_blockFieldEmpty ? XIcon( - width: Util.isDesktop ? 24 : 16, - height: Util.isDesktop ? 24 : 16, - ) + width: Util.isDesktop ? 24 : 16, + height: Util.isDesktop ? 24 : 16, + ) : const SizedBox.shrink(), ), - onTap: () { - widget.blockHeightController.text = ""; - setState(() { - _blockFieldEmpty = true; - }); - }, + onTap: () { + widget.blockHeightController.text = ""; + setState(() { + _blockFieldEmpty = true; + }); + }, + ), ), ), ), @@ -778,9 +787,7 @@ class _SeedRestoreOptionState extends ConsumerState { void initState() { super.initState(); - setState(() { - _blockFieldEmpty = widget.blockHeightController.text.isEmpty; - }); + _blockFieldEmpty = widget.blockHeightController.text.isEmpty; } } @@ -854,44 +861,57 @@ class _ViewOnlyRestoreOptionState extends State { onTap: widget.dateChooserFunction, controller: widget.dateController, ) - : TextField( - focusNode: widget.blockHeightFocusNode, - controller: widget.blockHeightController, - keyboardType: TextInputType.number, - textInputAction: TextInputAction.done, - style: Util.isDesktop - ? STextStyles.desktopTextMedium(context).copyWith( - height: 2, - ) - : STextStyles.field(context), - decoration: standardInputDecoration( - "Start scanning from...", - widget.blockHeightFocusNode, - context, - ).copyWith( - suffixIcon: UnconstrainedBox( - child: TextFieldIconButton( - child: Semantics( - label: - "Clear Block Height Field Button. Clears the block height field", - excludeSemantics: true, - child: !_blockFieldEmpty - ? XIcon( - width: Util.isDesktop ? 24 : 16, - height: Util.isDesktop ? 24 : 16, - ) - : const SizedBox.shrink(), - ), - onTap: () { - widget.blockHeightController.text = ""; - setState(() { - _blockFieldEmpty = true; - }); - }, - ), + : ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + focusNode: widget.blockHeightFocusNode, + controller: widget.blockHeightController, + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly, + ], + textInputAction: TextInputAction.done, + style: Util.isDesktop + ? STextStyles.desktopTextMedium(context).copyWith( + height: 2, + ) + : STextStyles.field(context), + onChanged: (value) { + setState(() { + _blockFieldEmpty = value.isEmpty; + }); + }, + decoration: standardInputDecoration( + "Start scanning from...", + widget.blockHeightFocusNode, + context, + ).copyWith( + suffixIcon: UnconstrainedBox( + child: TextFieldIconButton( + child: Semantics( + label: + "Clear Block Height Field Button. Clears the block height field", + excludeSemantics: true, + child: !_blockFieldEmpty + ? XIcon( + width: Util.isDesktop ? 24 : 16, + height: Util.isDesktop ? 24 : 16, + ) + : const SizedBox.shrink(), ), + onTap: () { + widget.blockHeightController.text = ""; + setState(() { + _blockFieldEmpty = true; + }); + }, ), ), + ), + ), + ), if (showDateOption) const SizedBox( height: 8, @@ -927,8 +947,6 @@ class _ViewOnlyRestoreOptionState extends State { void initState() { super.initState(); - setState(() { - _blockFieldEmpty = widget.blockHeightController.text.isEmpty; - }); + _blockFieldEmpty = widget.blockHeightController.text.isEmpty; } }