From 7b0da9469946509a581b135c4f2cb20b38c0e037 Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Mon, 5 Oct 2026 10:10:09 +0300 Subject: [PATCH] refactor(ui): migrate views to Querya UI Kit and add architectural guard tests (#1039) --- lib/core/sdui/sdui_tree_builder.dart | 9 ++-- .../extension_connection_form.dart | 10 ++-- .../connections/sqlite_connection_form.dart | 11 ++--- .../extension_driver_recovery_banner.dart | 10 ++-- .../extensions/extension_sql_workspace.dart | 20 +++----- .../extensions/extension_stats_view.dart | 16 ++----- .../extensions/extension_table_toolbar.dart | 8 +--- .../extensions/extension_table_view.dart | 2 +- .../pages/extension_manager_dialog.dart | 6 +-- .../widgets/extension_sideload_dialog.dart | 2 +- .../unsandboxed_driver_consent_dialog.dart | 2 +- .../main_screen/querya_status_bar.dart | 11 ++--- .../mongodb/mongo_collections_view.dart | 16 ++----- .../mongodb/mongo_databases_view.dart | 16 ++----- .../mongodb/mongo_document_editor.dart | 7 +-- .../mongodb/mongo_documents_view.dart | 29 ++++-------- lib/features/mongodb/mongo_explorer_view.dart | 16 ++----- lib/features/mongodb/mongo_stats_view.dart | 22 ++------- .../mongodb/mongodb_connection_form.dart | 11 ++--- lib/features/mysql/mysql_connection_form.dart | 11 ++--- lib/features/mysql/mysql_routine_view.dart | 2 +- lib/features/mysql/mysql_sql_tx_guard.dart | 23 +++------ lib/features/mysql/mysql_sql_workspace.dart | 10 ++-- lib/features/mysql/mysql_stats_view.dart | 16 ++----- .../postgresql/postgres_browser_views.dart | 10 ++-- .../postgresql/postgres_routine_view.dart | 16 ++----- .../postgresql/postgres_sequence_view.dart | 16 ++----- .../postgresql/postgres_sql_tx_guard.dart | 23 +++------ .../postgresql/postgres_sql_workspace.dart | 10 ++-- .../postgresql/postgres_stats_view.dart | 16 ++----- .../postgres_table_privileges_dialog.dart | 2 +- .../postgresql_connection_form.dart | 11 ++--- lib/features/redis/redis_cli_workspace.dart | 9 +--- lib/features/redis/redis_connection_form.dart | 11 ++--- lib/features/redis/redis_databases_view.dart | 16 ++----- lib/features/redis/redis_explorer_view.dart | 16 ++----- lib/features/redis/redis_key_editor.dart | 45 +++++------------- lib/features/redis/redis_keys_view.dart | 26 ++++------ lib/features/redis/redis_view.dart | 16 ++----- .../settings/theme_color_picker_dialog.dart | 6 +-- .../settings/theme_editor_dialog.dart | 47 +++---------------- lib/features/settings/theme_preview_card.dart | 14 ++---- .../settings/theme_remote_install_dialog.dart | 2 +- lib/features/sqlite/sqlite_overview_tab.dart | 16 ++----- lib/features/sqlite/sqlite_sql_tx_guard.dart | 23 +++------ lib/features/sqlite/sqlite_sql_workspace.dart | 10 ++-- lib/features/sqlite/sqlite_table_view.dart | 2 +- lib/features/updater/update_dialog.dart | 2 +- .../workspace/data_grid_staging_toolbar.dart | 10 ++-- .../grid_cell_popover_inspector.dart | 10 ++-- lib/features/workspace/results_tab.dart | 18 ++----- .../workspace/sql_query_history_dialog.dart | 2 +- .../widgets/connection_tree_loading_row.dart | 9 ++-- lib/shared/widgets/querya_modal_dialog.dart | 17 +++---- .../features/redis/redis_key_editor_test.dart | 5 +- test/guards/no_raw_material_dialogs_test.dart | 27 +++++++++++ .../no_raw_progress_indicators_test.dart | 29 ++++++++++++ 57 files changed, 271 insertions(+), 507 deletions(-) create mode 100644 test/guards/no_raw_material_dialogs_test.dart create mode 100644 test/guards/no_raw_progress_indicators_test.dart diff --git a/lib/core/sdui/sdui_tree_builder.dart b/lib/core/sdui/sdui_tree_builder.dart index 95a9b714..14bee9a1 100644 --- a/lib/core/sdui/sdui_tree_builder.dart +++ b/lib/core/sdui/sdui_tree_builder.dart @@ -263,12 +263,9 @@ class SduiTreeBuilderState extends material.State { expandable: node.expandable, ), iconWidget: isLoading - ? const material.SizedBox( - width: QueryaTreeTokens.spinnerNested, - height: QueryaTreeTokens.spinnerNested, - child: material.CircularProgressIndicator( - strokeWidth: QueryaTreeTokens.spinnerStroke, - ), + ? const QueryaSpinner( + customDimension: QueryaTreeTokens.spinnerNested, + strokeWidth: QueryaTreeTokens.spinnerStroke, ) : null, iconSize: iconSize, diff --git a/lib/features/connections/extension_connection_form.dart b/lib/features/connections/extension_connection_form.dart index 2f63e333..11487023 100644 --- a/lib/features/connections/extension_connection_form.dart +++ b/lib/features/connections/extension_connection_form.dart @@ -238,7 +238,7 @@ class _ExtensionConnectionFormContentState const material.Padding( padding: material.EdgeInsets.all(24), child: material.Center( - child: material.CircularProgressIndicator(), + child: QueryaSpinner(size: QueryaSpinnerSize.md), ), ) else if (_loadError != null) @@ -287,12 +287,8 @@ class _ExtensionConnectionFormContentState onPressed: _schema == null || _testing ? null : _testConnection, leading: _testing - ? const material.SizedBox( - width: 14, - height: 14, - child: material.CircularProgressIndicator( - strokeWidth: 2, - ), + ? const QueryaSpinner( + size: QueryaSpinnerSize.sm, ) : const material.Icon( material.Icons.bolt_rounded, diff --git a/lib/features/connections/sqlite_connection_form.dart b/lib/features/connections/sqlite_connection_form.dart index 08c6769f..0ca254cf 100644 --- a/lib/features/connections/sqlite_connection_form.dart +++ b/lib/features/connections/sqlite_connection_form.dart @@ -392,13 +392,10 @@ class _SqliteConnectionFormContentState onPressed: formValid && !_isTesting ? _testConnection : null, leading: _isTesting - ? material.SizedBox( - width: 18, - height: 18, - child: material.CircularProgressIndicator( - strokeWidth: 2, - color: theme.primary, - ), + ? QueryaSpinner( + customDimension: 18, + strokeWidth: 2, + color: theme.primary, ) : material.Icon( material.Icons.link_rounded, diff --git a/lib/features/extensions/extension_driver_recovery_banner.dart b/lib/features/extensions/extension_driver_recovery_banner.dart index 1dd6f15b..f9947bf9 100644 --- a/lib/features/extensions/extension_driver_recovery_banner.dart +++ b/lib/features/extensions/extension_driver_recovery_banner.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart' as material; -import 'package:shadcn_flutter/shadcn_flutter.dart'; +import 'package:querya_desktop/shared/widgets/widgets.dart'; /// Banner displayed when an extension driver process crashes, deadlocks, /// or disconnects unexpectedly, allowing the user to restart the driver process @@ -65,12 +65,8 @@ class ExtensionDriverRecoveryBanner extends material.StatelessWidget { size: ButtonSize.small, onPressed: isRestarting ? null : onRestart, leading: isRestarting - ? const material.SizedBox( - width: 14, - height: 14, - child: material.CircularProgressIndicator( - strokeWidth: 2, - ), + ? const QueryaSpinner( + size: QueryaSpinnerSize.sm, ) : const material.Icon( material.Icons.restart_alt_rounded, diff --git a/lib/features/extensions/extension_sql_workspace.dart b/lib/features/extensions/extension_sql_workspace.dart index cc2f20eb..ae175303 100644 --- a/lib/features/extensions/extension_sql_workspace.dart +++ b/lib/features/extensions/extension_sql_workspace.dart @@ -707,13 +707,9 @@ class _ExtensionSqlToolbar extends material.StatelessWidget { IconButton.ghost( onPressed: running || isRestarting ? null : onRestartDriver, icon: isRestarting - ? material.SizedBox( - width: 14, - height: 14, - child: material.CircularProgressIndicator( - strokeWidth: 2, - color: accent, - ), + ? QueryaSpinner( + size: QueryaSpinnerSize.sm, + color: accent, ) : material.Icon( material.Icons.restart_alt_rounded, @@ -755,13 +751,9 @@ class _ExtensionSqlToolbar extends material.StatelessWidget { OutlineButton( onPressed: onExecute, leading: running - ? material.SizedBox( - width: 16, - height: 16, - child: material.CircularProgressIndicator( - strokeWidth: 2, - color: accent, - ), + ? QueryaSpinner( + size: QueryaSpinnerSize.sm, + color: accent, ) : material.Icon( material.Icons.play_arrow_rounded, diff --git a/lib/features/extensions/extension_stats_view.dart b/lib/features/extensions/extension_stats_view.dart index 426ebace..aa199ba0 100644 --- a/lib/features/extensions/extension_stats_view.dart +++ b/lib/features/extensions/extension_stats_view.dart @@ -127,18 +127,10 @@ class _ExtensionStatsViewState extends material.State { final cs = Theme.of(context).colorScheme; if (_loading) { - return material.Center( - child: material.Column( - mainAxisSize: material.MainAxisSize.min, - children: [ - const material.SizedBox( - width: 32, - height: 32, - child: material.CircularProgressIndicator(strokeWidth: 2), - ), - const Gap(16), - const Text('Loading server statistics...').muted().small(), - ], + return const material.Center( + child: QueryaSpinner( + size: QueryaSpinnerSize.lg, + label: 'Loading server statistics...', ), ); } diff --git a/lib/features/extensions/extension_table_toolbar.dart b/lib/features/extensions/extension_table_toolbar.dart index f23805a9..92b038f5 100644 --- a/lib/features/extensions/extension_table_toolbar.dart +++ b/lib/features/extensions/extension_table_toolbar.dart @@ -210,13 +210,7 @@ class ExtensionTableToolbar extends material.StatelessWidget { size: ButtonSize.small, onPressed: loading || isRestarting ? null : onRestartDriver, leading: isRestarting - ? const material.SizedBox( - width: 14, - height: 14, - child: material.CircularProgressIndicator( - strokeWidth: 2, - ), - ) + ? const QueryaSpinner(size: QueryaSpinnerSize.sm) : const material.Icon( material.Icons.restart_alt_rounded, size: 15, diff --git a/lib/features/extensions/extension_table_view.dart b/lib/features/extensions/extension_table_view.dart index aaf5ad5f..4929fdfe 100644 --- a/lib/features/extensions/extension_table_view.dart +++ b/lib/features/extensions/extension_table_view.dart @@ -512,7 +512,7 @@ class _ExtensionTableViewState extends material.State { context: context, barrierDismissible: false, builder: (ctx) => const material.Center( - child: material.CircularProgressIndicator(), + child: QueryaSpinner(size: QueryaSpinnerSize.lg), ), )); diff --git a/lib/features/extensions/presentation/pages/extension_manager_dialog.dart b/lib/features/extensions/presentation/pages/extension_manager_dialog.dart index 032aa1a7..5781c635 100644 --- a/lib/features/extensions/presentation/pages/extension_manager_dialog.dart +++ b/lib/features/extensions/presentation/pages/extension_manager_dialog.dart @@ -273,7 +273,7 @@ class _ExtensionManagerContentState material.Widget _buildInstalledTab() { if (_loading) { return const material.Center( - child: material.CircularProgressIndicator(), + child: QueryaSpinner(size: QueryaSpinnerSize.lg), ); } return material.Column( @@ -342,7 +342,7 @@ class _ExtensionManagerContentState material.Widget _buildMarketplaceTab() { if (_loading) { return const material.Center( - child: material.CircularProgressIndicator(), + child: QueryaSpinner(size: QueryaSpinnerSize.lg), ); } final theme = Theme.of(context).colorScheme; @@ -431,7 +431,7 @@ class _ExtensionManagerContentState material.Widget _buildUpdatesTab() { if (_loading) { return const material.Center( - child: material.CircularProgressIndicator(), + child: QueryaSpinner(size: QueryaSpinnerSize.lg), ); } final theme = Theme.of(context).colorScheme; diff --git a/lib/features/extensions/presentation/widgets/extension_sideload_dialog.dart b/lib/features/extensions/presentation/widgets/extension_sideload_dialog.dart index dfb84ff7..842921ca 100644 --- a/lib/features/extensions/presentation/widgets/extension_sideload_dialog.dart +++ b/lib/features/extensions/presentation/widgets/extension_sideload_dialog.dart @@ -60,7 +60,7 @@ class _ExtensionSideloadDialogState final cs = Theme.of(context).colorScheme; final fileName = p.basename(widget.archivePath); - return material.AlertDialog( + return QueryaModalDialog( title: const material.Text('Install local extension'), content: material.SizedBox( width: 440, diff --git a/lib/features/extensions/presentation/widgets/unsandboxed_driver_consent_dialog.dart b/lib/features/extensions/presentation/widgets/unsandboxed_driver_consent_dialog.dart index dbba764d..727c2afe 100644 --- a/lib/features/extensions/presentation/widgets/unsandboxed_driver_consent_dialog.dart +++ b/lib/features/extensions/presentation/widgets/unsandboxed_driver_consent_dialog.dart @@ -8,7 +8,7 @@ Future showUnsandboxedDriverConsentDialog( ) async { final approved = await showAppDialog( context: context, - builder: (dialogContext) => material.AlertDialog( + builder: (dialogContext) => QueryaModalDialog( title: const material.Text('Run driver without OS sandbox?'), content: material.SizedBox( width: 440, diff --git a/lib/features/main_screen/querya_status_bar.dart b/lib/features/main_screen/querya_status_bar.dart index bea5f7ff..5a77e691 100644 --- a/lib/features/main_screen/querya_status_bar.dart +++ b/lib/features/main_screen/querya_status_bar.dart @@ -168,13 +168,10 @@ class QueryaStatusBar extends material.StatelessWidget { if (isBusy) ...[ const Gap(10), - material.SizedBox( - width: 10, - height: 10, - child: material.CircularProgressIndicator( - strokeWidth: 1.5, - color: wb.accent, - ), + QueryaSpinner( + customDimension: 10, + strokeWidth: 1.5, + color: wb.accent, ), ], if (statusMessage != null && statusMessage!.isNotEmpty) ...[ diff --git a/lib/features/mongodb/mongo_collections_view.dart b/lib/features/mongodb/mongo_collections_view.dart index 4d504e87..8f768703 100644 --- a/lib/features/mongodb/mongo_collections_view.dart +++ b/lib/features/mongodb/mongo_collections_view.dart @@ -213,18 +213,10 @@ class _MongoCollectionsViewState extends material.State { final cs = Theme.of(context).colorScheme; if (_loading) { - return material.Center( - child: material.Column( - mainAxisSize: material.MainAxisSize.min, - children: [ - const material.SizedBox( - width: 32, - height: 32, - child: material.CircularProgressIndicator(strokeWidth: 2), - ), - const Gap(16), - const Text('Loading collections...').muted().small(), - ], + return const material.Center( + child: QueryaSpinner( + size: QueryaSpinnerSize.lg, + label: 'Loading collections...', ), ); } diff --git a/lib/features/mongodb/mongo_databases_view.dart b/lib/features/mongodb/mongo_databases_view.dart index 1e3a8dca..7a87de66 100644 --- a/lib/features/mongodb/mongo_databases_view.dart +++ b/lib/features/mongodb/mongo_databases_view.dart @@ -221,18 +221,10 @@ class _MongoDatabasesViewState extends State { final cs = Theme.of(context).colorScheme; if (_isLoading) { - return material.Center( - child: material.Column( - mainAxisSize: material.MainAxisSize.min, - children: [ - const material.SizedBox( - width: 32, - height: 32, - child: material.CircularProgressIndicator(strokeWidth: 2), - ), - const Gap(16), - const Text('Loading databases...').muted().small(), - ], + return const material.Center( + child: QueryaSpinner( + size: QueryaSpinnerSize.lg, + label: 'Loading databases...', ), ); } diff --git a/lib/features/mongodb/mongo_document_editor.dart b/lib/features/mongodb/mongo_document_editor.dart index 33f6931e..400a0110 100644 --- a/lib/features/mongodb/mongo_document_editor.dart +++ b/lib/features/mongodb/mongo_document_editor.dart @@ -295,12 +295,7 @@ class _MongoDocumentEditorState extends material.State { onPressed: _saving ? null : _save, size: ButtonSize.small, leading: _saving - ? const material.SizedBox( - width: 14, - height: 14, - child: - material.CircularProgressIndicator(strokeWidth: 2), - ) + ? const QueryaSpinner(size: QueryaSpinnerSize.sm) : const material.Icon(material.Icons.save_rounded, size: 14), child: Text(_saving ? 'Saving...' : 'Save'), diff --git a/lib/features/mongodb/mongo_documents_view.dart b/lib/features/mongodb/mongo_documents_view.dart index 69ea0c53..a44690b1 100644 --- a/lib/features/mongodb/mongo_documents_view.dart +++ b/lib/features/mongodb/mongo_documents_view.dart @@ -363,18 +363,10 @@ class _MongoDocumentsViewState extends material.State { final cs = Theme.of(context).colorScheme; if (_loading && _documents.isEmpty) { - return material.Center( - child: material.Column( - mainAxisSize: material.MainAxisSize.min, - children: [ - const material.SizedBox( - width: 32, - height: 32, - child: material.CircularProgressIndicator(strokeWidth: 2), - ), - const Gap(16), - const Text('Loading documents...').muted().small(), - ], + return const material.Center( + child: QueryaSpinner( + size: QueryaSpinnerSize.lg, + label: 'Loading documents...', ), ); } @@ -456,16 +448,15 @@ class _MongoDocumentsViewState extends material.State { ), child: Row( children: [ - material.Icon(material.Icons.filter_list_rounded, - size: 18, color: shadcnCs.mutedForeground), - const Gap(10), material.Expanded( - child: TextField( + child: QueryaSearchField( controller: _filterController, - placeholder: const Text( - r'Filter (JSON / EJSON) e.g. {"_id": {"$oid": "…"}}', - ), + placeholder: + r'Filter (JSON / EJSON) e.g. {"_id": {"$oid": "…"}}', onSubmitted: (_) => _applyFilter(), + onChanged: (val) { + if (val.isEmpty) _clearFilter(); + }, ), ), const Gap(8), diff --git a/lib/features/mongodb/mongo_explorer_view.dart b/lib/features/mongodb/mongo_explorer_view.dart index 5d4a903a..c13ffc53 100644 --- a/lib/features/mongodb/mongo_explorer_view.dart +++ b/lib/features/mongodb/mongo_explorer_view.dart @@ -245,18 +245,10 @@ class _MongoExplorerViewState extends material.State { // Loading state if (_connecting) { - return material.Center( - child: material.Column( - mainAxisSize: material.MainAxisSize.min, - children: [ - const material.SizedBox( - width: 32, - height: 32, - child: material.CircularProgressIndicator(strokeWidth: 2), - ), - const Gap(16), - const Text('Connecting...').muted().small(), - ], + return const material.Center( + child: QueryaSpinner( + size: QueryaSpinnerSize.lg, + label: 'Connecting...', ), ); } diff --git a/lib/features/mongodb/mongo_stats_view.dart b/lib/features/mongodb/mongo_stats_view.dart index 0d69d077..a93790f3 100644 --- a/lib/features/mongodb/mongo_stats_view.dart +++ b/lib/features/mongodb/mongo_stats_view.dart @@ -209,18 +209,10 @@ class _MongoStatsViewState extends material.State { final cs = Theme.of(context).colorScheme; if (_loading) { - return material.Center( - child: material.Column( - mainAxisSize: material.MainAxisSize.min, - children: [ - const material.SizedBox( - width: 32, - height: 32, - child: material.CircularProgressIndicator(strokeWidth: 2), - ), - const Gap(16), - const Text('Connecting...').muted().small(), - ], + return const material.Center( + child: QueryaSpinner( + size: QueryaSpinnerSize.lg, + label: 'Connecting...', ), ); } @@ -422,11 +414,7 @@ class _MongoStatsViewState extends material.State { OutlineButton( onPressed: _manualRefreshing ? null : _refreshNow, leading: _manualRefreshing - ? const material.SizedBox( - width: 16, - height: 16, - child: material.CircularProgressIndicator(strokeWidth: 2), - ) + ? const QueryaSpinner(size: QueryaSpinnerSize.sm) : const material.Icon(material.Icons.refresh_rounded, size: 18), child: const Text('Refresh now'), diff --git a/lib/features/mongodb/mongodb_connection_form.dart b/lib/features/mongodb/mongodb_connection_form.dart index 179028ad..13b6ee90 100644 --- a/lib/features/mongodb/mongodb_connection_form.dart +++ b/lib/features/mongodb/mongodb_connection_form.dart @@ -664,13 +664,10 @@ class _MongoConnectionFormContentState onPressed: formValid && !_isTesting ? _testConnection : null, leading: _isTesting - ? material.SizedBox( - width: 18, - height: 18, - child: material.CircularProgressIndicator( - strokeWidth: 2, - color: theme.primary, - ), + ? QueryaSpinner( + customDimension: 18, + strokeWidth: 2, + color: theme.primary, ) : material.Icon( material.Icons.link_rounded, diff --git a/lib/features/mysql/mysql_connection_form.dart b/lib/features/mysql/mysql_connection_form.dart index 7406447e..4994c57f 100644 --- a/lib/features/mysql/mysql_connection_form.dart +++ b/lib/features/mysql/mysql_connection_form.dart @@ -576,13 +576,10 @@ class _MysqlConnectionFormContentState onPressed: formValid && !_isTesting ? _testConnection : null, leading: _isTesting - ? material.SizedBox( - width: 18, - height: 18, - child: material.CircularProgressIndicator( - strokeWidth: 2, - color: theme.primary, - ), + ? QueryaSpinner( + customDimension: 18, + strokeWidth: 2, + color: theme.primary, ) : material.Icon( material.Icons.link_rounded, diff --git a/lib/features/mysql/mysql_routine_view.dart b/lib/features/mysql/mysql_routine_view.dart index f3032b3f..de493d4e 100644 --- a/lib/features/mysql/mysql_routine_view.dart +++ b/lib/features/mysql/mysql_routine_view.dart @@ -168,7 +168,7 @@ class _MysqlRoutineViewState extends material.State { if (_loading) const material.Expanded( child: material.Center( - child: material.CircularProgressIndicator(), + child: QueryaSpinner(size: QueryaSpinnerSize.lg), ), ) else if (_error != null) diff --git a/lib/features/mysql/mysql_sql_tx_guard.dart b/lib/features/mysql/mysql_sql_tx_guard.dart index 8261e9c2..001e11e9 100644 --- a/lib/features/mysql/mysql_sql_tx_guard.dart +++ b/lib/features/mysql/mysql_sql_tx_guard.dart @@ -18,25 +18,14 @@ String mysqlSqlToolbarTxLabel(bool? txOpen) { Future confirmLeaveOpenMysqlTransaction( material.BuildContext context, ) async { - final ok = await showAppDialog( + final ok = await QueryaConfirmDialog.show( context: context, - builder: (ctx) => material.AlertDialog( - title: const material.Text('Open transaction'), - content: const material.Text( - 'The SQL tab has an open transaction. Leave anyway? ' + title: 'Open transaction', + message: 'The SQL tab has an open transaction. Leave anyway? ' 'Uncommitted work may be lost if the session ends.', - ), - actions: [ - material.TextButton( - onPressed: () => material.Navigator.of(ctx).pop(false), - child: const material.Text('Stay'), - ), - material.TextButton( - onPressed: () => material.Navigator.of(ctx).pop(true), - child: const material.Text('Leave'), - ), - ], - ), + confirmLabel: 'Leave', + cancelLabel: 'Stay', + isDestructive: true, ); return ok == true; } diff --git a/lib/features/mysql/mysql_sql_workspace.dart b/lib/features/mysql/mysql_sql_workspace.dart index 2a7b1e64..eadea64d 100644 --- a/lib/features/mysql/mysql_sql_workspace.dart +++ b/lib/features/mysql/mysql_sql_workspace.dart @@ -1030,13 +1030,9 @@ class _MysqlSqlToolbar extends material.StatelessWidget { OutlineButton( onPressed: onExecute, leading: running - ? material.SizedBox( - width: 16, - height: 16, - child: material.CircularProgressIndicator( - strokeWidth: 2, - color: accent, - ), + ? QueryaSpinner( + size: QueryaSpinnerSize.sm, + color: accent, ) : material.Icon( material.Icons.play_arrow_rounded, diff --git a/lib/features/mysql/mysql_stats_view.dart b/lib/features/mysql/mysql_stats_view.dart index d8727857..3ec4afa6 100644 --- a/lib/features/mysql/mysql_stats_view.dart +++ b/lib/features/mysql/mysql_stats_view.dart @@ -150,18 +150,10 @@ class _MysqlStatsViewState extends material.State { final cs = Theme.of(context).colorScheme; if (_loading) { - return material.Center( - child: material.Column( - mainAxisSize: material.MainAxisSize.min, - children: [ - const material.SizedBox( - width: 32, - height: 32, - child: material.CircularProgressIndicator(strokeWidth: 2), - ), - const Gap(16), - const Text('Connecting...').muted().small(), - ], + return const material.Center( + child: QueryaSpinner( + size: QueryaSpinnerSize.lg, + label: 'Connecting...', ), ); } diff --git a/lib/features/postgresql/postgres_browser_views.dart b/lib/features/postgresql/postgres_browser_views.dart index 4d67d575..4b3b23e0 100644 --- a/lib/features/postgresql/postgres_browser_views.dart +++ b/lib/features/postgresql/postgres_browser_views.dart @@ -99,7 +99,7 @@ class _PostgresIndexListViewState return material.Container( color: cs.background, child: const material.Center( - child: material.CircularProgressIndicator(strokeWidth: 2), + child: QueryaSpinner(size: QueryaSpinnerSize.md), ), ); } @@ -279,7 +279,7 @@ class _PostgresTriggerListViewState return material.Container( color: cs.background, child: const material.Center( - child: material.CircularProgressIndicator(strokeWidth: 2), + child: QueryaSpinner(size: QueryaSpinnerSize.md), ), ); } @@ -449,7 +449,7 @@ class _PostgresTypeListViewState extends material.State { final cs = Theme.of(context).colorScheme; if (_loading) { return const material.Center( - child: material.CircularProgressIndicator(strokeWidth: 2), + child: QueryaSpinner(size: QueryaSpinnerSize.md), ); } if (_error != null) { @@ -601,7 +601,7 @@ class _PostgresExtensionListViewState final cs = Theme.of(context).colorScheme; if (_loading) { return const material.Center( - child: material.CircularProgressIndicator(strokeWidth: 2), + child: QueryaSpinner(size: QueryaSpinnerSize.md), ); } if (_error != null) { @@ -750,7 +750,7 @@ class _PostgresFdwListViewState extends material.State { final cs = Theme.of(context).colorScheme; if (_loading) { return const material.Center( - child: material.CircularProgressIndicator(strokeWidth: 2), + child: QueryaSpinner(size: QueryaSpinnerSize.md), ); } if (_error != null) { diff --git a/lib/features/postgresql/postgres_routine_view.dart b/lib/features/postgresql/postgres_routine_view.dart index 4bb8b251..36af6bf4 100644 --- a/lib/features/postgresql/postgres_routine_view.dart +++ b/lib/features/postgresql/postgres_routine_view.dart @@ -137,18 +137,10 @@ class _PostgresRoutineViewState extends material.State { if (_loading) { return material.Container( color: cs.background, - child: material.Center( - child: material.Column( - mainAxisSize: material.MainAxisSize.min, - children: [ - const material.SizedBox( - width: 28, - height: 28, - child: material.CircularProgressIndicator(strokeWidth: 2), - ), - const Gap(12), - const Text('Loading function…').muted().small(), - ], + child: const material.Center( + child: QueryaSpinner( + size: QueryaSpinnerSize.md, + label: 'Loading function…', ), ), ); diff --git a/lib/features/postgresql/postgres_sequence_view.dart b/lib/features/postgresql/postgres_sequence_view.dart index 407c855d..08083997 100644 --- a/lib/features/postgresql/postgres_sequence_view.dart +++ b/lib/features/postgresql/postgres_sequence_view.dart @@ -120,18 +120,10 @@ class _PostgresSequenceViewState extends material.State { if (_loading) { return material.Container( color: cs.background, - child: material.Center( - child: material.Column( - mainAxisSize: material.MainAxisSize.min, - children: [ - const material.SizedBox( - width: 28, - height: 28, - child: material.CircularProgressIndicator(strokeWidth: 2), - ), - const Gap(12), - const Text('Loading sequence…').muted().small(), - ], + child: const material.Center( + child: QueryaSpinner( + size: QueryaSpinnerSize.md, + label: 'Loading sequence…', ), ), ); diff --git a/lib/features/postgresql/postgres_sql_tx_guard.dart b/lib/features/postgresql/postgres_sql_tx_guard.dart index 4c05d9eb..73f77884 100644 --- a/lib/features/postgresql/postgres_sql_tx_guard.dart +++ b/lib/features/postgresql/postgres_sql_tx_guard.dart @@ -15,25 +15,14 @@ bool postgresObjectOpensTableBrowser(PostgresObjectKind kind) { Future confirmLeaveOpenPostgresTransaction( material.BuildContext context, ) async { - final ok = await showAppDialog( + final ok = await QueryaConfirmDialog.show( context: context, - builder: (ctx) => material.AlertDialog( - title: const material.Text('Open transaction'), - content: const material.Text( - 'The SQL tab has an open transaction. Leave anyway? ' + title: 'Open transaction', + message: 'The SQL tab has an open transaction. Leave anyway? ' 'Uncommitted work may be lost if the session ends.', - ), - actions: [ - material.TextButton( - onPressed: () => material.Navigator.of(ctx).pop(false), - child: const material.Text('Stay'), - ), - material.TextButton( - onPressed: () => material.Navigator.of(ctx).pop(true), - child: const material.Text('Leave'), - ), - ], - ), + confirmLabel: 'Leave', + cancelLabel: 'Stay', + isDestructive: true, ); return ok == true; } diff --git a/lib/features/postgresql/postgres_sql_workspace.dart b/lib/features/postgresql/postgres_sql_workspace.dart index 1c21cf15..cd8e1326 100644 --- a/lib/features/postgresql/postgres_sql_workspace.dart +++ b/lib/features/postgresql/postgres_sql_workspace.dart @@ -1116,13 +1116,9 @@ class _SqlToolbar extends material.StatelessWidget { OutlineButton( onPressed: onExecute, leading: running - ? material.SizedBox( - width: 16, - height: 16, - child: material.CircularProgressIndicator( - strokeWidth: 2, - color: accent, - ), + ? QueryaSpinner( + size: QueryaSpinnerSize.sm, + color: accent, ) : material.Icon( material.Icons.play_arrow_rounded, diff --git a/lib/features/postgresql/postgres_stats_view.dart b/lib/features/postgresql/postgres_stats_view.dart index 870cf4e3..bb721ea4 100644 --- a/lib/features/postgresql/postgres_stats_view.dart +++ b/lib/features/postgresql/postgres_stats_view.dart @@ -167,18 +167,10 @@ class _PostgresStatsViewState extends material.State { final cs = Theme.of(context).colorScheme; if (_loading) { - return material.Center( - child: material.Column( - mainAxisSize: material.MainAxisSize.min, - children: [ - const material.SizedBox( - width: 32, - height: 32, - child: material.CircularProgressIndicator(strokeWidth: 2), - ), - const Gap(16), - const Text('Connecting...').muted().small(), - ], + return const material.Center( + child: QueryaSpinner( + size: QueryaSpinnerSize.lg, + label: 'Connecting...', ), ); } diff --git a/lib/features/postgresql/postgres_table_privileges_dialog.dart b/lib/features/postgresql/postgres_table_privileges_dialog.dart index fc3a2f10..bb9a707d 100644 --- a/lib/features/postgresql/postgres_table_privileges_dialog.dart +++ b/lib/features/postgresql/postgres_table_privileges_dialog.dart @@ -164,7 +164,7 @@ class _PrivilegesDialogBodyState extends material.State<_PrivilegesDialogBody> { material.Widget _buildListArea(ColorScheme theme) { if (_loading) { return const material.Center( - child: material.CircularProgressIndicator(strokeWidth: 2), + child: QueryaSpinner(size: QueryaSpinnerSize.md), ); } if (_error != null) { diff --git a/lib/features/postgresql/postgresql_connection_form.dart b/lib/features/postgresql/postgresql_connection_form.dart index c105a8e1..4ddea4f6 100644 --- a/lib/features/postgresql/postgresql_connection_form.dart +++ b/lib/features/postgresql/postgresql_connection_form.dart @@ -761,13 +761,10 @@ class _PostgresConnectionFormContentState onPressed: formValid && !_isTesting ? _testConnection : null, leading: _isTesting - ? material.SizedBox( - width: 18, - height: 18, - child: material.CircularProgressIndicator( - strokeWidth: 2, - color: theme.primary, - ), + ? QueryaSpinner( + customDimension: 18, + strokeWidth: 2, + color: theme.primary, ) : material.Icon( material.Icons.link_rounded, diff --git a/lib/features/redis/redis_cli_workspace.dart b/lib/features/redis/redis_cli_workspace.dart index d9135fd6..edd0a770 100644 --- a/lib/features/redis/redis_cli_workspace.dart +++ b/lib/features/redis/redis_cli_workspace.dart @@ -8,8 +8,7 @@ import 'package:querya_desktop/core/database/redis_bulk.dart'; import 'package:querya_desktop/core/database/redis_connection.dart'; import 'package:querya_desktop/core/storage/local_db.dart'; import 'package:querya_desktop/features/workspace/destructive_query_dialog.dart'; -import 'package:querya_desktop/shared/widgets/app_toast.dart'; -import 'package:shadcn_flutter/shadcn_flutter.dart'; +import 'package:querya_desktop/shared/widgets/widgets.dart'; /// Known Redis commands for auto-completion. const List kRedisCommands = [ @@ -770,11 +769,7 @@ class _RedisCliWorkspaceState extends material.State { onPressed: _executing ? null : _submitCommand, size: ButtonSize.small, leading: _executing - ? const material.SizedBox( - width: 14, - height: 14, - child: material.CircularProgressIndicator(strokeWidth: 2), - ) + ? const QueryaSpinner(size: QueryaSpinnerSize.sm) : const material.Icon( material.Icons.play_arrow_rounded, size: 16, diff --git a/lib/features/redis/redis_connection_form.dart b/lib/features/redis/redis_connection_form.dart index 592c31fa..79bfb495 100644 --- a/lib/features/redis/redis_connection_form.dart +++ b/lib/features/redis/redis_connection_form.dart @@ -509,13 +509,10 @@ class _RedisConnectionFormContentState onPressed: formValid && !_isTesting ? _testConnection : null, leading: _isTesting - ? material.SizedBox( - width: 18, - height: 18, - child: material.CircularProgressIndicator( - strokeWidth: 2, - color: theme.primary, - ), + ? QueryaSpinner( + customDimension: 18, + strokeWidth: 2, + color: theme.primary, ) : material.Icon( material.Icons.link_rounded, diff --git a/lib/features/redis/redis_databases_view.dart b/lib/features/redis/redis_databases_view.dart index eb15fd96..de236139 100644 --- a/lib/features/redis/redis_databases_view.dart +++ b/lib/features/redis/redis_databases_view.dart @@ -97,18 +97,10 @@ class _RedisDatabasesViewState extends material.State { final shadcnCs = shadcn.Theme.of(context).colorScheme; if (_loading) { - return material.Center( - child: material.Column( - mainAxisSize: material.MainAxisSize.min, - children: [ - const material.SizedBox( - width: 32, - height: 32, - child: material.CircularProgressIndicator(strokeWidth: 2), - ), - const Gap(16), - const Text('Loading databases...').muted().small(), - ], + return const material.Center( + child: QueryaSpinner( + size: QueryaSpinnerSize.lg, + label: 'Loading databases...', ), ); } diff --git a/lib/features/redis/redis_explorer_view.dart b/lib/features/redis/redis_explorer_view.dart index d6e74c7f..c8e14c86 100644 --- a/lib/features/redis/redis_explorer_view.dart +++ b/lib/features/redis/redis_explorer_view.dart @@ -213,18 +213,10 @@ class _RedisExplorerViewState extends material.State { // Loading state if (_connecting) { - return material.Center( - child: material.Column( - mainAxisSize: material.MainAxisSize.min, - children: [ - const material.SizedBox( - width: 32, - height: 32, - child: material.CircularProgressIndicator(strokeWidth: 2), - ), - const Gap(16), - const Text('Connecting...').muted().small(), - ], + return const material.Center( + child: QueryaSpinner( + size: QueryaSpinnerSize.lg, + label: 'Connecting...', ), ); } diff --git a/lib/features/redis/redis_key_editor.dart b/lib/features/redis/redis_key_editor.dart index ecd2079a..b3f66383 100644 --- a/lib/features/redis/redis_key_editor.dart +++ b/lib/features/redis/redis_key_editor.dart @@ -141,25 +141,14 @@ class _RedisKeyEditorState extends material.State { Future _confirmDiscardStringEdits() async { if (!_isStringDirty) return true; if (!mounted) return false; - final confirmed = await showAppDialog( + final confirmed = await QueryaConfirmDialog.show( context: context, - builder: (ctx) => AlertDialog( - title: const Text('Unsaved changes'), - content: const Text( - 'You have unsaved edits to this string value. ' + title: 'Unsaved changes', + message: 'You have unsaved edits to this string value. ' 'Do you want to discard them?', - ), - actions: [ - OutlineButton( - onPressed: () => Navigator.of(ctx).pop(false), - child: const Text('Cancel'), - ), - DestructiveButton( - onPressed: () => Navigator.of(ctx).pop(true), - child: const Text('Discard'), - ), - ], - ), + confirmLabel: 'Discard', + cancelLabel: 'Cancel', + isDestructive: true, ); return confirmed == true; } @@ -649,18 +638,10 @@ class _RedisKeyEditorState extends material.State { final palette = context.semanticPalette; if (_loading) { - return material.Center( - child: material.Column( - mainAxisSize: material.MainAxisSize.min, - children: [ - const material.SizedBox( - width: 32, - height: 32, - child: material.CircularProgressIndicator(strokeWidth: 2), - ), - const Gap(16), - const Text('Loading key...').muted().small(), - ], + return const material.Center( + child: QueryaSpinner( + size: QueryaSpinnerSize.lg, + label: 'Loading key...', ), ); } @@ -1325,7 +1306,7 @@ class _RedisTtlDialogContentState @override material.Widget build(material.BuildContext context) { - return AlertDialog( + return QueryaModalDialog( title: const Text('Set TTL'), content: material.Column( mainAxisSize: material.MainAxisSize.min, @@ -1389,7 +1370,7 @@ class _RedisRenameDialogContentState @override material.Widget build(material.BuildContext context) { - return AlertDialog( + return QueryaModalDialog( title: const Text('Rename Key'), content: material.Column( mainAxisSize: material.MainAxisSize.min, @@ -1454,7 +1435,7 @@ class _RedisEditListDialogContentState @override material.Widget build(material.BuildContext context) { - return AlertDialog( + return QueryaModalDialog( title: Text('Edit Item [${widget.index}]'), content: material.Column( mainAxisSize: material.MainAxisSize.min, diff --git a/lib/features/redis/redis_keys_view.dart b/lib/features/redis/redis_keys_view.dart index 8565e82e..ed50afd1 100644 --- a/lib/features/redis/redis_keys_view.dart +++ b/lib/features/redis/redis_keys_view.dart @@ -295,18 +295,10 @@ class _RedisKeysViewState extends material.State { final cs = Theme.of(context).colorScheme; if (_loading && _keys.isEmpty) { - return material.Center( - child: material.Column( - mainAxisSize: material.MainAxisSize.min, - children: [ - const material.SizedBox( - width: 32, - height: 32, - child: material.CircularProgressIndicator(strokeWidth: 2), - ), - const Gap(16), - const Text('Scanning keys...').muted().small(), - ], + return const material.Center( + child: QueryaSpinner( + size: QueryaSpinnerSize.lg, + label: 'Scanning keys...', ), ); } @@ -467,14 +459,14 @@ class _RedisKeysViewState extends material.State { ), child: Row( children: [ - material.Icon(material.Icons.search_rounded, - size: 18, color: shadcnCs.mutedForeground), - const Gap(10), material.Expanded( - child: TextField( + child: QueryaSearchField( controller: _filterController, - placeholder: const Text('Pattern e.g. user:* or session:*'), + placeholder: 'Pattern e.g. user:* or session:*', onSubmitted: (_) => _applyFilter(), + onChanged: (val) { + if (val.isEmpty) _clearFilter(); + }, ), ), const Gap(8), diff --git a/lib/features/redis/redis_view.dart b/lib/features/redis/redis_view.dart index aacfa46d..81a086cf 100644 --- a/lib/features/redis/redis_view.dart +++ b/lib/features/redis/redis_view.dart @@ -218,18 +218,10 @@ class _RedisViewState extends material.State { final cs = Theme.of(context).colorScheme; if (_loading) { - return material.Center( - child: material.Column( - mainAxisSize: material.MainAxisSize.min, - children: [ - const material.SizedBox( - width: 32, - height: 32, - child: material.CircularProgressIndicator(strokeWidth: 2), - ), - const Gap(16), - const Text('Connecting...').muted().small(), - ], + return const material.Center( + child: QueryaSpinner( + size: QueryaSpinnerSize.lg, + label: 'Connecting...', ), ); } diff --git a/lib/features/settings/theme_color_picker_dialog.dart b/lib/features/settings/theme_color_picker_dialog.dart index 0ceca4ad..baf10f43 100644 --- a/lib/features/settings/theme_color_picker_dialog.dart +++ b/lib/features/settings/theme_color_picker_dialog.dart @@ -11,7 +11,7 @@ Future showThemeColorPickerDialog({ return showAppDialog( context: context, builder: (dialogContext) { - return material.AlertDialog( + return QueryaModalDialog( title: const material.Text('Pick color'), content: material.SizedBox( width: 320, @@ -22,11 +22,11 @@ Future showThemeColorPickerDialog({ ), ), actions: [ - material.TextButton( + OutlineButton( onPressed: () => material.Navigator.pop(dialogContext), child: const material.Text('Cancel'), ), - material.TextButton( + PrimaryButton( onPressed: () => material.Navigator.pop(dialogContext, picked.toColor()), child: const material.Text('Apply'), diff --git a/lib/features/settings/theme_editor_dialog.dart b/lib/features/settings/theme_editor_dialog.dart index 204f60ef..615f809f 100644 --- a/lib/features/settings/theme_editor_dialog.dart +++ b/lib/features/settings/theme_editor_dialog.dart @@ -147,51 +147,18 @@ class _ThemeEditorDialogState extends material.State { final theme = Theme.of(context); final cs = theme.colorScheme; - return material.AlertDialog( - backgroundColor: cs.popover, - shape: material.RoundedRectangleBorder( - borderRadius: material.BorderRadius.circular(12), - side: material.BorderSide(color: cs.border.withValues(alpha: 0.35)), - ), - titlePadding: const material.EdgeInsets.fromLTRB(20, 16, 16, 12), - contentPadding: const material.EdgeInsets.symmetric(horizontal: 20), - actionsPadding: const material.EdgeInsets.fromLTRB(20, 12, 20, 16), - title: material.Row( - children: [ - material.Icon( - material.Icons.palette_outlined, - size: 20, - color: cs.primary, - ), - const material.SizedBox(width: 10), - material.Expanded( - child: material.Column( - crossAxisAlignment: material.CrossAxisAlignment.start, - children: [ - const Text('Theme Studio') - .semiBold() - .medium() - .foreground(), - const material.SizedBox(height: 2), - const Text( - 'Customize color tokens with real-time UI preview', - ).muted().xSmall(), - ], - ), - ), - IconButton.ghost( - icon: const material.Icon(material.Icons.close, size: 16), - onPressed: () => material.Navigator.of(context).pop(), - density: ButtonDensity.compact, - ), - ], - ), + return QueryaModalDialog( + constraints: const material.BoxConstraints(maxWidth: 580), + title: const Text('Theme Studio'), + description: const Text('Customize color tokens with real-time UI preview'), + icon: const material.Icon(material.Icons.palette_outlined), + showCloseButton: true, content: material.SizedBox( width: 540, height: 480, child: _loading ? const material.Center( - child: material.CircularProgressIndicator(), + child: QueryaSpinner(size: QueryaSpinnerSize.lg), ) : material.SingleChildScrollView( child: material.Column( diff --git a/lib/features/settings/theme_preview_card.dart b/lib/features/settings/theme_preview_card.dart index b1668c75..b61d0136 100644 --- a/lib/features/settings/theme_preview_card.dart +++ b/lib/features/settings/theme_preview_card.dart @@ -1,8 +1,7 @@ import 'package:flutter/material.dart' as material; import 'package:querya_desktop/core/layout/ui_scale.dart'; import 'package:querya_desktop/core/theme/querya_theme.dart'; -import 'package:querya_desktop/shared/widgets/querya_dropdown_tokens.dart'; -import 'package:shadcn_flutter/shadcn_flutter.dart'; +import 'package:querya_desktop/shared/widgets/widgets.dart'; /// Result of an async theme preview load for [ThemePreviewCard]. sealed class ThemePreviewResult { @@ -55,13 +54,10 @@ class ThemePreviewCard extends material.StatelessWidget { borderColor: appScheme.border, child: material.Row( children: [ - material.SizedBox( - width: context.scaled(14), - height: context.scaled(14), - child: material.CircularProgressIndicator( - strokeWidth: 2, - color: appScheme.mutedForeground, - ), + QueryaSpinner( + customDimension: context.scaled(14), + strokeWidth: 2, + color: appScheme.mutedForeground, ), material.SizedBox(width: context.scaled(8)), material.Text( diff --git a/lib/features/settings/theme_remote_install_dialog.dart b/lib/features/settings/theme_remote_install_dialog.dart index 2790674e..9e3d76ee 100644 --- a/lib/features/settings/theme_remote_install_dialog.dart +++ b/lib/features/settings/theme_remote_install_dialog.dart @@ -74,7 +74,7 @@ class _ThemeRemoteInstallDialogState final cs = Theme.of(context).colorScheme; final host = Uri.tryParse(_urlController.text.trim())?.host; - return material.AlertDialog( + return QueryaModalDialog( title: const material.Text('Install theme from URL'), content: material.SizedBox( width: 420, diff --git a/lib/features/sqlite/sqlite_overview_tab.dart b/lib/features/sqlite/sqlite_overview_tab.dart index e3ae3f71..aa8eb03a 100644 --- a/lib/features/sqlite/sqlite_overview_tab.dart +++ b/lib/features/sqlite/sqlite_overview_tab.dart @@ -136,18 +136,10 @@ class _SqliteOverviewTabState extends material.State { final cs = Theme.of(context).colorScheme; if (_loading && _overview == null) { - return material.Center( - child: material.Row( - mainAxisSize: material.MainAxisSize.min, - children: [ - const material.SizedBox( - width: 16, - height: 16, - child: material.CircularProgressIndicator(strokeWidth: 2), - ), - const Gap(12), - const Text('Loading database overview...').muted().small(), - ], + return const material.Center( + child: QueryaSpinner( + size: QueryaSpinnerSize.sm, + label: 'Loading database overview...', ), ); } diff --git a/lib/features/sqlite/sqlite_sql_tx_guard.dart b/lib/features/sqlite/sqlite_sql_tx_guard.dart index f55ed589..463ad96c 100644 --- a/lib/features/sqlite/sqlite_sql_tx_guard.dart +++ b/lib/features/sqlite/sqlite_sql_tx_guard.dart @@ -13,25 +13,14 @@ bool sqliteObjectOpensTableBrowser(SqliteObjectKind kind) { Future confirmLeaveOpenSqliteTransaction( material.BuildContext context, ) async { - final ok = await showAppDialog( + final ok = await QueryaConfirmDialog.show( context: context, - builder: (ctx) => material.AlertDialog( - title: const material.Text('Open transaction'), - content: const material.Text( - 'The SQL tab has an open transaction. Leave anyway? ' + title: 'Open transaction', + message: 'The SQL tab has an open transaction. Leave anyway? ' 'Uncommitted work may be lost if the session ends.', - ), - actions: [ - material.TextButton( - onPressed: () => material.Navigator.of(ctx).pop(false), - child: const material.Text('Stay'), - ), - material.TextButton( - onPressed: () => material.Navigator.of(ctx).pop(true), - child: const material.Text('Leave'), - ), - ], - ), + confirmLabel: 'Leave', + cancelLabel: 'Stay', + isDestructive: true, ); return ok == true; } diff --git a/lib/features/sqlite/sqlite_sql_workspace.dart b/lib/features/sqlite/sqlite_sql_workspace.dart index 62352b9d..aa51adcf 100644 --- a/lib/features/sqlite/sqlite_sql_workspace.dart +++ b/lib/features/sqlite/sqlite_sql_workspace.dart @@ -944,13 +944,9 @@ class _SqliteSqlToolbar extends material.StatelessWidget { OutlineButton( onPressed: onExecute, leading: running - ? material.SizedBox( - width: 16, - height: 16, - child: material.CircularProgressIndicator( - strokeWidth: 2, - color: accent, - ), + ? QueryaSpinner( + size: QueryaSpinnerSize.sm, + color: accent, ) : material.Icon( material.Icons.play_arrow_rounded, diff --git a/lib/features/sqlite/sqlite_table_view.dart b/lib/features/sqlite/sqlite_table_view.dart index 38f6dd8c..33a2e57c 100644 --- a/lib/features/sqlite/sqlite_table_view.dart +++ b/lib/features/sqlite/sqlite_table_view.dart @@ -537,7 +537,7 @@ class _SqliteTableViewState extends material.State { context: context, barrierDismissible: false, builder: (_) => const material.Center( - child: material.CircularProgressIndicator(), + child: QueryaSpinner(size: QueryaSpinnerSize.lg), ), )); try { diff --git a/lib/features/updater/update_dialog.dart b/lib/features/updater/update_dialog.dart index be638f41..9492b0e1 100644 --- a/lib/features/updater/update_dialog.dart +++ b/lib/features/updater/update_dialog.dart @@ -447,7 +447,7 @@ class UpdateDialogContentState extends material.State { UpdateDialogPhase.checking => const material.Center( child: material.Padding( padding: material.EdgeInsets.all(32), - child: material.CircularProgressIndicator(), + child: QueryaSpinner(size: QueryaSpinnerSize.lg), ), ), UpdateDialogPhase.upToDate => material.Padding( diff --git a/lib/features/workspace/data_grid_staging_toolbar.dart b/lib/features/workspace/data_grid_staging_toolbar.dart index ea1e6513..31eb9b06 100644 --- a/lib/features/workspace/data_grid_staging_toolbar.dart +++ b/lib/features/workspace/data_grid_staging_toolbar.dart @@ -148,13 +148,9 @@ class DataGridStagingToolbar extends StatelessWidget { size: ButtonSize.small, onPressed: isDirty && !isSaving ? onApplyChanges : null, leading: isSaving - ? material.SizedBox( - width: 12, - height: 12, - child: material.CircularProgressIndicator( - strokeWidth: 2, - color: cs.primaryForeground, - ), + ? QueryaSpinner( + size: QueryaSpinnerSize.sm, + color: cs.primaryForeground, ) : const material.Icon( material.Icons.save_rounded, diff --git a/lib/features/workspace/grid_cell_popover_inspector.dart b/lib/features/workspace/grid_cell_popover_inspector.dart index 9d3b6199..e2734279 100644 --- a/lib/features/workspace/grid_cell_popover_inspector.dart +++ b/lib/features/workspace/grid_cell_popover_inspector.dart @@ -487,13 +487,9 @@ class _GridCellInspectorDialogState density: ButtonDensity.compact, onPressed: _savingToDb ? null : _saveToDatabase, leading: _savingToDb - ? material.SizedBox( - width: 12, - height: 12, - child: material.CircularProgressIndicator( - strokeWidth: 2, - color: cs.primaryForeground, - ), + ? QueryaSpinner( + size: QueryaSpinnerSize.sm, + color: cs.primaryForeground, ) : const material.Icon( material.Icons.save_rounded, diff --git a/lib/features/workspace/results_tab.dart b/lib/features/workspace/results_tab.dart index 31c1b126..6dd93e50 100644 --- a/lib/features/workspace/results_tab.dart +++ b/lib/features/workspace/results_tab.dart @@ -255,19 +255,11 @@ class _ResultsTabState extends material.State { @override material.Widget build(material.BuildContext context) { if (widget.isLoading) { - return material.Center( - key: const material.ValueKey('results_mode_loading'), - child: material.Row( - mainAxisSize: material.MainAxisSize.min, - children: [ - const material.SizedBox( - width: 16, - height: 16, - child: material.CircularProgressIndicator(strokeWidth: 2), - ), - const Gap(12), - const Text('Executing query...').muted().small(), - ], + return const material.Center( + key: material.ValueKey('results_mode_loading'), + child: QueryaSpinner( + size: QueryaSpinnerSize.sm, + label: 'Executing query...', ), ); } diff --git a/lib/features/workspace/sql_query_history_dialog.dart b/lib/features/workspace/sql_query_history_dialog.dart index 1315518e..867e6136 100644 --- a/lib/features/workspace/sql_query_history_dialog.dart +++ b/lib/features/workspace/sql_query_history_dialog.dart @@ -281,7 +281,7 @@ class _SqlQueryHistoryDialogContentState return const material.Center( child: material.Padding( padding: material.EdgeInsets.all(24), - child: material.CircularProgressIndicator(), + child: QueryaSpinner(size: QueryaSpinnerSize.lg), ), ); } diff --git a/lib/shared/widgets/connection_tree_loading_row.dart b/lib/shared/widgets/connection_tree_loading_row.dart index 2959e461..75b4404c 100644 --- a/lib/shared/widgets/connection_tree_loading_row.dart +++ b/lib/shared/widgets/connection_tree_loading_row.dart @@ -2,6 +2,8 @@ import 'package:flutter/material.dart' as material; import 'package:querya_desktop/core/ui/querya_tree_tokens.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; +import 'querya_spinner.dart'; + /// Compact inline spinner row used while connection trees lazy-load children. class ConnectionTreeLoadingRow extends material.StatelessWidget { const ConnectionTreeLoadingRow.connection({ @@ -32,10 +34,9 @@ class ConnectionTreeLoadingRow extends material.StatelessWidget { padding: padding, child: material.Row( children: [ - material.SizedBox( - width: spinnerSize, - height: spinnerSize, - child: material.CircularProgressIndicator(strokeWidth: strokeWidth), + QueryaSpinner( + customDimension: spinnerSize, + strokeWidth: strokeWidth, ), Gap(gap), Text(label).muted().xSmall(), diff --git a/lib/shared/widgets/querya_modal_dialog.dart b/lib/shared/widgets/querya_modal_dialog.dart index 229f3f53..d4561a5f 100644 --- a/lib/shared/widgets/querya_modal_dialog.dart +++ b/lib/shared/widgets/querya_modal_dialog.dart @@ -149,14 +149,15 @@ class QueryaModalDialog extends material.StatelessWidget { // Actions if (actions != null && actions!.isNotEmpty) ...[ const Gap(20), - material.Row( - mainAxisAlignment: material.MainAxisAlignment.end, - children: [ - for (var i = 0; i < actions!.length; i++) ...[ - if (i > 0) const Gap(8), - actions![i], - ], - ], + material.Align( + alignment: material.Alignment.centerRight, + child: material.Wrap( + alignment: material.WrapAlignment.end, + crossAxisAlignment: material.WrapCrossAlignment.center, + spacing: 8, + runSpacing: 8, + children: actions!, + ), ), ], ], diff --git a/test/features/redis/redis_key_editor_test.dart b/test/features/redis/redis_key_editor_test.dart index 5367f070..9f6f0e17 100644 --- a/test/features/redis/redis_key_editor_test.dart +++ b/test/features/redis/redis_key_editor_test.dart @@ -5,6 +5,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:querya_desktop/core/database/redis_bulk.dart'; import 'package:querya_desktop/core/database/redis_connection.dart'; import 'package:querya_desktop/features/redis/redis_key_editor.dart'; +import 'package:querya_desktop/shared/widgets/widgets.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart' as shadcn; import '../../support/querya_theme_test_shell.dart'; @@ -416,7 +417,7 @@ void main() { expect(find.text('Edit Item [0]'), findsOneWidget); final field = find.descendant( - of: find.byType(shadcn.AlertDialog), + of: find.byType(QueryaModalDialog), matching: find.byType(shadcn.TextField), ); await tester.enterText(field, 'task1_updated'); @@ -500,7 +501,7 @@ void main() { fake.listItems[0] = 'task1_concurrent_edit'; final field = find.descendant( - of: find.byType(shadcn.AlertDialog), + of: find.byType(QueryaModalDialog), matching: find.byType(shadcn.TextField), ); await tester.enterText(field, 'task1_my_edit'); diff --git a/test/guards/no_raw_material_dialogs_test.dart b/test/guards/no_raw_material_dialogs_test.dart new file mode 100644 index 00000000..e3326b7c --- /dev/null +++ b/test/guards/no_raw_material_dialogs_test.dart @@ -0,0 +1,27 @@ +import 'dart:io'; + +import 'package:flutter_test/flutter_test.dart'; + +/// #1039: Raw Material AlertDialog must not be used directly in `lib/`. +/// All dialogs must use QueryaModalDialog or QueryaConfirmDialog. +void main() { + test('no raw Material AlertDialog in lib/', () { + final banned = RegExp(r'\b(material\.)?AlertDialog\b'); + final offenders = []; + + for (final f in Directory('lib').listSync(recursive: true)) { + if (f is! File || !f.path.endsWith('.dart')) continue; + final content = f.readAsStringSync(); + if (banned.hasMatch(content)) { + offenders.add(f.path); + } + } + + expect( + offenders, + isEmpty, + reason: 'Found raw AlertDialog in the following files: $offenders. ' + 'Use QueryaModalDialog or QueryaConfirmDialog instead.', + ); + }); +} diff --git a/test/guards/no_raw_progress_indicators_test.dart b/test/guards/no_raw_progress_indicators_test.dart new file mode 100644 index 00000000..131d21ce --- /dev/null +++ b/test/guards/no_raw_progress_indicators_test.dart @@ -0,0 +1,29 @@ +import 'dart:io'; + +import 'package:flutter_test/flutter_test.dart'; + +/// #1039: Raw Material CircularProgressIndicator must not be used directly in `lib/` +/// outside of `QueryaSpinner` (`lib/shared/widgets/querya_spinner.dart`). +void main() { + test('no raw CircularProgressIndicator in lib/ outside of QueryaSpinner', () { + final banned = RegExp(r'\b(material\.)?CircularProgressIndicator\b'); + final offenders = []; + + for (final f in Directory('lib').listSync(recursive: true)) { + if (f is! File || !f.path.endsWith('.dart')) continue; + if (f.path.endsWith('lib/shared/widgets/querya_spinner.dart')) continue; + + final content = f.readAsStringSync(); + if (banned.hasMatch(content)) { + offenders.add(f.path); + } + } + + expect( + offenders, + isEmpty, + reason: 'Found raw CircularProgressIndicator in the following files: $offenders. ' + 'Use QueryaSpinner from lib/shared/widgets/widgets.dart instead.', + ); + }); +}