Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions lib/features/settings/preferences_mcp_section.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import 'package:querya_desktop/core/security/connection_environment.dart';
import 'package:querya_desktop/core/storage/local_db.dart';
import 'package:querya_desktop/features/settings/preferences_controls.dart';
import 'package:querya_desktop/shared/widgets/widgets.dart';
import 'package:querya_desktop/core/ui/querya_control_tokens.dart';

/// Preferences → MCP Server (#1136): enable the in-app MCP server, choose the
/// connections AI clients may read, copy client configs, see recent calls.
Expand Down Expand Up @@ -256,14 +257,14 @@ class PreferencesMcpSectionState
key: material.ValueKey('mcp_copy_${kind.name}'),
label: 'Copy ${kind.label} config',
icon: material.Icons.copy_rounded,
size: ButtonSize.small,
size: QueryaControlSize.sm,
onPressed: () => unawaited(_copy(kind)),
),
QueryaActionButton(
key: const material.ValueKey('mcp_regenerate'),
label: 'Regenerate token',
icon: material.Icons.key_rounded,
size: ButtonSize.small,
size: QueryaControlSize.sm,
tooltip: 'Disconnects connected clients; they reconnect on '
'their next start.',
onPressed: _enabled && !_busy ? () => unawaited(_regenerate()) : null,
Expand All @@ -285,7 +286,7 @@ class PreferencesMcpSectionState
QueryaActionButton(
key: const material.ValueKey('mcp_clear_log'),
label: 'Clear',
size: ButtonSize.small,
size: QueryaControlSize.sm,
onPressed: _activity.isEmpty ? null : () => unawaited(_clearLog()),
),
],
Expand Down
66 changes: 23 additions & 43 deletions lib/shared/widgets/querya_action_button.dart
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import 'package:flutter/material.dart' as material;
import 'package:querya_desktop/core/theme/querya_theme_scope.dart';
import 'package:querya_desktop/core/ui/querya_control_tokens.dart';
import 'package:querya_desktop/core/ui/querya_tooltip.dart';
import 'package:querya_desktop/shared/widgets/querya_spinner.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart';
import 'package:querya_desktop/shared/widgets/querya_button.dart';

