From 11e578977ffbbe486fb7cf052634035ee1bf1787 Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Fri, 10 Jul 2026 16:39:36 +0300 Subject: [PATCH 1/3] fix(ui): disable workspace Execute/Refresh when query cannot run (#266) Replace the noop onPressed handler with a disabled OutlineButton and tooltip when no execute action is available. Add widget tests for the hidden empty-state button and the disabled fallback workspace button. --- lib/features/main_screen/workspace_panel.dart | 41 ++++++++++++++---- .../workspace_panel_layout_test.dart | 42 +++++++++++++++++++ 2 files changed, 74 insertions(+), 9 deletions(-) diff --git a/lib/features/main_screen/workspace_panel.dart b/lib/features/main_screen/workspace_panel.dart index 80d89b72..ea7d14ce 100644 --- a/lib/features/main_screen/workspace_panel.dart +++ b/lib/features/main_screen/workspace_panel.dart @@ -20,7 +20,8 @@ import 'package:flutter/material.dart' as material Row, MainAxisSize, Widget, - BoxConstraints; + BoxConstraints, + Tooltip; import 'package:querya_desktop/core/layout/vertical_split_pane.dart'; import 'package:querya_desktop/core/motion/querya_cross_fade_stack.dart'; import 'package:querya_desktop/core/motion/querya_motion.dart'; @@ -253,7 +254,7 @@ class _WorkspacePanelState extends State { tabs: const ['Query Editor', 'Query History'], index: _editorTabIndex, onTabChanged: (v) => setState(() => _editorTabIndex = v), - trailing: const _RunButton(), + trailing: _RunButton(activeConnection: activeConn), ), const Divider(height: 1), Expanded( @@ -407,7 +408,16 @@ class _TabButtonState extends State<_TabButton> { } class _RunButton extends StatefulWidget { - const _RunButton(); + const _RunButton({ + required this.activeConnection, + this.onExecute, + }); + + final ConnectionRow? activeConnection; + final VoidCallback? onExecute; + + static const _noConnectionTooltip = + 'Select an active database connection to execute queries'; @override State<_RunButton> createState() => _RunButtonState(); @@ -418,21 +428,34 @@ class _RunButtonState extends State<_RunButton> { @override Widget build(BuildContext context) { - return material.MouseRegion( - onEnter: (_) => setState(() => _hovered = true), - onExit: (_) => setState(() => _hovered = false), - cursor: material.SystemMouseCursors.click, + final canExecute = + widget.activeConnection != null && widget.onExecute != null; + final button = material.MouseRegion( + onEnter: canExecute ? (_) => setState(() => _hovered = true) : null, + onExit: canExecute ? (_) => setState(() => _hovered = false) : null, + cursor: canExecute + ? material.SystemMouseCursors.click + : material.SystemMouseCursors.basic, child: material.AnimatedScale( - scale: _hovered ? 1.03 : 1.0, + scale: canExecute && _hovered ? 1.03 : 1.0, duration: context.motionDuration(QueryaMotion.fast), curve: context.motionCurve(QueryaMotion.enter), child: OutlineButton( - onPressed: () {}, + key: const Key('workspace_run_button'), + onPressed: canExecute ? widget.onExecute : null, leading: const material.Icon(material.Icons.play_arrow, size: 18), child: const Text('Execute/Refresh (F5)'), ), ), ); + + if (canExecute) return button; + + return material.Tooltip( + message: _RunButton._noConnectionTooltip, + waitDuration: const Duration(milliseconds: 450), + child: button, + ); } } diff --git a/test/features/main_screen/workspace_panel_layout_test.dart b/test/features/main_screen/workspace_panel_layout_test.dart index 430f6778..8997a426 100644 --- a/test/features/main_screen/workspace_panel_layout_test.dart +++ b/test/features/main_screen/workspace_panel_layout_test.dart @@ -72,5 +72,47 @@ void main() { await tester.pumpAndSettle(); }); }); + + testWidgets('Execute button hidden when no active connection', (tester) async { + await pumpWidgetWithSurfaceSize( + tester, + const material.Size(800, 600), + queryaThemeTestShell( + child: const material.SizedBox.expand( + child: WorkspacePanel(), + ), + ), + ); + + expect(find.byKey(const Key('workspace_run_button')), findsNothing); + expect(find.text('Execute/Refresh (F5)'), findsNothing); + }); + + testWidgets('Execute button is disabled when execute is unavailable', + (tester) async { + await pumpWidgetWithSurfaceSize( + tester, + const material.Size(800, 600), + queryaThemeTestShell( + child: const material.SizedBox.expand( + child: WorkspacePanel( + activeConnection: stubSplitWorkspaceConnection, + ), + ), + ), + ); + + final buttonFinder = find.byKey(const Key('workspace_run_button')); + expect(buttonFinder, findsOneWidget); + final button = tester.widget(buttonFinder); + expect(button.onPressed, isNull); + expect( + find.text( + 'Select an active database connection to execute queries', + ), + findsNothing, + ); + expect(find.byType(material.Tooltip), findsWidgets); + }); }); } From 0a59710ce4fcdb703bd50bc15b0016f4cc5bbace Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Fri, 10 Jul 2026 16:43:42 +0300 Subject: [PATCH 2/3] fix(ui): simplify disabled RunButton to satisfy analyzer (#266) Remove unused onExecute parameter; keep onPressed null with tooltip. --- lib/features/main_screen/workspace_panel.dart | 42 ++++--------------- 1 file changed, 8 insertions(+), 34 deletions(-) diff --git a/lib/features/main_screen/workspace_panel.dart b/lib/features/main_screen/workspace_panel.dart index ea7d14ce..3a697b7e 100644 --- a/lib/features/main_screen/workspace_panel.dart +++ b/lib/features/main_screen/workspace_panel.dart @@ -254,7 +254,7 @@ class _WorkspacePanelState extends State { tabs: const ['Query Editor', 'Query History'], index: _editorTabIndex, onTabChanged: (v) => setState(() => _editorTabIndex = v), - trailing: _RunButton(activeConnection: activeConn), + trailing: const _RunButton(), ), const Divider(height: 1), Expanded( @@ -408,13 +408,7 @@ class _TabButtonState extends State<_TabButton> { } class _RunButton extends StatefulWidget { - const _RunButton({ - required this.activeConnection, - this.onExecute, - }); - - final ConnectionRow? activeConnection; - final VoidCallback? onExecute; + const _RunButton(); static const _noConnectionTooltip = 'Select an active database connection to execute queries'; @@ -424,37 +418,17 @@ class _RunButton extends StatefulWidget { } class _RunButtonState extends State<_RunButton> { - bool _hovered = false; - @override Widget build(BuildContext context) { - final canExecute = - widget.activeConnection != null && widget.onExecute != null; - final button = material.MouseRegion( - onEnter: canExecute ? (_) => setState(() => _hovered = true) : null, - onExit: canExecute ? (_) => setState(() => _hovered = false) : null, - cursor: canExecute - ? material.SystemMouseCursors.click - : material.SystemMouseCursors.basic, - child: material.AnimatedScale( - scale: canExecute && _hovered ? 1.03 : 1.0, - duration: context.motionDuration(QueryaMotion.fast), - curve: context.motionCurve(QueryaMotion.enter), - child: OutlineButton( - key: const Key('workspace_run_button'), - onPressed: canExecute ? widget.onExecute : null, - leading: const material.Icon(material.Icons.play_arrow, size: 18), - child: const Text('Execute/Refresh (F5)'), - ), - ), - ); - - if (canExecute) return button; - return material.Tooltip( message: _RunButton._noConnectionTooltip, waitDuration: const Duration(milliseconds: 450), - child: button, + child: OutlineButton( + key: const Key('workspace_run_button'), + onPressed: null, + leading: const material.Icon(material.Icons.play_arrow, size: 18), + child: const Text('Execute/Refresh (F5)'), + ), ); } } From dff1c039a9d239a68ffadb8c65a961625a94a834 Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Fri, 10 Jul 2026 16:46:42 +0300 Subject: [PATCH 3/3] fix(ui): address analyzer warnings in disabled RunButton (#266) Use a const StatelessWidget and drop unused AnimatedScale import. --- lib/features/main_screen/workspace_panel.dart | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/lib/features/main_screen/workspace_panel.dart b/lib/features/main_screen/workspace_panel.dart index 3a697b7e..77d41cdc 100644 --- a/lib/features/main_screen/workspace_panel.dart +++ b/lib/features/main_screen/workspace_panel.dart @@ -13,7 +13,6 @@ import 'package:flutter/material.dart' as material Icons, MouseRegion, AnimatedContainer, - AnimatedScale, SystemMouseCursors, SizedBox, SingleChildScrollView, @@ -407,27 +406,22 @@ class _TabButtonState extends State<_TabButton> { } } -class _RunButton extends StatefulWidget { +class _RunButton extends StatelessWidget { const _RunButton(); static const _noConnectionTooltip = 'Select an active database connection to execute queries'; - @override - State<_RunButton> createState() => _RunButtonState(); -} - -class _RunButtonState extends State<_RunButton> { @override Widget build(BuildContext context) { - return material.Tooltip( - message: _RunButton._noConnectionTooltip, - waitDuration: const Duration(milliseconds: 450), + return const material.Tooltip( + message: _noConnectionTooltip, + waitDuration: Duration(milliseconds: 450), child: OutlineButton( - key: const Key('workspace_run_button'), + key: Key('workspace_run_button'), onPressed: null, - leading: const material.Icon(material.Icons.play_arrow, size: 18), - child: const Text('Execute/Refresh (F5)'), + leading: material.Icon(material.Icons.play_arrow, size: 18), + child: Text('Execute/Refresh (F5)'), ), ); }