diff --git a/lib/main.dart b/lib/main.dart index 2086d351e..8136965db 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -76,11 +76,17 @@ void main() async { Util.libraryPath = await getLibraryDirectory(); } + Screen? screen; + if (Platform.isLinux || Util.isDesktop) { + screen = await getCurrentScreen(); + Util.screenWidth = screen?.frame.width; + } + if (Util.isDesktop) { setWindowTitle('Stack Wallet'); setWindowMinSize(const Size(1220, 100)); setWindowMaxSize(Size.infinite); - final screen = await getCurrentScreen(); + final screenHeight = screen?.frame.height; if (screenHeight != null) { // starting to height be 3/4 screen height or 900, whichever is smaller @@ -312,17 +318,17 @@ class _MaterialAppWithThemeState extends ConsumerState final colorScheme = DB.instance .get(boxName: DB.boxNameTheme, key: "colorScheme") as String?; - ThemeType themeType; + StackColorTheme colorTheme; switch (colorScheme) { case "dark": - themeType = ThemeType.dark; + colorTheme = DarkColors(); break; case "oceanBreeze": - themeType = ThemeType.oceanBreeze; + colorTheme = OceanBreezeColors(); break; case "light": default: - themeType = ThemeType.light; + colorTheme = LightColors(); } loadingCompleter = Completer(); WidgetsBinding.instance.addObserver(this); @@ -333,11 +339,7 @@ class _MaterialAppWithThemeState extends ConsumerState WidgetsBinding.instance.addPostFrameCallback((_) async { ref.read(colorThemeProvider.state).state = - StackColors.fromStackColorTheme(themeType == ThemeType.dark - ? DarkColors() - : (themeType == ThemeType.light - ? LightColors() - : OceanBreezeColors())); + StackColors.fromStackColorTheme(colorTheme); if (Platform.isAndroid) { // fetch open file if it exists 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 ac84964ca..a66af63fc 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 @@ -23,6 +23,8 @@ import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart'; +import 'package:stackwallet/widgets/rounded_date_picker/flutter_rounded_date_picker_widget.dart' + as datePicker; import 'package:stackwallet/widgets/rounded_white_container.dart'; import 'package:tuple/tuple.dart'; @@ -152,10 +154,10 @@ class _RestoreOptionsViewState extends ConsumerState { await Future.delayed(const Duration(milliseconds: 125)); } - final date = await showRoundedDatePicker( + final date = await datePicker.showRoundedDatePicker( context: context, initialDate: DateTime.now(), - height: height / 3.2, + height: height * 0.5, theme: ThemeData( primarySwatch: Util.createMaterialColor(fetchedColor), ), diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/sub_views/stack_restore_progress_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/sub_views/stack_restore_progress_view.dart index c7f53378d..92e7742e1 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/sub_views/stack_restore_progress_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/sub_views/stack_restore_progress_view.dart @@ -25,6 +25,9 @@ import 'package:stackwallet/widgets/icon_widgets/addressbook_icon.dart'; import 'package:stackwallet/widgets/loading_indicator.dart'; import 'package:stackwallet/widgets/rounded_container.dart'; +import '../../../../../pages_desktop_specific/home/desktop_home_view.dart'; +import '../../../../../pages_desktop_specific/home/desktop_menu.dart'; +import '../../../../../providers/desktop/current_desktop_menu_item.dart'; import '../../../../../widgets/desktop/primary_button.dart'; class StackRestoreProgressView extends ConsumerStatefulWidget { @@ -685,7 +688,19 @@ class _StackRestoreProgressViewState enabled: true, label: "Done", onPressed: () async { - Navigator.of(context).pop(); + DesktopMenuItemId keyID = + DesktopMenuItemId.myStack; + + ref + .read(currentDesktopMenuItemProvider + .state) + .state = keyID; + + Navigator.of(context, rootNavigator: true) + .popUntil( + ModalRoute.withName( + DesktopHomeView.routeName), + ); }, ) : SecondaryButton( diff --git a/lib/pages/wallets_view/sub_widgets/favorite_card.dart b/lib/pages/wallets_view/sub_widgets/favorite_card.dart index 7749f264d..24b1e021a 100644 --- a/lib/pages/wallets_view/sub_widgets/favorite_card.dart +++ b/lib/pages/wallets_view/sub_widgets/favorite_card.dart @@ -49,6 +49,8 @@ class _FavoriteCardState extends ConsumerState { super.initState(); } + bool _hovering = false; + @override Widget build(BuildContext context) { final coin = ref.watch(managerProvider.select((value) => value.coin)); @@ -59,7 +61,48 @@ class _FavoriteCardState extends ConsumerState { condition: Util.isDesktop, builder: (child) => MouseRegion( cursor: SystemMouseCursors.click, - child: child, + onEnter: (_) { + setState(() { + _hovering = true; + }); + }, + onExit: (_) { + setState(() { + _hovering = false; + }); + }, + child: AnimatedScale( + duration: const Duration(milliseconds: 200), + scale: _hovering ? 1.05 : 1, + child: AnimatedContainer( + duration: const Duration(milliseconds: 200), + decoration: _hovering + ? BoxDecoration( + color: Colors.transparent, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + boxShadow: [ + Theme.of(context) + .extension()! + .standardBoxShadow, + Theme.of(context) + .extension()! + .standardBoxShadow, + Theme.of(context) + .extension()! + .standardBoxShadow, + ], + ) + : BoxDecoration( + color: Colors.transparent, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + child: child, + ), + ), ), child: GestureDetector( onTap: () { diff --git a/lib/pages_desktop_specific/home/desktop_home_view.dart b/lib/pages_desktop_specific/home/desktop_home_view.dart index 54c74fe88..3e0b9311b 100644 --- a/lib/pages_desktop_specific/home/desktop_home_view.dart +++ b/lib/pages_desktop_specific/home/desktop_home_view.dart @@ -9,12 +9,19 @@ import 'package:stackwallet/pages_desktop_specific/home/notifications/desktop_no import 'package:stackwallet/pages_desktop_specific/home/support_and_about_view/desktop_about_view.dart'; import 'package:stackwallet/pages_desktop_specific/home/support_and_about_view/desktop_support_view.dart'; import 'package:stackwallet/providers/desktop/current_desktop_menu_item.dart'; +import 'package:stackwallet/providers/global/auto_swb_service_provider.dart'; import 'package:stackwallet/providers/global/notifications_provider.dart'; +import 'package:stackwallet/providers/global/prefs_provider.dart'; +import 'package:stackwallet/providers/global/wallets_provider.dart'; +import 'package:stackwallet/providers/ui/transaction_filter_provider.dart'; import 'package:stackwallet/providers/ui/unread_notifications_provider.dart'; import 'package:stackwallet/route_generator.dart'; +import 'package:stackwallet/utilities/enums/backup_frequency_type.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/background.dart'; +final currentWalletIdProvider = StateProvider((_) => null); + class DesktopHomeView extends ConsumerStatefulWidget { const DesktopHomeView({Key? key}) : super(key: key); @@ -25,12 +32,25 @@ class DesktopHomeView extends ConsumerStatefulWidget { } class _DesktopHomeViewState extends ConsumerState { - final Map contentViews = { - DesktopMenuItemId.myStack: const Navigator( - key: Key("desktopStackHomeKey"), + final GlobalKey key = GlobalKey(); + late final Navigator myStackViewNav; + + @override + void initState() { + myStackViewNav = Navigator( + key: key, onGenerateRoute: RouteGenerator.generateRoute, initialRoute: MyStackView.routeName, - ), + ); + super.initState(); + } + + final Map contentViews = { + DesktopMenuItemId.myStack: Container( + // key: Key("desktopStackHomeKey"), + // onGenerateRoute: RouteGenerator.generateRoute, + // initialRoute: MyStackView.routeName, + ), DesktopMenuItemId.exchange: const Navigator( key: Key("desktopExchangeHomeKey"), onGenerateRoute: RouteGenerator.generateRoute, @@ -63,7 +83,30 @@ class _DesktopHomeViewState extends ConsumerState { ), }; + DesktopMenuItemId prev = DesktopMenuItemId.myStack; + void onMenuSelectionWillChange(DesktopMenuItemId newKey) { + if (prev == DesktopMenuItemId.myStack && prev == newKey) { + Navigator.of(key.currentContext!) + .popUntil(ModalRoute.withName(MyStackView.routeName)); + if (ref.read(currentWalletIdProvider.state).state != null) { + final managerProvider = ref + .read(walletsChangeNotifierProvider) + .getManagerProvider(ref.read(currentWalletIdProvider.state).state!); + if (ref.read(managerProvider).shouldAutoSync) { + ref.read(managerProvider).shouldAutoSync = false; + } + ref.read(transactionFilterProvider.state).state = null; + if (ref.read(prefsChangeNotifierProvider).isAutoBackupEnabled && + ref.read(prefsChangeNotifierProvider).backupFrequencyType == + BackupFrequencyType.afterClosingAWallet) { + ref.read(autoSWBServiceProvider).doBackup(); + } + ref.read(managerProvider.notifier).isActiveWallet = false; + } + } + prev = newKey; + // check for unread notifications and refresh provider before // showing notifications view if (newKey == DesktopMenuItemId.notifications) { @@ -111,9 +154,25 @@ class _DesktopHomeViewState extends ConsumerState { color: Theme.of(context).extension()!.background, ), Expanded( - child: contentViews[ - ref.watch(currentDesktopMenuItemProvider.state).state]!, + child: IndexedStack( + index: ref + .watch(currentDesktopMenuItemProvider.state) + .state + .index > + 0 + ? 1 + : 0, + children: [ + myStackViewNav, + contentViews[ + ref.watch(currentDesktopMenuItemProvider.state).state]!, + ], + ), ), + // Expanded( + // child: contentViews[ + // ref.watch(currentDesktopMenuItemProvider.state).state]!, + // ), ], ), ), diff --git a/lib/pages_desktop_specific/home/desktop_menu.dart b/lib/pages_desktop_specific/home/desktop_menu.dart index d82d62883..fd404db94 100644 --- a/lib/pages_desktop_specific/home/desktop_menu.dart +++ b/lib/pages_desktop_specific/home/desktop_menu.dart @@ -9,6 +9,7 @@ import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/widgets/desktop/living_stack_icon.dart'; enum DesktopMenuItemId { myStack, @@ -38,6 +39,9 @@ class _DesktopMenuState extends ConsumerState { static const expandedWidth = 225.0; static const minimizedWidth = 72.0; + final Duration duration = const Duration(milliseconds: 250); + late final List controllers; + double _width = expandedWidth; void updateSelectedMenuItem(DesktopMenuItemId idKey) { @@ -49,45 +53,84 @@ class _DesktopMenuState extends ConsumerState { } void toggleMinimize() { + final expanded = _width == expandedWidth; + + for (var e in controllers) { + e.toggle?.call(); + } + setState(() { - _width = _width == expandedWidth ? minimizedWidth : expandedWidth; + _width = expanded ? minimizedWidth : expandedWidth; }); } + @override + void initState() { + controllers = [ + DMIController(), + DMIController(), + DMIController(), + DMIController(), + DMIController(), + DMIController(), + DMIController(), + DMIController(), + ]; + + super.initState(); + } + + @override + void dispose() { + for (var e in controllers) { + e.dispose(); + } + super.dispose(); + } + @override Widget build(BuildContext context) { return Material( color: Theme.of(context).extension()!.popupBG, - child: SizedBox( + child: AnimatedContainer( width: _width, + duration: duration, child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ - SizedBox( - height: _width == expandedWidth ? 22 : 25, + const SizedBox( + height: 25, ), - SizedBox( + AnimatedContainer( + duration: duration, width: _width == expandedWidth ? 70 : 32, - height: _width == expandedWidth ? 70 : 32, - child: SvgPicture.asset( - Assets.svg.stackIcon(context), + child: LivingStackIcon( + onPressed: toggleMinimize, ), ), const SizedBox( height: 10, ), - Text( - _width == expandedWidth ? "Stack Wallet" : "", - style: STextStyles.desktopH2(context).copyWith( - fontSize: 18, - height: 23.4 / 18, + AnimatedOpacity( + duration: duration, + opacity: _width == expandedWidth ? 1 : 0, + child: SizedBox( + height: 28, + child: Text( + "Stack Wallet", + style: STextStyles.desktopH2(context).copyWith( + fontSize: 18, + height: 23.4 / 18, + ), + ), ), ), const SizedBox( height: 60, ), Expanded( - child: SizedBox( + child: AnimatedContainer( + duration: duration, width: _width == expandedWidth ? _width - 32 // 16 padding on either side : _width - 16, // 8 padding on either side @@ -95,6 +138,7 @@ class _DesktopMenuState extends ConsumerState { crossAxisAlignment: CrossAxisAlignment.stretch, children: [ DesktopMenuItem( + duration: duration, icon: SvgPicture.asset( Assets.svg.walletDesktop, width: 20, @@ -113,15 +157,14 @@ class _DesktopMenuState extends ConsumerState { ), label: "My Stack", value: DesktopMenuItemId.myStack, - group: - ref.watch(currentDesktopMenuItemProvider.state).state, onChanged: updateSelectedMenuItem, - iconOnly: _width == minimizedWidth, + controller: controllers[0], ), const SizedBox( height: 2, ), DesktopMenuItem( + duration: duration, icon: SvgPicture.asset( Assets.svg.exchangeDesktop, width: 20, @@ -140,15 +183,14 @@ class _DesktopMenuState extends ConsumerState { ), label: "Exchange", value: DesktopMenuItemId.exchange, - group: - ref.watch(currentDesktopMenuItemProvider.state).state, onChanged: updateSelectedMenuItem, - iconOnly: _width == minimizedWidth, + controller: controllers[1], ), const SizedBox( height: 2, ), DesktopMenuItem( + duration: duration, icon: SvgPicture.asset( ref.watch(notificationsProvider.select( (value) => value.hasUnreadNotifications)) @@ -174,15 +216,14 @@ class _DesktopMenuState extends ConsumerState { ), label: "Notifications", value: DesktopMenuItemId.notifications, - group: - ref.watch(currentDesktopMenuItemProvider.state).state, onChanged: updateSelectedMenuItem, - iconOnly: _width == minimizedWidth, + controller: controllers[2], ), const SizedBox( height: 2, ), DesktopMenuItem( + duration: duration, icon: SvgPicture.asset( Assets.svg.addressBookDesktop, width: 20, @@ -201,15 +242,14 @@ class _DesktopMenuState extends ConsumerState { ), label: "Address Book", value: DesktopMenuItemId.addressBook, - group: - ref.watch(currentDesktopMenuItemProvider.state).state, onChanged: updateSelectedMenuItem, - iconOnly: _width == minimizedWidth, + controller: controllers[3], ), const SizedBox( height: 2, ), DesktopMenuItem( + duration: duration, icon: SvgPicture.asset( Assets.svg.gear, width: 20, @@ -228,15 +268,14 @@ class _DesktopMenuState extends ConsumerState { ), label: "Settings", value: DesktopMenuItemId.settings, - group: - ref.watch(currentDesktopMenuItemProvider.state).state, onChanged: updateSelectedMenuItem, - iconOnly: _width == minimizedWidth, + controller: controllers[4], ), const SizedBox( height: 2, ), DesktopMenuItem( + duration: duration, icon: SvgPicture.asset( Assets.svg.messageQuestion, width: 20, @@ -255,15 +294,14 @@ class _DesktopMenuState extends ConsumerState { ), label: "Support", value: DesktopMenuItemId.support, - group: - ref.watch(currentDesktopMenuItemProvider.state).state, onChanged: updateSelectedMenuItem, - iconOnly: _width == minimizedWidth, + controller: controllers[5], ), const SizedBox( height: 2, ), DesktopMenuItem( + duration: duration, icon: SvgPicture.asset( Assets.svg.aboutDesktop, width: 20, @@ -282,13 +320,13 @@ class _DesktopMenuState extends ConsumerState { ), label: "About", value: DesktopMenuItemId.about, - group: - ref.watch(currentDesktopMenuItemProvider.state).state, onChanged: updateSelectedMenuItem, - iconOnly: _width == minimizedWidth, + controller: controllers[6], ), const Spacer(), DesktopMenuItem( + duration: duration, + labelLength: 123, icon: SvgPicture.asset( Assets.svg.exitDesktop, width: 20, @@ -300,13 +338,11 @@ class _DesktopMenuState extends ConsumerState { ), label: "Exit", value: 7, - group: - ref.watch(currentDesktopMenuItemProvider.state).state, onChanged: (_) { // todo: save stuff/ notify before exit? exit(0); }, - iconOnly: _width == minimizedWidth, + controller: controllers[7], ), ], ), diff --git a/lib/pages_desktop_specific/home/desktop_menu_item.dart b/lib/pages_desktop_specific/home/desktop_menu_item.dart index 76d945e2d..78dcde79b 100644 --- a/lib/pages_desktop_specific/home/desktop_menu_item.dart +++ b/lib/pages_desktop_specific/home/desktop_menu_item.dart @@ -1,27 +1,96 @@ import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/providers/desktop/current_desktop_menu_item.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; -class DesktopMenuItem extends StatelessWidget { +class DMIController { + VoidCallback? toggle; + void dispose() { + toggle = null; + } +} + +class DesktopMenuItem extends ConsumerStatefulWidget { const DesktopMenuItem({ Key? key, required this.icon, required this.label, required this.value, - required this.group, required this.onChanged, - required this.iconOnly, + required this.duration, + this.labelLength = 125, + this.controller, }) : super(key: key); final Widget icon; final String label; final T value; - final T group; final void Function(T) onChanged; - final bool iconOnly; + final Duration duration; + final double labelLength; + final DMIController? controller; + + @override + ConsumerState> createState() => _DesktopMenuItemState(); +} + +class _DesktopMenuItemState extends ConsumerState> + with SingleTickerProviderStateMixin { + late final Widget icon; + late final String label; + late final T value; + late final void Function(T) onChanged; + late final Duration duration; + late final double labelLength; + + late final DMIController? controller; + + late final AnimationController animationController; + + bool _iconOnly = false; + + void toggle() { + setState(() { + _iconOnly = !_iconOnly; + }); + if (_iconOnly) { + animationController.reverse(); + } else { + animationController.forward(); + } + } + + @override + void initState() { + icon = widget.icon; + label = widget.label; + value = widget.value; + onChanged = widget.onChanged; + duration = widget.duration; + labelLength = widget.labelLength; + controller = widget.controller; + + controller?.toggle = toggle; + animationController = AnimationController( + vsync: this, + duration: duration, + )..forward(); + + super.initState(); + } + + @override + void dispose() { + controller?.dispose(); + animationController.dispose(); + super.dispose(); + } @override Widget build(BuildContext context) { + final group = ref.watch(currentDesktopMenuItemProvider.state).state; + debugPrint("============ value:$value ============ group:$group"); return TextButton( style: value == group ? Theme.of(context) @@ -34,26 +103,42 @@ class DesktopMenuItem extends StatelessWidget { onChanged(value); }, child: Padding( - padding: EdgeInsets.symmetric( + padding: const EdgeInsets.symmetric( vertical: 16, - horizontal: iconOnly ? 0 : 16, ), child: Row( - mainAxisAlignment: - iconOnly ? MainAxisAlignment.center : MainAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, children: [ + AnimatedContainer( + duration: duration, + width: _iconOnly ? 0 : 16, + ), icon, - if (!iconOnly) - const SizedBox( - width: 12, - ), - if (!iconOnly) - Text( - label, - style: value == group - ? STextStyles.desktopMenuItemSelected(context) - : STextStyles.desktopMenuItem(context), + AnimatedOpacity( + duration: duration, + opacity: _iconOnly ? 0 : 1.0, + child: SizeTransition( + sizeFactor: animationController, + axis: Axis.horizontal, + axisAlignment: -1, + child: SizedBox( + width: labelLength, + child: Row( + children: [ + const SizedBox( + width: 12, + ), + Text( + label, + style: value == group + ? STextStyles.desktopMenuItemSelected(context) + : STextStyles.desktopMenuItem(context), + ), + ], + ), + ), ), + ) ], ), ), diff --git a/lib/pages_desktop_specific/home/my_stack_view/coin_wallets_table.dart b/lib/pages_desktop_specific/home/my_stack_view/coin_wallets_table.dart index b16a9bc58..1edb93e06 100644 --- a/lib/pages_desktop_specific/home/my_stack_view/coin_wallets_table.dart +++ b/lib/pages_desktop_specific/home/my_stack_view/coin_wallets_table.dart @@ -1,8 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/pages_desktop_specific/home/desktop_home_view.dart'; import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_view/desktop_wallet_view.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/widgets/rounded_container.dart'; import 'package:stackwallet/widgets/wallet_info_row/wallet_info_row.dart'; class CoinWalletsTable extends ConsumerWidget { @@ -24,8 +26,10 @@ class CoinWalletsTable extends ConsumerWidget { ), child: Padding( padding: const EdgeInsets.symmetric( - horizontal: 20, - vertical: 16, + // horizontal: 20, + // vertical: 16, + horizontal: 6, + vertical: 6, ), child: Column( children: [ @@ -36,14 +40,29 @@ class CoinWalletsTable extends ConsumerWidget { const SizedBox( height: 32, ), - WalletInfoRow( - walletId: walletIds[i], - onPressed: () async { - await Navigator.of(context).pushNamed( - DesktopWalletView.routeName, - arguments: walletIds[i], - ); - }, + Stack( + children: [ + WalletInfoRow( + padding: const EdgeInsets.symmetric( + horizontal: 14, + vertical: 10, + ), + walletId: walletIds[i], + ), + Positioned.fill( + child: WalletRowHoverOverlay( + onPressed: () async { + ref.read(currentWalletIdProvider.state).state = + walletIds[i]; + + await Navigator.of(context).pushNamed( + DesktopWalletView.routeName, + arguments: walletIds[i], + ); + }, + ), + ), + ], ), ], ), @@ -53,3 +72,55 @@ class CoinWalletsTable extends ConsumerWidget { ); } } + +class WalletRowHoverOverlay extends StatefulWidget { + const WalletRowHoverOverlay({ + Key? key, + required this.onPressed, + }) : super(key: key); + + final VoidCallback onPressed; + + @override + State createState() => _WalletRowHoverOverlayState(); +} + +class _WalletRowHoverOverlayState extends State { + late final VoidCallback onPressed; + + bool _hovering = false; + + @override + void initState() { + onPressed = widget.onPressed; + super.initState(); + } + + @override + Widget build(BuildContext context) { + return MouseRegion( + cursor: SystemMouseCursors.click, + onEnter: (_) { + setState(() { + _hovering = true; + }); + }, + onExit: (_) { + setState(() { + _hovering = false; + }); + }, + child: GestureDetector( + onTap: onPressed, + child: AnimatedOpacity( + duration: const Duration(milliseconds: 100), + opacity: _hovering ? 0.1 : 0, + child: RoundedContainer( + color: + Theme.of(context).extension()!.buttonBackSecondary, + ), + ), + ), + ); + } +} diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/desktop_wallet_view.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/desktop_wallet_view.dart index 5996597b5..d870835f1 100644 --- a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/desktop_wallet_view.dart +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/desktop_wallet_view.dart @@ -81,13 +81,13 @@ class _DesktopWalletViewState extends ConsumerState { // disable auto sync if it was enabled only when loading wallet ref.read(managerProvider).shouldAutoSync = false; } - ref.read(managerProvider.notifier).isActiveWallet = false; ref.read(transactionFilterProvider.state).state = null; if (ref.read(prefsChangeNotifierProvider).isAutoBackupEnabled && ref.read(prefsChangeNotifierProvider).backupFrequencyType == BackupFrequencyType.afterClosingAWallet) { unawaited(ref.read(autoSWBServiceProvider).doBackup()); } + ref.read(managerProvider.notifier).isActiveWallet = false; } void _loadCNData() { diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/delete_wallet_keys_popup.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/delete_wallet_keys_popup.dart index 70f4a3e13..a2d58465b 100644 --- a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/delete_wallet_keys_popup.dart +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/delete_wallet_keys_popup.dart @@ -95,7 +95,8 @@ class _DeleteWalletKeysPopup extends ConsumerState { horizontal: 32, ), child: Text( - "Please write down your recovery phrase in the correct order and save it to keep your funds secure. You will also be asked to verify the words on the next screen.", + "Please write down your recovery phrase in the correct order and " + "save it to keep your funds secure. You will be shown your recovery phrase on the next screen.", style: STextStyles.desktopTextExtraExtraSmall(context), textAlign: TextAlign.center, ), diff --git a/lib/utilities/util.dart b/lib/utilities/util.dart index 5963bfee9..2940b6d40 100644 --- a/lib/utilities/util.dart +++ b/lib/utilities/util.dart @@ -1,14 +1,24 @@ import 'dart:io'; import 'package:flutter/material.dart'; -import 'package:path_provider/path_provider.dart'; abstract class Util { static Directory? libraryPath; + static double? screenWidth; + static bool get isDesktop { - if(Platform.isIOS && libraryPath != null && !libraryPath!.path.contains("/var/mobile/")){ + // special check for running on linux based phones + if (Platform.isLinux && screenWidth != null && screenWidth! < 800) { + return false; + } + + // special check for running under ipad mode in macos + if (Platform.isIOS && + libraryPath != null && + !libraryPath!.path.contains("/var/mobile/")) { return true; } + return Platform.isLinux || Platform.isMacOS || Platform.isWindows; } diff --git a/lib/widgets/desktop/living_stack_icon.dart b/lib/widgets/desktop/living_stack_icon.dart new file mode 100644 index 000000000..7afc8f8d2 --- /dev/null +++ b/lib/widgets/desktop/living_stack_icon.dart @@ -0,0 +1,54 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:stackwallet/utilities/assets.dart'; + +class LivingStackIcon extends StatefulWidget { + const LivingStackIcon({Key? key, this.onPressed,}) : super(key: key); + + final VoidCallback? onPressed; + + @override + State createState() => _LivingStackIconState(); +} + +class _LivingStackIconState extends State { + bool _hovering = false; + + late final VoidCallback? onPressed; + + @override + void initState() { + onPressed = widget.onPressed; + super.initState(); + } + + @override + Widget build(BuildContext context) { + return SizedBox( + height: 76, + child: MouseRegion( + cursor: SystemMouseCursors.click, + onEnter: (_) { + setState(() { + _hovering = true; + }); + }, + onExit: (_) { + setState(() { + _hovering = false; + }); + }, + child: GestureDetector( + onTap: () => onPressed?.call(), + child: AnimatedScale( + duration: const Duration(milliseconds: 200), + scale: _hovering ? 1.2 : 1, + child: SvgPicture.asset( + Assets.svg.stackIcon(context), + ), + ), + ), + ), + ); + } +} diff --git a/lib/widgets/rounded_date_picker/LICENSE b/lib/widgets/rounded_date_picker/LICENSE new file mode 100644 index 000000000..58665fbd2 --- /dev/null +++ b/lib/widgets/rounded_date_picker/LICENSE @@ -0,0 +1,5 @@ +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with the License. You may obtain a copy of the License in the LICENSE file, or at: + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. \ No newline at end of file diff --git a/lib/widgets/rounded_date_picker/flutter_rounded_date_picker_dialog.dart b/lib/widgets/rounded_date_picker/flutter_rounded_date_picker_dialog.dart new file mode 100644 index 000000000..6d7f775cd --- /dev/null +++ b/lib/widgets/rounded_date_picker/flutter_rounded_date_picker_dialog.dart @@ -0,0 +1,332 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/semantics.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_rounded_date_picker/flutter_rounded_date_picker.dart'; +import 'package:flutter_rounded_date_picker/src/flutter_rounded_button_action.dart'; +import 'package:flutter_rounded_date_picker/src/material_rounded_date_picker_style.dart'; +import 'package:flutter_rounded_date_picker/src/material_rounded_year_picker_style.dart'; +import 'package:flutter_rounded_date_picker/src/widgets/flutter_rounded_date_picker_header.dart'; +import 'package:flutter_rounded_date_picker/src/widgets/flutter_rounded_day_picker.dart'; +import 'package:flutter_rounded_date_picker/src/widgets/flutter_rounded_month_picker.dart'; +import 'package:flutter_rounded_date_picker/src/widgets/flutter_rounded_year_picker.dart'; +import 'package:stackwallet/utilities/util.dart'; + +/// +/// This file uses code taken from https://github.com/benznest/flutter_rounded_date_picker +/// + +class FlutterRoundedDatePickerDialog extends StatefulWidget { + const FlutterRoundedDatePickerDialog( + {Key? key, + this.height, + required this.initialDate, + required this.firstDate, + required this.lastDate, + this.selectableDayPredicate, + required this.initialDatePickerMode, + required this.era, + this.locale, + required this.borderRadius, + this.imageHeader, + this.description = "", + this.fontFamily, + this.textNegativeButton, + this.textPositiveButton, + this.textActionButton, + this.onTapActionButton, + this.styleDatePicker, + this.styleYearPicker, + this.customWeekDays, + this.builderDay, + this.listDateDisabled, + this.onTapDay, + this.onMonthChange}) + : super(key: key); + + final DateTime initialDate; + final DateTime firstDate; + final DateTime lastDate; + final SelectableDayPredicate? selectableDayPredicate; + final DatePickerMode initialDatePickerMode; + + /// double height. + final double? height; + + /// Custom era year. + final EraMode era; + final Locale? locale; + + /// Border + final double borderRadius; + + /// Header; + final ImageProvider? imageHeader; + final String description; + + /// Font + final String? fontFamily; + + /// Button + final String? textNegativeButton; + final String? textPositiveButton; + final String? textActionButton; + + final VoidCallback? onTapActionButton; + + /// Style + final MaterialRoundedDatePickerStyle? styleDatePicker; + final MaterialRoundedYearPickerStyle? styleYearPicker; + + /// Custom Weekday + final List? customWeekDays; + + final BuilderDayOfDatePicker? builderDay; + + final List? listDateDisabled; + final OnTapDay? onTapDay; + + final Function? onMonthChange; + + @override + _FlutterRoundedDatePickerDialogState createState() => + _FlutterRoundedDatePickerDialogState(); +} + +class _FlutterRoundedDatePickerDialogState + extends State { + @override + void initState() { + super.initState(); + _selectedDate = widget.initialDate; + _mode = widget.initialDatePickerMode; + } + + bool _announcedInitialDate = false; + + late MaterialLocalizations localizations; + late TextDirection textDirection; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + localizations = MaterialLocalizations.of(context); + textDirection = Directionality.of(context); + if (!_announcedInitialDate) { + _announcedInitialDate = true; + SemanticsService.announce( + localizations.formatFullDate(_selectedDate), + textDirection, + ); + } + } + + late DateTime _selectedDate; + late DatePickerMode _mode; + final GlobalKey _pickerKey = GlobalKey(); + + void _vibrate() { + switch (Theme.of(context).platform) { + case TargetPlatform.android: + case TargetPlatform.fuchsia: + HapticFeedback.vibrate(); + break; + case TargetPlatform.iOS: + default: + break; + } + } + + void _handleModeChanged(DatePickerMode mode) { + _vibrate(); + setState(() { + _mode = mode; + if (_mode == DatePickerMode.day) { + SemanticsService.announce( + localizations.formatMonthYear(_selectedDate), + textDirection, + ); + } else { + SemanticsService.announce( + localizations.formatYear(_selectedDate), + textDirection, + ); + } + }); + } + + Future _handleYearChanged(DateTime value) async { + if (value.isBefore(widget.firstDate)) { + value = widget.firstDate; + } else if (value.isAfter(widget.lastDate)) { + value = widget.lastDate; + } + if (value == _selectedDate) return; + + if (widget.onMonthChange != null) await widget.onMonthChange!(value); + + _vibrate(); + setState(() { + _mode = DatePickerMode.day; + _selectedDate = value; + }); + } + + void _handleDayChanged(DateTime value) { + _vibrate(); + setState(() { + _selectedDate = value; + }); + } + + void _handleCancel() { + Navigator.of(context).pop(); + } + + void _handleOk() { + Navigator.of(context).pop(_selectedDate); + } + + Widget _buildPicker() { + switch (_mode) { + case DatePickerMode.year: + return FlutterRoundedYearPicker( + key: _pickerKey, + selectedDate: _selectedDate, + onChanged: (DateTime date) async => await _handleYearChanged(date), + firstDate: widget.firstDate, + lastDate: widget.lastDate, + era: widget.era, + fontFamily: widget.fontFamily, + style: widget.styleYearPicker, + ); + case DatePickerMode.day: + default: + return FlutterRoundedMonthPicker( + key: _pickerKey, + selectedDate: _selectedDate, + onChanged: _handleDayChanged, + firstDate: widget.firstDate, + lastDate: widget.lastDate, + era: widget.era, + locale: widget.locale, + selectableDayPredicate: widget.selectableDayPredicate, + fontFamily: widget.fontFamily, + style: widget.styleDatePicker, + borderRadius: widget.borderRadius, + customWeekDays: widget.customWeekDays, + builderDay: widget.builderDay, + listDateDisabled: widget.listDateDisabled, + onTapDay: widget.onTapDay, + onMonthChange: widget.onMonthChange); + } + } + + @override + Widget build(BuildContext context) { + final ThemeData theme = Theme.of(context); + final Widget picker = _buildPicker(); + final isDesktop = Util.isDesktop; + + final Widget actions = FlutterRoundedButtonAction( + textButtonNegative: widget.textNegativeButton, + textButtonPositive: widget.textPositiveButton, + onTapButtonNegative: _handleCancel, + onTapButtonPositive: _handleOk, + textActionButton: widget.textActionButton, + onTapButtonAction: widget.onTapActionButton, + localizations: localizations, + textStyleButtonNegative: widget.styleDatePicker?.textStyleButtonNegative, + textStyleButtonPositive: widget.styleDatePicker?.textStyleButtonPositive, + textStyleButtonAction: widget.styleDatePicker?.textStyleButtonAction, + borderRadius: widget.borderRadius, + paddingActionBar: widget.styleDatePicker?.paddingActionBar, + background: widget.styleDatePicker?.backgroundActionBar, + ); + + Color backgroundPicker = theme.dialogBackgroundColor; + if (_mode == DatePickerMode.day) { + backgroundPicker = widget.styleDatePicker?.backgroundPicker ?? + theme.dialogBackgroundColor; + } else { + backgroundPicker = widget.styleYearPicker?.backgroundPicker ?? + theme.dialogBackgroundColor; + } + + final Dialog dialog = Dialog( + child: OrientationBuilder( + builder: (BuildContext context, Orientation orientation) { + final Widget header = FlutterRoundedDatePickerHeader( + selectedDate: _selectedDate, + mode: _mode, + onModeChanged: _handleModeChanged, + orientation: orientation, + era: widget.era, + borderRadius: widget.borderRadius, + imageHeader: widget.imageHeader, + description: widget.description, + fontFamily: widget.fontFamily, + style: widget.styleDatePicker); + switch (orientation) { + case Orientation.landscape: + return Container( + height: isDesktop ? 600 : null, + width: isDesktop ? 700 : null, + decoration: BoxDecoration( + color: backgroundPicker, + borderRadius: BorderRadius.circular(widget.borderRadius), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Flexible(flex: 1, child: header), + Flexible( + flex: 2, // have the picker take up 2/3 of the dialog width + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + SizedBox( + height: isDesktop ? 530 : null, + width: isDesktop ? 700 : null, + child: picker), + actions, + ], + ), + ), + ], + ), + ); + case Orientation.portrait: + default: + return Container( + decoration: BoxDecoration( + color: backgroundPicker, + borderRadius: BorderRadius.circular(widget.borderRadius), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + header, + if (widget.height == null) + Flexible(child: picker) + else + SizedBox( + height: widget.height, + child: picker, + ), + actions, + ], + ), + ); + } + }), + ); + + return Theme( + data: theme.copyWith(dialogBackgroundColor: Colors.transparent), + child: dialog, + ); + } +} diff --git a/lib/widgets/rounded_date_picker/flutter_rounded_date_picker_widget.dart b/lib/widgets/rounded_date_picker/flutter_rounded_date_picker_widget.dart new file mode 100644 index 000000000..5f576f480 --- /dev/null +++ b/lib/widgets/rounded_date_picker/flutter_rounded_date_picker_widget.dart @@ -0,0 +1,216 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/widgets.dart'; +// import 'package:flutter_rounded_date_picker/src/dialogs/flutter_rounded_date_picker_dialog.dart'; +import 'package:flutter_rounded_date_picker/src/era_mode.dart'; +import 'package:flutter_rounded_date_picker/src/material_rounded_date_picker_style.dart'; +import 'package:flutter_rounded_date_picker/src/material_rounded_year_picker_style.dart'; +import 'package:flutter_rounded_date_picker/src/widgets/flutter_rounded_day_picker.dart'; +import 'package:stackwallet/widgets/rounded_date_picker/flutter_rounded_date_picker_dialog.dart'; + +/// +/// This file uses code taken from https://github.com/benznest/flutter_rounded_date_picker +/// + +// Examples can assume: +// BuildContext context; + +/// Initial display mode of the date picker dialog. +/// +/// Date picker UI mode for either showing a list of available years or a +/// monthly calendar initially in the dialog shown by calling [showDatePicker]. +/// + +// Shows the selected date in large font and toggles between year and day mode + +/// Signature for predicating dates for enabled date selections. +/// +/// See [showDatePicker]. +typedef SelectableDayPredicate = bool Function(DateTime day); + +/// Shows a dialog containing a material design date picker. +/// +/// The returned [Future] resolves to the date selected by the user when the +/// user closes the dialog. If the user cancels the dialog, null is returned. +/// +/// An optional [selectableDayPredicate] function can be passed in to customize +/// the days to enable for selection. If provided, only the days that +/// [selectableDayPredicate] returned true for will be selectable. +/// +/// An optional [initialDatePickerMode] argument can be used to display the +/// date picker initially in the year or month+day picker mode. It defaults +/// to month+day, and must not be null. +/// +/// An optional [locale] argument can be used to set the locale for the date +/// picker. It defaults to the ambient locale provided by [Localizations]. +/// +/// An optional [textDirection] argument can be used to set the text direction +/// (RTL or LTR) for the date picker. It defaults to the ambient text direction +/// provided by [Directionality]. If both [locale] and [textDirection] are not +/// null, [textDirection] overrides the direction chosen for the [locale]. +/// +/// The [context] argument is passed to [showDialog], the documentation for +/// which discusses how it is used. +/// +/// The [builder] parameter can be used to wrap the dialog widget +/// to add inherited widgets like [Theme]. +/// +/// {@tool sample} +/// Show a date picker with the dark theme. +/// +/// ```dart +/// Future selectedDate = showDatePicker( +/// context: context, +/// initialDate: DateTime.now(), +/// firstDate: DateTime(2018), +/// lastDate: DateTime(2030), +/// builder: (BuildContext context, Widget child) { +/// return Theme( +/// data: ThemeData.dark(), +/// child: child, +/// ); +/// }, +/// ); +/// ``` +/// {@end-tool} +/// +/// The [context], [initialDate], [firstDate], and [lastDate] parameters must +/// not be null. +/// +/// See also: +/// +/// * [showTimePicker], which shows a dialog that contains a material design +/// time picker. +/// * [DayPicker], which displays the days of a given month and allows +/// choosing a day. +/// * [MonthPicker], which displays a scrollable list of months to allow +/// picking a month. +/// * [YearPicker], which displays a scrollable list of years to allow picking +/// a year. +/// + +Future showRoundedDatePicker( + {required BuildContext context, + double? height, + DateTime? initialDate, + DateTime? firstDate, + DateTime? lastDate, + SelectableDayPredicate? selectableDayPredicate, + DatePickerMode initialDatePickerMode = DatePickerMode.day, + Locale? locale, + TextDirection? textDirection, + ThemeData? theme, + double borderRadius = 16, + EraMode era = EraMode.CHRIST_YEAR, + ImageProvider? imageHeader, + String description = "", + String? fontFamily, + bool barrierDismissible = false, + Color background = Colors.transparent, + String? textNegativeButton, + String? textPositiveButton, + String? textActionButton, + VoidCallback? onTapActionButton, + MaterialRoundedDatePickerStyle? styleDatePicker, + MaterialRoundedYearPickerStyle? styleYearPicker, + List? customWeekDays, + BuilderDayOfDatePicker? builderDay, + List? listDateDisabled, + OnTapDay? onTapDay, + Function? onMonthChange}) async { + initialDate ??= DateTime.now(); + firstDate ??= DateTime(initialDate.year - 1); + lastDate ??= DateTime(initialDate.year + 1); + theme ??= ThemeData(); + + assert( + !initialDate.isBefore(firstDate), + 'initialDate must be on or after firstDate', + ); + assert( + !initialDate.isAfter(lastDate), + 'initialDate must be on or before lastDate', + ); + assert( + !firstDate.isAfter(lastDate), + 'lastDate must be on or after firstDate', + ); + assert( + selectableDayPredicate == null || selectableDayPredicate(initialDate), + 'Provided initialDate must satisfy provided selectableDayPredicate', + ); + assert( + (onTapActionButton != null && textActionButton != null) || + onTapActionButton == null, + "If you provide onLeftBtn, you must provide leftBtn", + ); + assert(debugCheckHasMaterialLocalizations(context)); + + Widget child = GestureDetector( + onTap: () { + if (!barrierDismissible) { + Navigator.pop(context); + } + }, + child: Container( + color: background, + child: GestureDetector( + onTap: () { + // + }, + child: FlutterRoundedDatePickerDialog( + height: height, + initialDate: initialDate, + firstDate: firstDate, + lastDate: lastDate, + selectableDayPredicate: selectableDayPredicate, + initialDatePickerMode: initialDatePickerMode, + era: era, + locale: locale, + borderRadius: borderRadius, + imageHeader: imageHeader, + description: description, + fontFamily: fontFamily, + textNegativeButton: textNegativeButton, + textPositiveButton: textPositiveButton, + textActionButton: textActionButton, + onTapActionButton: onTapActionButton, + styleDatePicker: styleDatePicker, + styleYearPicker: styleYearPicker, + customWeekDays: customWeekDays, + builderDay: builderDay, + listDateDisabled: listDateDisabled, + onTapDay: onTapDay, + onMonthChange: onMonthChange, + ), + ), + ), + ); + + if (textDirection != null) { + child = Directionality( + textDirection: textDirection, + child: child, + ); + } + + if (locale != null) { + child = Localizations.override( + context: context, + locale: locale, + child: child, + ); + } + + return await showDialog( + context: context, + barrierDismissible: barrierDismissible, + builder: (_) => Theme(data: theme!, child: child), + ); +} diff --git a/lib/widgets/table_view/table_view_row.dart b/lib/widgets/table_view/table_view_row.dart index e95eb68bd..9c3175efe 100644 --- a/lib/widgets/table_view/table_view_row.dart +++ b/lib/widgets/table_view/table_view_row.dart @@ -3,7 +3,7 @@ import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/expandable.dart'; import 'package:stackwallet/widgets/table_view/table_view_cell.dart'; -class TableViewRow extends StatelessWidget { +class TableViewRow extends StatefulWidget { const TableViewRow({ Key? key, required this.cells, @@ -17,40 +17,66 @@ class TableViewRow extends StatelessWidget { final List cells; final Widget? expandingChild; - final Decoration? decoration; + final BoxDecoration? decoration; final void Function(ExpandableState)? onExpandChanged; final EdgeInsetsGeometry padding; final double spacing; final CrossAxisAlignment crossAxisAlignment; + @override + State createState() => _TableViewRowState(); +} + +class _TableViewRowState extends State { + late final List cells; + late final Widget? expandingChild; + late final BoxDecoration? decoration; + late final void Function(ExpandableState)? onExpandChanged; + late final EdgeInsetsGeometry padding; + late final double spacing; + late final CrossAxisAlignment crossAxisAlignment; + + bool _hovering = false; + + @override + void initState() { + cells = widget.cells; + expandingChild = widget.expandingChild; + decoration = widget.decoration; + onExpandChanged = widget.onExpandChanged; + padding = widget.padding; + spacing = widget.spacing; + crossAxisAlignment = widget.crossAxisAlignment; + super.initState(); + } + @override Widget build(BuildContext context) { return Container( - decoration: decoration, + decoration: !_hovering + ? decoration + : decoration?.copyWith( + boxShadow: [ + Theme.of(context).extension()!.standardBoxShadow, + Theme.of(context).extension()!.standardBoxShadow, + ], + ), child: expandingChild == null - ? Padding( - padding: padding, - child: Row( - crossAxisAlignment: crossAxisAlignment, - children: [ - for (int i = 0; i < cells.length; i++) ...[ - if (i != 0 && i != cells.length) - SizedBox( - width: spacing, - ), - Expanded( - flex: cells[i].flex, - child: cells[i], - ), - ], - ], - ), - ) - : Expandable( - onExpandChanged: onExpandChanged, - header: Padding( + ? MouseRegion( + onEnter: (_) { + setState(() { + _hovering = true; + }); + }, + onExit: (_) { + setState(() { + _hovering = false; + }); + }, + child: Padding( padding: padding, child: Row( + crossAxisAlignment: crossAxisAlignment, children: [ for (int i = 0; i < cells.length; i++) ...[ if (i != 0 && i != cells.length) @@ -65,6 +91,38 @@ class TableViewRow extends StatelessWidget { ], ), ), + ) + : Expandable( + onExpandChanged: onExpandChanged, + header: MouseRegion( + onEnter: (_) { + setState(() { + _hovering = true; + }); + }, + onExit: (_) { + setState(() { + _hovering = false; + }); + }, + child: Padding( + padding: padding, + child: Row( + children: [ + for (int i = 0; i < cells.length; i++) ...[ + if (i != 0 && i != cells.length) + SizedBox( + width: spacing, + ), + Expanded( + flex: cells[i].flex, + child: cells[i], + ), + ], + ], + ), + ), + ), body: Column( children: [ Container( diff --git a/lib/widgets/wallet_info_row/wallet_info_row.dart b/lib/widgets/wallet_info_row/wallet_info_row.dart index fe006a67b..5bb51e2e6 100644 --- a/lib/widgets/wallet_info_row/wallet_info_row.dart +++ b/lib/widgets/wallet_info_row/wallet_info_row.dart @@ -14,10 +14,12 @@ class WalletInfoRow extends ConsumerWidget { Key? key, required this.walletId, this.onPressed, + this.padding = const EdgeInsets.all(0), }) : super(key: key); final String walletId; final VoidCallback? onPressed; + final EdgeInsets padding; @override Widget build(BuildContext context, WidgetRef ref) { @@ -30,53 +32,56 @@ class WalletInfoRow extends ConsumerWidget { cursor: SystemMouseCursors.click, child: GestureDetector( onTap: onPressed, - child: Container( - color: Colors.transparent, - child: Row( - children: [ - Expanded( - flex: 4, - child: Row( - children: [ - WalletInfoCoinIcon(coin: manager.coin), - const SizedBox( - width: 12, - ), - Text( - manager.walletName, - style: - STextStyles.desktopTextExtraSmall(context).copyWith( + child: Padding( + padding: padding, + child: Container( + color: Colors.transparent, + child: Row( + children: [ + Expanded( + flex: 4, + child: Row( + children: [ + WalletInfoCoinIcon(coin: manager.coin), + const SizedBox( + width: 12, + ), + Text( + manager.walletName, + style: STextStyles.desktopTextExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark, + ), + ), + ], + ), + ), + Expanded( + flex: 4, + child: WalletInfoRowBalanceFuture( + walletId: walletId, + ), + ), + Expanded( + flex: 6, + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + SvgPicture.asset( + Assets.svg.chevronRight, + width: 20, + height: 20, color: Theme.of(context) .extension()! - .textDark, - ), - ), - ], - ), - ), - Expanded( - flex: 4, - child: WalletInfoRowBalanceFuture( - walletId: walletId, - ), - ), - Expanded( - flex: 6, - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - SvgPicture.asset( - Assets.svg.chevronRight, - width: 20, - height: 20, - color: Theme.of(context) - .extension()! - .textSubtitle1, - ) - ], - ), - ) - ], + .textSubtitle1, + ) + ], + ), + ) + ], + ), ), ), ),