/// Labelled outline button for toolbars: optional leading icon, a [loading]
/// state with a spinner, and a destructive tone.
/// Labelled toolbar button: optional leading icon, a [loading] state and a
/// destructive tone. A thin wrapper over [QueryaButton.secondary]; new code
/// uses [QueryaButton] directly (epic #1331).
///
/// While [loading] the button is disabled and shows a [QueryaSpinner] in place
/// of [icon]. Icon and spinner follow the workbench accent, or the destructive
/// color when [isDestructive] is set.
/// While [loading] the button is disabled and shows a spinner in place of
/// [icon]. With [isDestructive] the label and icon use the workbench
/// destructive colour.
class QueryaActionButton extends material.StatelessWidget {
const QueryaActionButton({
super.key,
Expand All @@ -19,7 +19,7 @@ class QueryaActionButton extends material.StatelessWidget {
this.loading = false,
this.isDestructive = false,
this.tooltip,
this.size = ButtonSize.normal,
this.size,
this.compact = false,
});

Expand All @@ -32,52 +32,32 @@ class QueryaActionButton extends material.StatelessWidget {
/// Shows a spinner instead of [icon] and disables the button.
final bool loading;

/// Uses the destructive color for the icon and the label.
/// Uses the destructive colour for the icon and the label.
final bool isDestructive;

/// Shown on hover after [kQueryaTooltipWait].
final String? tooltip;
final ButtonSize size;

/// Control size; without one the nearest `QueryaControlScope`, then `md`.
final QueryaControlSize? size;

/// Hides the label and keeps the icon (narrow toolbars). The label becomes
/// the tooltip when none is set. Without an [icon], [compact] has no effect.
final bool compact;

@override
material.Widget build(material.BuildContext context) {
final workbench = context.workbench;
final tone = isDestructive ? workbench.destructive : workbench.accent;
final enabled = onPressed != null && !loading;

material.Widget? leading;
if (loading) {
leading = QueryaSpinner(size: QueryaSpinnerSize.sm, color: tone);
} else if (icon != null) {
leading = material.Icon(icon, size: 18, color: tone);
}

final iconOnly = compact && leading != null;
material.Widget button = OutlineButton(
return QueryaButton.secondary(
label: label,
icon: icon,
onPressed: onPressed,
loading: loading,
tooltip: tooltip,
size: size,
onPressed: enabled ? onPressed : null,
leading: leading,
child: iconOnly
? const material.SizedBox.shrink()
: isDestructive
? Text(label, style: material.TextStyle(color: tone))
: Text(label),
compact: compact,
tone: isDestructive
? QueryaButtonTone.destructive
: QueryaButtonTone.normal,
);

final hint = (tooltip != null && tooltip!.isNotEmpty)
? tooltip!
: (iconOnly ? label : null);
if (hint != null) {
button = material.Tooltip(
message: hint,
waitDuration: kQueryaTooltipWait,
child: button,
);
}
return button;
}
}
3 changes: 2 additions & 1 deletion test/features/extensions/extension_sql_workspace_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import 'package:querya_desktop/core/storage/app_settings.dart';
import 'package:querya_desktop/core/storage/local_db.dart';
import 'package:querya_desktop/features/extensions/extension_sql_workspace.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart';
import 'package:querya_desktop/shared/widgets/querya_button.dart';

import '../../support/querya_theme_test_shell.dart';

Expand Down Expand Up @@ -110,7 +111,7 @@ void main() {
await tester.pumpAndSettle();

// Find Execute button in SQL editor chrome
final executeBtn = find.widgetWithText(OutlineButton, 'Execute (F5)');
final executeBtn = find.widgetWithText(QueryaButton, 'Execute (F5)');
expect(executeBtn, findsOneWidget);

await tester.tap(executeBtn);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import 'package:querya_desktop/features/settings/preferences_shortcuts_section.d
import 'package:querya_desktop/features/workspace/generic_sql_workspace.dart';
import 'package:querya_desktop/features/workspace/results_tab.dart' show formatResultElapsed;
import 'package:querya_desktop/features/workspace/sql_execution_delegate.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart' show OutlineButton;
import 'package:querya_desktop/shared/widgets/querya_button.dart';

import '../../support/fake_sql_execution_delegate.dart';
import '../../support/local_db_test_support.dart';
Expand Down Expand Up @@ -98,7 +98,7 @@ void main() {
initialSql: 'SELECT n FROM t',
);

await tester.tap(find.widgetWithText(OutlineButton, 'Execute (F5)'));
await tester.tap(find.widgetWithText(QueryaButton, 'Execute (F5)'));
await settle(tester);

expect(delegate.executed, ['SELECT n FROM t']);
Expand Down Expand Up @@ -390,7 +390,7 @@ void main() {
await pumpWorkspace(tester, delegate, initialSql: 'SELECT n FROM t');
expect(find.byKey(const material.ValueKey('result_elapsed')), findsNothing);

await tester.tap(find.widgetWithText(OutlineButton, 'Execute (F5)'));
await tester.tap(find.widgetWithText(QueryaButton, 'Execute (F5)'));
await settle(tester);

expect(find.byKey(const material.ValueKey('result_elapsed')), findsOneWidget);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import 'package:querya_desktop/core/storage/local_db.dart';
import 'package:querya_desktop/features/sqlite/sqlite_sql_workspace.dart';
import 'package:querya_desktop/features/workspace/workspace.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart';
import 'package:querya_desktop/shared/widgets/querya_button.dart';
import 'package:sqflite_common_ffi/sqflite_ffi.dart';

import '../../support/querya_theme_test_shell.dart';
Expand Down Expand Up @@ -111,7 +112,7 @@ void main() {
expect(buffer.isDirty, isTrue);

// 1. Press Execute while buffer is dirty
final executeBtn = find.widgetWithText(OutlineButton, 'Execute (F5)');
final executeBtn = find.widgetWithText(QueryaButton, 'Execute (F5)');
expect(executeBtn, findsOneWidget);

// 3. Press Execute again while buffer is dirty
Expand Down
1 change: 0 additions & 1 deletion test/guards/shared_does_not_import_shadcn_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ void main() {
const allowed = <String>{
'lib/shared/widgets/app_toast.dart',
'lib/shared/widgets/export_menu_button.dart',
'lib/shared/widgets/querya_action_button.dart',
'lib/shared/widgets/querya_action_menu.dart',
'lib/shared/widgets/widgets.dart',
};
Expand Down
2 changes: 1 addition & 1 deletion test/shared/widgets/querya_action_button_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ void main() {
child: const QueryaActionButton(label: 'Begin'),
));

final button = tester.widget<OutlineButton>(find.byType(OutlineButton));
final button = tester.widget<QueryaButton>(find.byType(QueryaButton));
expect(button.onPressed, isNull);
});

Expand Down
Loading