diff --git a/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/edit_refresh_height_view.dart b/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/edit_refresh_height_view.dart new file mode 100644 index 000000000..61d497452 --- /dev/null +++ b/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/edit_refresh_height_view.dart @@ -0,0 +1,238 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../../../notifications/show_flush_bar.dart'; +import '../../../../providers/db/main_db_provider.dart'; +import '../../../../providers/global/wallets_provider.dart'; +import '../../../../themes/stack_colors.dart'; +import '../../../../utilities/constants.dart'; +import '../../../../utilities/text_styles.dart'; +import '../../../../utilities/util.dart'; +import '../../../../wallets/wallet/intermediate/lib_monero_wallet.dart'; +import '../../../../widgets/background.dart'; +import '../../../../widgets/conditional_parent.dart'; +import '../../../../widgets/custom_buttons/app_bar_icon_button.dart'; +import '../../../../widgets/desktop/desktop_dialog.dart'; +import '../../../../widgets/desktop/desktop_dialog_close_button.dart'; +import '../../../../widgets/desktop/primary_button.dart'; +import '../../../../widgets/icon_widgets/x_icon.dart'; +import '../../../../widgets/stack_text_field.dart'; +import '../../../../widgets/textfield_icon_button.dart'; + +class EditRefreshHeightView extends ConsumerStatefulWidget { + const EditRefreshHeightView({ + super.key, + required this.walletId, + }); + + static const String routeName = "/editRefreshHeightView"; + + final String walletId; + + @override + ConsumerState<EditRefreshHeightView> createState() => + _EditRefreshHeightViewState(); +} + +class _EditRefreshHeightViewState extends ConsumerState<EditRefreshHeightView> { + late final LibMoneroWallet _wallet; + late final TextEditingController _controller; + final _focusNode = FocusNode(); + + bool _saveLock = false; + + void _save() async { + if (_saveLock) return; + _saveLock = true; + try { + String? errMessage; + try { + final newHeight = int.tryParse(_controller.text); + if (newHeight != null && newHeight >= 0) { + await _wallet.info.updateRestoreHeight( + newRestoreHeight: newHeight, + isar: ref.read(mainDBProvider).isar, + ); + _wallet.libMoneroWallet!.setRefreshFromBlockHeight(newHeight); + } else { + errMessage = "Invalid height: ${_controller.text}"; + } + } catch (e) { + errMessage = e.toString(); + } + + if (mounted) { + if (errMessage == null) { + Navigator.of(context).pop(); + unawaited( + showFloatingFlushBar( + type: FlushBarType.success, + message: "Refresh height updated", + context: context, + ), + ); + } else { + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: errMessage, + context: context, + ), + ); + } + } + } finally { + _saveLock = false; + } + } + + @override + void initState() { + super.initState(); + _wallet = ref.read(pWallets).getWallet(widget.walletId) as LibMoneroWallet; + _controller = TextEditingController() + ..text = _wallet.libMoneroWallet!.getRefreshFromBlockHeight().toString(); + } + + @override + void dispose() { + _controller.dispose(); + _focusNode.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return ConditionalParent( + condition: Util.isDesktop, + builder: (child) { + return DesktopDialog( + maxWidth: 500, + maxHeight: double.infinity, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + DesktopDialogCloseButton( + onPressedOverride: Navigator.of( + context, + rootNavigator: true, + ).pop, + ), + ], + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 32), + child: child, + ), + const SizedBox( + height: 32, + ), + ], + ), + ); + }, + child: ConditionalParent( + condition: !Util.isDesktop, + builder: (child) { + return Background( + child: Scaffold( + backgroundColor: + Theme.of(context).extension<StackColors>()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () { + Navigator.of(context).pop(); + }, + ), + title: Text( + "Restore height", + style: STextStyles.navBarTitle(context), + ), + ), + body: Padding( + padding: const EdgeInsets.all(16), + child: child, + ), + ), + ); + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + key: const Key("restoreHeightFieldKey"), + controller: _controller, + focusNode: _focusNode, + style: Util.isDesktop + ? STextStyles.desktopTextMedium(context).copyWith( + height: 2, + ) + : STextStyles.field(context), + enableSuggestions: false, + autocorrect: false, + autofocus: true, + onSubmitted: (_) => _save(), + onChanged: (_) => setState(() {}), + decoration: standardInputDecoration( + "Restore height", + _focusNode, + context, + ).copyWith( + suffixIcon: _controller.text.isNotEmpty + ? Padding( + padding: const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: ConditionalParent( + condition: Util.isDesktop, + builder: (child) => SizedBox( + height: 70, + child: child, + ), + child: Row( + children: [ + TextFieldIconButton( + child: const XIcon(), + onTap: () async { + setState(() { + _controller.text = ""; + }); + }, + ), + ], + ), + ), + ), + ) + : Util.isDesktop + ? const SizedBox( + height: 70, + ) + : null, + ), + ), + ), + Util.isDesktop + ? const SizedBox( + height: 32, + ) + : const Spacer(), + PrimaryButton( + label: "Save", + onPressed: _save, + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/wallet_settings_wallet_settings_view.dart b/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/wallet_settings_wallet_settings_view.dart index bf41d6261..c689baf30 100644 --- a/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/wallet_settings_wallet_settings_view.dart +++ b/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/wallet_settings_wallet_settings_view.dart @@ -20,6 +20,7 @@ import '../../../../utilities/constants.dart'; import '../../../../utilities/text_styles.dart'; import '../../../../wallets/isar/models/wallet_info.dart'; import '../../../../wallets/isar/providers/wallet_info_provider.dart'; +import '../../../../wallets/wallet/intermediate/lib_monero_wallet.dart'; import '../../../../wallets/wallet/wallet_mixin_interfaces/lelantus_interface.dart'; import '../../../../wallets/wallet/wallet_mixin_interfaces/multi_address_interface.dart'; import '../../../../wallets/wallet/wallet_mixin_interfaces/rbf_interface.dart'; @@ -32,6 +33,7 @@ import '../../../../widgets/rounded_white_container.dart'; import '../../../../widgets/stack_dialog.dart'; import '../../../pinpad_views/lock_screen_view.dart'; import 'delete_wallet_warning_view.dart'; +import 'edit_refresh_height_view.dart'; import 'lelantus_settings_view.dart'; import 'rbf_settings_view.dart'; import 'rename_wallet_view.dart'; @@ -354,6 +356,42 @@ class _WalletSettingsWalletSettingsViewState ), ), ), + if (wallet is LibMoneroWallet) + const SizedBox( + height: 8, + ), + if (wallet is LibMoneroWallet) + RoundedWhiteContainer( + padding: const EdgeInsets.all(0), + child: RawMaterialButton( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + onPressed: () { + Navigator.of(context).pushNamed( + EditRefreshHeightView.routeName, + arguments: widget.walletId, + ); + }, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 12.0, + vertical: 20, + ), + child: Row( + children: [ + Text( + "Restore height", + style: STextStyles.titleBold12(context), + ), + ], + ), + ), + ), + ), const SizedBox( height: 8, ), diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/wallet_options_button.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/wallet_options_button.dart index e0da6d5e9..f0756370f 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/wallet_options_button.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/wallet_options_button.dart @@ -17,6 +17,7 @@ import 'package:flutter_svg/svg.dart'; import '../../../../pages/settings_views/wallet_settings_view/frost_ms/frost_ms_options_view.dart'; import '../../../../pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/change_representative_view.dart'; +import '../../../../pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/edit_refresh_height_view.dart'; import '../../../../pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/xpub_view.dart'; import '../../../../providers/global/wallets_provider.dart'; import '../../../../route_generator.dart'; @@ -30,6 +31,7 @@ import '../../../../wallets/crypto_currency/coins/firo.dart'; import '../../../../wallets/crypto_currency/intermediate/frost_currency.dart'; import '../../../../wallets/crypto_currency/intermediate/nano_currency.dart'; import '../../../../wallets/isar/providers/wallet_info_provider.dart'; +import '../../../../wallets/wallet/intermediate/lib_monero_wallet.dart'; import '../../../../wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.dart'; import '../../../../wallets/wallet/wallet_mixin_interfaces/view_only_option_interface.dart'; import '../../../addresses/desktop_wallet_addresses_view.dart'; @@ -44,7 +46,8 @@ enum _WalletOptions { showXpub, lelantusCoins, sparkCoins, - frostOptions; + frostOptions, + refreshFromHeight; String get prettyName { switch (this) { @@ -62,6 +65,8 @@ enum _WalletOptions { return "Spark Coins"; case _WalletOptions.frostOptions: return "FROST settings"; + case _WalletOptions.refreshFromHeight: + return "Refresh height"; } } } @@ -111,6 +116,9 @@ class WalletOptionsButton extends ConsumerWidget { onFrostMSWalletOptionsPressed: () async { Navigator.of(context).pop(_WalletOptions.frostOptions); }, + onRefreshHeightPressed: () async { + Navigator.of(context).pop(_WalletOptions.refreshFromHeight); + }, walletId: walletId, ); }, @@ -243,6 +251,26 @@ class WalletOptionsButton extends ConsumerWidget { ), ); break; + + case _WalletOptions.refreshFromHeight: + if (Util.isDesktop) { + unawaited( + showDialog( + context: context, + builder: (context) => EditRefreshHeightView( + walletId: walletId, + ), + ), + ); + } else { + unawaited( + Navigator.of(context).pushNamed( + EditRefreshHeightView.routeName, + arguments: walletId, + ), + ); + } + break; } } }, @@ -278,6 +306,7 @@ class WalletOptionsPopupMenu extends ConsumerWidget { required this.onFiroShowLelantusCoins, required this.onFiroShowSparkCoins, required this.onFrostMSWalletOptionsPressed, + required this.onRefreshHeightPressed, required this.walletId, }); @@ -288,6 +317,7 @@ class WalletOptionsPopupMenu extends ConsumerWidget { final VoidCallback onFiroShowLelantusCoins; final VoidCallback onFiroShowSparkCoins; final VoidCallback onFrostMSWalletOptionsPressed; + final VoidCallback onRefreshHeightPressed; final String walletId; @override @@ -307,6 +337,7 @@ class WalletOptionsPopupMenu extends ConsumerWidget { final bool canChangeRep = coin is NanoCurrency; final bool isFrost = coin is FrostCurrency; + final bool isMoneroWow = wallet is LibMoneroWallet; return Stack( children: [ @@ -509,6 +540,43 @@ class WalletOptionsPopupMenu extends ConsumerWidget { ), ), ), + if (isMoneroWow) + const SizedBox( + height: 8, + ), + if (isMoneroWow) + TransparentButton( + onPressed: onRefreshHeightPressed, + child: Padding( + padding: const EdgeInsets.all(8), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SvgPicture.asset( + Assets.svg.addressBookDesktop, + width: 20, + height: 20, + color: Theme.of(context) + .extension<StackColors>()! + .textFieldActiveSearchIconLeft, + ), + const SizedBox(width: 14), + Expanded( + child: Text( + _WalletOptions.refreshFromHeight.prettyName, + style: STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: Theme.of(context) + .extension<StackColors>()! + .textDark, + ), + ), + ), + ], + ), + ), + ), if (xpubEnabled) const SizedBox( height: 8, diff --git a/lib/route_generator.dart b/lib/route_generator.dart index 52454fb29..bcfffa8cd 100644 --- a/lib/route_generator.dart +++ b/lib/route_generator.dart @@ -135,6 +135,7 @@ import 'pages/settings_views/wallet_settings_view/wallet_settings_wallet_setting import 'pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/delete_view_only_wallet_keys_view.dart'; import 'pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/delete_wallet_recovery_phrase_view.dart'; import 'pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/delete_wallet_warning_view.dart'; +import 'pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/edit_refresh_height_view.dart'; import 'pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/lelantus_settings_view.dart'; import 'pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/rbf_settings_view.dart'; import 'pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/rename_wallet_view.dart'; @@ -2140,6 +2141,20 @@ class RouteGenerator { } return _routeError("${settings.name} invalid args: ${args.toString()}"); + case EditRefreshHeightView.routeName: + if (args is String) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => EditRefreshHeightView( + walletId: args, + ), + settings: RouteSettings( + name: settings.name, + ), + ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); + // == Desktop specific routes ============================================ case CreatePasswordView.routeName: if (args is bool) {