From 037c26f0e458dac82b321dbcb45d1eea934dbdc5 Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Tue, 16 Jun 2026 17:04:02 +0300 Subject: [PATCH] Refactor: resolve UI architecture tech debt and accessibility bugs (fixes #196) --- lib/app/app.dart | 7 +- lib/core/database/mongodb_connection.dart | 3 +- lib/core/database/mongodb_service.dart | 6 +- lib/core/database/postgres_connection.dart | 3 +- .../database/postgres_connection_pool.dart | 3 +- lib/core/database/postgres_sql.dart | 1 - lib/core/database/redis_connection.dart | 6 +- lib/core/editor/querya_code_editor.dart | 4 +- .../editor/querya_highlight_controller.dart | 5 +- lib/core/editor/syntax_highlight_isolate.dart | 5 +- lib/core/layout/window_layout.dart | 6 +- lib/core/market/marketplace_client.dart | 14 +- lib/core/motion/querya_animated_expand.dart | 5 +- lib/core/storage/app_settings.dart | 23 +- .../storage/connection_secrets_store.dart | 9 +- lib/core/storage/folders_storage.dart | 5 +- lib/core/storage/local_db.dart | 15 +- lib/core/theme/parser/color_parser.dart | 7 +- .../parser/querya_theme_color_scheme.dart | 3 +- .../parser/querya_theme_from_manifest.dart | 3 +- .../parser/querya_theme_from_vscode.dart | 3 +- .../theme/parser/querya_theme_manifest.dart | 9 +- .../theme/parser/token_style_resolver.dart | 3 +- .../theme/parser/vscode_theme_manifest.dart | 3 +- lib/core/theme/querya_theme_scope.dart | 10 +- lib/core/theme/theme_controller.dart | 16 +- lib/core/theme/theme_editor_loader.dart | 3 +- lib/core/theme/theme_folder_watcher.dart | 3 +- lib/core/theme/theme_registry_service.dart | 6 +- .../theme/theme_remote_install_service.dart | 6 +- .../connections/connection_creation_flow.dart | 3 +- .../connections/connections_panel.dart | 242 ++++++---- .../connections/connections_panel_mongo.dart | 136 +++--- .../connections/connections_panel_mysql.dart | 325 ++++++------- .../connections_panel_pg_tree.dart | 426 +++++++++--------- ...connections_panel_postgres_connection.dart | 84 ++-- .../connections/connections_panel_redis.dart | 80 ++-- .../connections_panel_sidebar.dart | 25 +- .../connections/driver_manager_dialog.dart | 18 +- .../connections/new_connection_dialog.dart | 302 +++++++------ .../connections/new_folder_dialog.dart | 18 +- lib/features/main_screen/main_screen.dart | 3 +- .../main_screen_workspace_state.dart | 31 +- .../main_screen/query_editor_tab.dart | 3 +- .../main_screen/querya_window_title_bar.dart | 26 +- .../main_screen/result_grid_view.dart | 3 +- .../main_screen/sql_query_history_dialog.dart | 6 +- .../main_screen/workspace_empty_hero.dart | 3 +- lib/features/main_screen/workspace_panel.dart | 206 +++++---- .../mongodb/mongo_collections_view.dart | 35 +- .../mongodb/mongo_database_dialog.dart | 16 +- .../mongodb/mongo_databases_view.dart | 26 +- .../mongodb/mongo_document_editor.dart | 11 +- .../mongodb/mongo_documents_view.dart | 14 +- lib/features/mongodb/mongo_explorer_view.dart | 7 +- lib/features/mongodb/mongo_stats_view.dart | 116 +++-- .../mongodb/mongodb_connection_form.dart | 73 ++- lib/features/mysql/mysql_connection_form.dart | 28 +- .../mysql/mysql_sql_editor_dialog.dart | 9 +- lib/features/mysql/mysql_sql_workspace.dart | 14 +- lib/features/mysql/mysql_stats_view.dart | 45 +- lib/features/mysql/mysql_table_view.dart | 22 +- .../postgresql/postgres_browser_views.dart | 9 +- .../postgresql/postgres_object_kind.dart | 9 + .../postgresql/postgres_object_workspace.dart | 7 +- .../postgresql/postgres_routine_view.dart | 4 +- .../postgresql/postgres_sequence_view.dart | 4 +- .../postgres_sql_editor_dialog.dart | 12 +- .../postgresql/postgres_sql_workspace.dart | 25 +- .../postgresql/postgres_stats_view.dart | 61 +-- .../postgresql/postgres_table_toolbar.dart | 3 +- .../postgresql/postgres_workspace_home.dart | 15 +- .../postgresql_connection_form.dart | 23 +- lib/features/redis/redis_connection_form.dart | 49 +- lib/features/redis/redis_databases_view.dart | 18 +- lib/features/redis/redis_explorer_view.dart | 14 +- lib/features/redis/redis_key_editor.dart | 51 +-- lib/features/redis/redis_keys_view.dart | 14 +- lib/features/redis/redis_view.dart | 137 ++++-- .../preferences_appearance_section.dart | 7 +- .../settings/preferences_controls.dart | 90 ++-- lib/features/settings/preferences_dialog.dart | 3 +- .../settings/theme_color_picker_dialog.dart | 3 +- .../settings/theme_editor_section.dart | 3 +- .../settings/theme_picker_button.dart | 20 +- lib/features/settings/theme_preview_card.dart | 3 +- lib/shared/widgets/app_dialog.dart | 3 +- lib/shared/widgets/querya_dropdown.dart | 61 ++- .../widgets/querya_dropdown_tokens.dart | 6 +- lib/shared/widgets/widgets.dart | 6 +- test/core/csv/result_grid_csv_test.dart | 6 +- .../postgres_connection_pool_test.dart | 6 +- .../database/postgres_connection_test.dart | 3 +- .../core/database/postgres_metadata_test.dart | 3 +- test/core/database/postgres_sql_test.dart | 27 +- test/core/database/redis_info_test.dart | 22 +- .../highlighter_theme_from_querya_test.dart | 6 +- test/core/editor/querya_code_editor_test.dart | 17 +- test/core/json/result_grid_json_test.dart | 6 +- .../core/layout/window_layout_scale_test.dart | 6 +- test/core/market/extension_manifest_test.dart | 3 +- .../motion/querya_animated_expand_test.dart | 3 +- .../motion/querya_cross_fade_stack_test.dart | 9 +- .../motion/querya_motion_controller_test.dart | 3 +- test/core/platform/open_directory_test.dart | 4 +- test/core/storage/app_settings_test.dart | 67 ++- test/core/storage/local_db_secrets_test.dart | 6 +- test/core/storage/sql_query_history_test.dart | 3 +- ...uerya_editor_theme_from_manifest_test.dart | 4 +- .../querya_theme_color_scheme_test.dart | 4 +- .../querya_theme_from_manifest_test.dart | 16 +- .../parser/querya_theme_manifest_test.dart | 32 +- ...ya_workbench_theme_from_manifest_test.dart | 4 +- .../parser/token_colors_dracula_sql_test.dart | 6 +- .../theme/parser/vscode_color_map_test.dart | 3 +- test/core/theme/querya_app_theme_test.dart | 3 +- test/core/theme/querya_light_theme_test.dart | 4 +- test/core/theme/querya_theme_scope_test.dart | 2 +- test/core/theme/theme_controller_test.dart | 61 ++- test/core/theme/theme_editor_draft_test.dart | 16 +- test/core/theme/theme_editor_loader_test.dart | 6 +- .../theme_registry_builtin_assets_test.dart | 4 +- .../theme_registry_legacy_import_test.dart | 16 +- .../theme/theme_registry_service_test.dart | 24 +- .../theme_remote_install_policy_test.dart | 4 +- .../theme_remote_install_service_test.dart | 16 +- .../util/deep_collection_equals_test.dart | 18 +- .../connections_panel_layout_test.dart | 9 +- .../lazy_connection_tree_list_test.dart | 6 +- .../main_screen/query_editor_tab_test.dart | 3 +- .../main_screen/results_tab_test.dart | 3 +- .../main_screen/sql_editor_chrome_test.dart | 9 +- .../workspace_empty_hero_test.dart | 18 +- .../workspace_homes_and_preferences_test.dart | 16 +- .../workspace_panel_layout_test.dart | 3 +- .../mongodb/mongo_document_editor_test.dart | 13 +- .../mysql/mysql_table_utils_test.dart | 3 +- .../postgresql/postgres_object_kind_test.dart | 9 +- .../postgres_table_toolbar_layout_test.dart | 3 +- .../postgresql/postgres_table_utils_test.dart | 3 +- test/features/redis/redis_keys_view_test.dart | 3 +- .../settings/interface_scale_slider_test.dart | 3 +- .../preferences_appearance_section_test.dart | 6 +- .../settings/theme_import_flow_test.dart | 3 +- .../settings/theme_picker_button_test.dart | 9 +- test/memory_secrets_backend.dart | 3 +- test/shared/querya_dropdown_test.dart | 9 +- .../lib/src/generated/refresh_rate_api.g.dart | 101 +++-- .../lib/src/models/display_info.dart | 19 +- .../refresh_rate/lib/src/models/enums.dart | 30 +- .../lib/src/models/session_report.dart | 1 + .../refresh_rate/lib/src/refresh_rate.dart | 12 +- .../lib/src/refresh_rate_api_adapter.dart | 12 +- .../lib/src/verification/fps_tracker.dart | 25 +- .../src/verification/overlay_controller.dart | 4 + .../lib/src/verification/overlay_widgets.dart | 9 +- .../lib/src/verification/session_scorer.dart | 9 +- .../lib/src/web/raf_hz_detector.dart | 13 +- .../pigeons/refresh_rate_api.dart | 3 +- .../refresh_rate/test/fps_tracker_test.dart | 28 +- .../refresh_rate/test/models_test.dart | 7 +- .../test/refresh_rate_api_test.dart | 61 ++- .../lib/src/components/display/divider.dart | 3 +- .../lib/src/components/layout/icon.dart | 9 + third_party/shadcn_flutter/lib/src/util.dart | 1 + 165 files changed, 2405 insertions(+), 1730 deletions(-) diff --git a/lib/app/app.dart b/lib/app/app.dart index 214ab030..83e0201d 100644 --- a/lib/app/app.dart +++ b/lib/app/app.dart @@ -48,7 +48,12 @@ class QueryaApp extends StatelessWidget { scale: scale, child: MediaQuery( data: (mq ?? const MediaQueryData()).copyWith( - textScaler: TextScaler.linear(scale), + textScaler: TextScaler.linear( + (mq ?? const MediaQueryData()) + .textScaler + .scale(1.0) * + scale, + ), ), child: QueryaThemeScope( data: queryaTheme, diff --git a/lib/core/database/mongodb_connection.dart b/lib/core/database/mongodb_connection.dart index 916e8de4..153f368f 100644 --- a/lib/core/database/mongodb_connection.dart +++ b/lib/core/database/mongodb_connection.dart @@ -94,8 +94,7 @@ class MongoConnection { // fall back to "admin" (Mongo's default authSource). final existingAuthSource = uri.queryParameters['authSource']; final hasCredentials = - uri.userInfo.isNotEmpty || - (username != null && username!.isNotEmpty); + uri.userInfo.isNotEmpty || (username != null && username!.isNotEmpty); Map? newQueryParams; if (existingAuthSource == null && hasCredentials) { diff --git a/lib/core/database/mongodb_service.dart b/lib/core/database/mongodb_service.dart index dc2566f5..631af8d1 100644 --- a/lib/core/database/mongodb_service.dart +++ b/lib/core/database/mongodb_service.dart @@ -126,14 +126,16 @@ class MongoService { return _withDb(connection, database, (db) async { final coll = db.collection(collection); - + final selector = where; if (filter != null && filter.isNotEmpty) { selector.raw(filter); } if (sort != null && sort.isNotEmpty) { for (final entry in sort.entries) { - final isDesc = entry.value == -1 || entry.value == 'desc' || entry.value == 'DESC'; + final isDesc = entry.value == -1 || + entry.value == 'desc' || + entry.value == 'DESC'; selector.sortBy(entry.key, descending: isDesc); } } diff --git a/lib/core/database/postgres_connection.dart b/lib/core/database/postgres_connection.dart index 1b968dbb..752cc0f5 100644 --- a/lib/core/database/postgres_connection.dart +++ b/lib/core/database/postgres_connection.dart @@ -700,8 +700,7 @@ ORDER BY grantee, privilege_type .toList(); } - static String _quoteIdent(String id) => - '"${id.replaceAll('"', '""')}"'; + static String _quoteIdent(String id) => '"${id.replaceAll('"', '""')}"'; static bool _parsePgBool(Object? v) { if (v is bool) return v; diff --git a/lib/core/database/postgres_connection_pool.dart b/lib/core/database/postgres_connection_pool.dart index 03d19490..73c71433 100644 --- a/lib/core/database/postgres_connection_pool.dart +++ b/lib/core/database/postgres_connection_pool.dart @@ -79,7 +79,8 @@ class PostgresConnectionPool { entry.refs++; if (!entry.connection.isConnected) { await entry.connection.connect(); - await entry.connection.setSessionReadOnly(mode == PgSessionMode.readOnly); + await entry.connection + .setSessionReadOnly(mode == PgSessionMode.readOnly); } return PgLease._(this, k, entry.connection); } diff --git a/lib/core/database/postgres_sql.dart b/lib/core/database/postgres_sql.dart index 5cc2dac9..8f9de124 100644 --- a/lib/core/database/postgres_sql.dart +++ b/lib/core/database/postgres_sql.dart @@ -73,4 +73,3 @@ String injectSqlLimit(String sql, int limit) { return '$body\nLIMIT $limit$suffix'; } - diff --git a/lib/core/database/redis_connection.dart b/lib/core/database/redis_connection.dart index 94760aa9..9a65026b 100644 --- a/lib/core/database/redis_connection.dart +++ b/lib/core/database/redis_connection.dart @@ -125,10 +125,8 @@ class RedisConnection { final result = await sendCommand(args); if (result is List && result.length == 2) { final nextCursor = int.tryParse(result[0].toString()) ?? 0; - final keys = (result[1] as List?) - ?.map((e) => e.toString()) - .toList() ?? - []; + final keys = + (result[1] as List?)?.map((e) => e.toString()).toList() ?? []; return (nextCursor, keys); } return (0, []); diff --git a/lib/core/editor/querya_code_editor.dart b/lib/core/editor/querya_code_editor.dart index 377de8c1..7e9cb9f8 100644 --- a/lib/core/editor/querya_code_editor.dart +++ b/lib/core/editor/querya_code_editor.dart @@ -309,8 +309,8 @@ class _QueryaCodeEditorState extends State { decoration: material.InputDecoration( border: material.InputBorder.none, hintText: widget.hintText, - contentPadding: widget.contentPadding ?? - const material.EdgeInsets.all(12), + contentPadding: + widget.contentPadding ?? const material.EdgeInsets.all(12), ), onChanged: widget.onChanged, ); diff --git a/lib/core/editor/querya_highlight_controller.dart b/lib/core/editor/querya_highlight_controller.dart index eb46c8e0..12e04c78 100644 --- a/lib/core/editor/querya_highlight_controller.dart +++ b/lib/core/editor/querya_highlight_controller.dart @@ -43,9 +43,8 @@ class QueryaHighlightController extends TextEditingController { required bool withComposing, }) { final brightness = Theme.of(context).brightness; - final themeConfig = brightness == Brightness.light - ? lightThemeConfig - : darkThemeConfig; + final themeConfig = + brightness == Brightness.light ? lightThemeConfig : darkThemeConfig; if (_cachedText == text && _cachedBrightness == brightness && diff --git a/lib/core/editor/syntax_highlight_isolate.dart b/lib/core/editor/syntax_highlight_isolate.dart index a0fece91..ca69d171 100644 --- a/lib/core/editor/syntax_highlight_isolate.dart +++ b/lib/core/editor/syntax_highlight_isolate.dart @@ -113,9 +113,8 @@ TextStyle? _styleFromSegment(HighlightSegment s, TextStyle? base) { return (base ?? const TextStyle()).copyWith( color: s.colorArgb != null ? Color(s.colorArgb!) : null, fontWeight: _fontWeightFromValue(s.fontWeightValue), - fontStyle: s.fontStyleIndex != null - ? FontStyle.values[s.fontStyleIndex!] - : null, + fontStyle: + s.fontStyleIndex != null ? FontStyle.values[s.fontStyleIndex!] : null, ); } diff --git a/lib/core/layout/window_layout.dart b/lib/core/layout/window_layout.dart index 7491d6d8..c464d527 100644 --- a/lib/core/layout/window_layout.dart +++ b/lib/core/layout/window_layout.dart @@ -30,7 +30,8 @@ abstract class WindowLayout { return BoxConstraints( maxWidth: maxWidth != null ? context.scaled(maxWidth) : double.infinity, minWidth: minWidth != null ? context.scaled(minWidth) : 0, - maxHeight: maxHeight != null ? context.scaled(maxHeight) : double.infinity, + maxHeight: + maxHeight != null ? context.scaled(maxHeight) : double.infinity, minHeight: minHeight != null ? context.scaled(minHeight) : 0, ); } @@ -45,7 +46,8 @@ abstract class WindowLayout { double viewportFactor = 1.0, }) { final available = math.max(0.0, screenExtent - insetTotal); - final base = math.min(baseMax, math.max(baseMin, available * viewportFactor)); + final base = + math.min(baseMax, math.max(baseMin, available * viewportFactor)); return math.min(context.scaled(base), available); } diff --git a/lib/core/market/marketplace_client.dart b/lib/core/market/marketplace_client.dart index 247ce9d4..1c450cb0 100644 --- a/lib/core/market/marketplace_client.dart +++ b/lib/core/market/marketplace_client.dart @@ -23,13 +23,11 @@ class MockMarketplaceClient implements MarketplaceClient { String? type, }) async { final normalized = query.trim().toLowerCase(); - return _items - .where((item) { - if (type != null && item.type != type) return false; - if (normalized.isEmpty) return true; - return item.name.toLowerCase().contains(normalized) || - item.id.toLowerCase().contains(normalized); - }) - .toList(growable: false); + return _items.where((item) { + if (type != null && item.type != type) return false; + if (normalized.isEmpty) return true; + return item.name.toLowerCase().contains(normalized) || + item.id.toLowerCase().contains(normalized); + }).toList(growable: false); } } diff --git a/lib/core/motion/querya_animated_expand.dart b/lib/core/motion/querya_animated_expand.dart index 5f837e0b..96dd97c5 100644 --- a/lib/core/motion/querya_animated_expand.dart +++ b/lib/core/motion/querya_animated_expand.dart @@ -23,9 +23,8 @@ class QueryaAnimatedExpand extends StatelessWidget { curve: context.motionCurve(QueryaMotion.enter), alignment: alignment, clipBehavior: Clip.hardEdge, - child: expanded - ? child - : const SizedBox(width: double.infinity, height: 0), + child: + expanded ? child : const SizedBox(width: double.infinity, height: 0), ); } } diff --git a/lib/core/storage/app_settings.dart b/lib/core/storage/app_settings.dart index 05e783a1..0919cf08 100644 --- a/lib/core/storage/app_settings.dart +++ b/lib/core/storage/app_settings.dart @@ -333,7 +333,8 @@ class AppSettings { } Future getSelectedThemeId() async { - final v = await LocalDb.instance.getAppSetting(AppSettingsKeys.themeSelectedId); + final v = + await LocalDb.instance.getAppSetting(AppSettingsKeys.themeSelectedId); if (v == null || v.isEmpty) return null; return v; } @@ -348,8 +349,8 @@ class AppSettings { } Future getSelectedThemeSource() async { - final v = - await LocalDb.instance.getAppSetting(AppSettingsKeys.themeSelectedSource); + final v = await LocalDb.instance + .getAppSetting(AppSettingsKeys.themeSelectedSource); if (v == null || v.isEmpty) return null; return v; } @@ -377,7 +378,8 @@ class AppSettings { Future setSelectedThemePath(String? path) async { if (path == null || path.isEmpty) { - await LocalDb.instance.deleteAppSetting(AppSettingsKeys.themeSelectedPath); + await LocalDb.instance + .deleteAppSetting(AppSettingsKeys.themeSelectedPath); } else { await LocalDb.instance.setAppSetting( AppSettingsKeys.themeSelectedPath, @@ -389,7 +391,8 @@ class AppSettings { Future clearSelectedThemeRegistry() async { await LocalDb.instance.deleteAppSetting(AppSettingsKeys.themeSelectedId); - await LocalDb.instance.deleteAppSetting(AppSettingsKeys.themeSelectedSource); + await LocalDb.instance + .deleteAppSetting(AppSettingsKeys.themeSelectedSource); await LocalDb.instance.deleteAppSetting(AppSettingsKeys.themeSelectedPath); AppSettingsRevision.bump(); } @@ -452,8 +455,8 @@ class AppSettings { } Future> getThemeColorOverrides() async { - final v = - await LocalDb.instance.getAppSetting(AppSettingsKeys.themeOverridesJson); + final v = await LocalDb.instance + .getAppSetting(AppSettingsKeys.themeOverridesJson); if (v == null || v.isEmpty) return {}; try { final decoded = jsonDecode(v); @@ -519,9 +522,11 @@ class AppSettings { await LocalDb.instance.deleteAppSetting(AppSettingsKeys.themeMode); await LocalDb.instance.deleteAppSetting(AppSettingsKeys.themePreset); await LocalDb.instance.deleteAppSetting(AppSettingsKeys.themeOverridesJson); - await LocalDb.instance.deleteAppSetting(AppSettingsKeys.themeAnimationEnabled); + await LocalDb.instance + .deleteAppSetting(AppSettingsKeys.themeAnimationEnabled); await LocalDb.instance.deleteAppSetting(AppSettingsKeys.themeSelectedId); - await LocalDb.instance.deleteAppSetting(AppSettingsKeys.themeSelectedSource); + await LocalDb.instance + .deleteAppSetting(AppSettingsKeys.themeSelectedSource); await LocalDb.instance.deleteAppSetting(AppSettingsKeys.themeSelectedPath); await deleteThemeImportKeys(); AppSettingsRevision.bump(); diff --git a/lib/core/storage/connection_secrets_store.dart b/lib/core/storage/connection_secrets_store.dart index 09f75315..a37758e5 100644 --- a/lib/core/storage/connection_secrets_store.dart +++ b/lib/core/storage/connection_secrets_store.dart @@ -38,7 +38,8 @@ class ConnectionSecretsStore { static const _keyPrefix = 'querya.v1.conn'; - static String _passwordKey(int connectionId) => '$_keyPrefix.$connectionId.password'; + static String _passwordKey(int connectionId) => + '$_keyPrefix.$connectionId.password'; static String _connectionStringKey(int connectionId) => '$_keyPrefix.$connectionId.connection_string'; @@ -51,11 +52,13 @@ class ConnectionSecretsStore { await backend.write(_connectionStringKey(connectionId), connectionString); } - static Future<({String? password, String? connectionString})> readForConnection( + static Future<({String? password, String? connectionString})> + readForConnection( int connectionId, ) async { final password = await backend.read(_passwordKey(connectionId)); - final connectionString = await backend.read(_connectionStringKey(connectionId)); + final connectionString = + await backend.read(_connectionStringKey(connectionId)); return (password: password, connectionString: connectionString); } diff --git a/lib/core/storage/folders_storage.dart b/lib/core/storage/folders_storage.dart index 332f57c2..f89696fe 100644 --- a/lib/core/storage/folders_storage.dart +++ b/lib/core/storage/folders_storage.dart @@ -44,7 +44,10 @@ class FoldersStorage { final decoded = jsonDecode(content) as Map; final list = decoded[_keyFolders]; final names = list != null && list is List - ? list.map((e) => e?.toString() ?? '').where((s) => s.isNotEmpty).toList() + ? list + .map((e) => e?.toString() ?? '') + .where((s) => s.isNotEmpty) + .toList() : []; final existing = await LocalDb.instance.getFolders(); for (final name in names) { diff --git a/lib/core/storage/local_db.dart b/lib/core/storage/local_db.dart index bfac6e3e..07ff8442 100644 --- a/lib/core/storage/local_db.dart +++ b/lib/core/storage/local_db.dart @@ -115,8 +115,10 @@ class LocalDb { await db.execute('ALTER TABLE connections ADD COLUMN password TEXT'); await db.execute('ALTER TABLE connections ADD COLUMN database_name TEXT'); await db.execute('ALTER TABLE connections ADD COLUMN auth_source TEXT'); - await db.execute('ALTER TABLE connections ADD COLUMN use_ssl INTEGER NOT NULL DEFAULT 0'); - await db.execute('ALTER TABLE connections ADD COLUMN connection_string TEXT'); + await db.execute( + 'ALTER TABLE connections ADD COLUMN use_ssl INTEGER NOT NULL DEFAULT 0'); + await db + .execute('ALTER TABLE connections ADD COLUMN connection_string TEXT'); } if (oldVersion < 3) { await db.execute('PRAGMA foreign_keys=ON'); @@ -319,15 +321,18 @@ class LocalDb { Future getFolderIdByName(String name) async { final db = await _open(); - final rows = await db.query('folders', columns: ['id'], where: 'name = ?', whereArgs: [name]); + final rows = await db.query('folders', + columns: ['id'], where: 'name = ?', whereArgs: [name]); if (rows.isEmpty) return null; return _sqliteInt(rows.first['id']); } Future> getConnections() async { final db = await _open(); - final rows = await db.query('connections', orderBy: 'sort_order ASC, name ASC'); - final futures = rows.map((m) => _hydrateConnection(ConnectionRow.fromMap(m))); + final rows = + await db.query('connections', orderBy: 'sort_order ASC, name ASC'); + final futures = + rows.map((m) => _hydrateConnection(ConnectionRow.fromMap(m))); return Future.wait(futures); } diff --git a/lib/core/theme/parser/color_parser.dart b/lib/core/theme/parser/color_parser.dart index 38aa0c9d..8627bc39 100644 --- a/lib/core/theme/parser/color_parser.dart +++ b/lib/core/theme/parser/color_parser.dart @@ -70,8 +70,11 @@ Color parseQueryaThemeColor(String raw) { /// Encodes a [Color] as a VS Code hex string (`#RRGGBB` or `#RRGGBBAA`). String formatVsCodeColor(Color color) { - String channel(double component) => - (component * 255.0).round().clamp(0, 255).toRadixString(16).padLeft(2, '0'); + String channel(double component) => (component * 255.0) + .round() + .clamp(0, 255) + .toRadixString(16) + .padLeft(2, '0'); final rr = channel(color.r); final gg = channel(color.g); final bb = channel(color.b); diff --git a/lib/core/theme/parser/querya_theme_color_scheme.dart b/lib/core/theme/parser/querya_theme_color_scheme.dart index 026f191c..543c2cf3 100644 --- a/lib/core/theme/parser/querya_theme_color_scheme.dart +++ b/lib/core/theme/parser/querya_theme_color_scheme.dart @@ -65,7 +65,8 @@ ColorScheme colorSchemeFromQueryaThemeColors({ final destructive = pick('destructive', base.destructive); // querya.theme.v1 still maps this key; shadcn marks the ColorScheme field legacy. // ignore: deprecated_member_use - final destructiveForeground = pick('destructiveForeground', base.destructiveForeground); + final destructiveForeground = + pick('destructiveForeground', base.destructiveForeground); return ColorScheme( brightness: base.brightness, diff --git a/lib/core/theme/parser/querya_theme_from_manifest.dart b/lib/core/theme/parser/querya_theme_from_manifest.dart index 7142b5a3..dce9dce5 100644 --- a/lib/core/theme/parser/querya_theme_from_manifest.dart +++ b/lib/core/theme/parser/querya_theme_from_manifest.dart @@ -11,8 +11,7 @@ import 'querya_workbench_theme_from_manifest.dart'; QueryaTheme queryaThemeFromManifest(QueryaThemeManifest manifest) { final fallback = manifest.isLight ? QueryaTheme.lightDefault : QueryaTheme.darkDefault; - final brightness = - manifest.isLight ? Brightness.light : Brightness.dark; + final brightness = manifest.isLight ? Brightness.light : Brightness.dark; var editor = editorThemeFromQueryaColors( colors: manifest.editorColors, diff --git a/lib/core/theme/parser/querya_theme_from_vscode.dart b/lib/core/theme/parser/querya_theme_from_vscode.dart index 6e029144..914a1b8c 100644 --- a/lib/core/theme/parser/querya_theme_from_vscode.dart +++ b/lib/core/theme/parser/querya_theme_from_vscode.dart @@ -92,8 +92,7 @@ QueryaTheme buildQueryaThemeFromVsCodeManifest( brightness: brightness, ); - final editorForegroundChanged = - editor.foreground != base.editor.foreground; + final editorForegroundChanged = editor.foreground != base.editor.foreground; if (schemeForeground != null || editorForegroundChanged) { final fg = schemeForeground ?? editor.foreground; colorScheme = colorScheme.copyWith( diff --git a/lib/core/theme/parser/querya_theme_manifest.dart b/lib/core/theme/parser/querya_theme_manifest.dart index 074272dd..9d9390a9 100644 --- a/lib/core/theme/parser/querya_theme_manifest.dart +++ b/lib/core/theme/parser/querya_theme_manifest.dart @@ -182,16 +182,19 @@ class QueryaThemeManifest { case 'light': return QueryaThemeType.light; default: - throw QueryaThemeManifestParseException('Invalid type "$raw"; expected dark or light'); + throw QueryaThemeManifestParseException( + 'Invalid type "$raw"; expected dark or light'); } } static Map _parseColorMap(Object? raw, String fieldName) { if (raw == null) { - throw QueryaThemeManifestParseException('Missing required field "$fieldName"'); + throw QueryaThemeManifestParseException( + 'Missing required field "$fieldName"'); } if (raw is! Map) { - throw QueryaThemeManifestParseException('"$fieldName" must be a JSON object'); + throw QueryaThemeManifestParseException( + '"$fieldName" must be a JSON object'); } final colors = {}; diff --git a/lib/core/theme/parser/token_style_resolver.dart b/lib/core/theme/parser/token_style_resolver.dart index 66e665f7..acb48322 100644 --- a/lib/core/theme/parser/token_style_resolver.dart +++ b/lib/core/theme/parser/token_style_resolver.dart @@ -35,8 +35,7 @@ class TokenStyleResolver { List _scopePrefixes(String scope) { final parts = scope.split('.'); return [ - for (var i = parts.length; i >= 1; i--) - parts.sublist(0, i).join('.'), + for (var i = parts.length; i >= 1; i--) parts.sublist(0, i).join('.'), ]; } diff --git a/lib/core/theme/parser/vscode_theme_manifest.dart b/lib/core/theme/parser/vscode_theme_manifest.dart index b9f5f6df..966ff294 100644 --- a/lib/core/theme/parser/vscode_theme_manifest.dart +++ b/lib/core/theme/parser/vscode_theme_manifest.dart @@ -29,7 +29,8 @@ class VsCodeThemeManifest { try { decoded = jsonDecode(cleaned); } on FormatException catch (e) { - throw VsCodeThemeParseException('Invalid JSON after JSONC strip: ${e.message}'); + throw VsCodeThemeParseException( + 'Invalid JSON after JSONC strip: ${e.message}'); } if (decoded is! Map) { throw VsCodeThemeParseException('Theme root must be a JSON object'); diff --git a/lib/core/theme/querya_theme_scope.dart b/lib/core/theme/querya_theme_scope.dart index b35c72b5..8f048508 100644 --- a/lib/core/theme/querya_theme_scope.dart +++ b/lib/core/theme/querya_theme_scope.dart @@ -15,20 +15,18 @@ class QueryaThemeScope extends InheritedWidget { final QueryaTheme data; static QueryaTheme of(BuildContext context) { - final scope = context.dependOnInheritedWidgetOfExactType(); + final scope = + context.dependOnInheritedWidgetOfExactType(); assert(scope != null, 'QueryaThemeScope not found in context'); return scope!.data; } static QueryaTheme? maybeOf(BuildContext context) { - return context - .dependOnInheritedWidgetOfExactType() - ?.data; + return context.dependOnInheritedWidgetOfExactType()?.data; } @override - bool updateShouldNotify(QueryaThemeScope oldWidget) => - data != oldWidget.data; + bool updateShouldNotify(QueryaThemeScope oldWidget) => data != oldWidget.data; } /// Convenient access to [QueryaTheme] tokens from [BuildContext]. diff --git a/lib/core/theme/theme_controller.dart b/lib/core/theme/theme_controller.dart index 6a4f498a..b735eec1 100644 --- a/lib/core/theme/theme_controller.dart +++ b/lib/core/theme/theme_controller.dart @@ -98,7 +98,8 @@ class ThemeController extends ChangeNotifier { String? get importedThemeName => _importedThemeName; - List get availableThemes => List.unmodifiable(_availableThemes); + List get availableThemes => + List.unmodifiable(_availableThemes); /// True while [loadAvailableThemes] is scanning the registry. bool get isLoadingAvailableThemes => _isLoadingAvailableThemes; @@ -162,8 +163,7 @@ class ThemeController extends ChangeNotifier { /// Cached Material theme for dialogs/dropdowns (avoids rebuild churn). material.ThemeData materialThemeFor(ColorScheme scheme) { - if (_cachedMaterialTheme != null && - _cachedMaterialThemeScheme == scheme) { + if (_cachedMaterialTheme != null && _cachedMaterialThemeScheme == scheme) { return _cachedMaterialTheme!; } _cachedMaterialThemeScheme = scheme; @@ -358,7 +358,8 @@ class ThemeController extends ChangeNotifier { ? ThemeMode.light : ThemeMode.dark; await AppSettings.instance.setSelectedThemeId(definition.id); - await AppSettings.instance.setSelectedThemeSource(definition.source.name); + await AppSettings.instance + .setSelectedThemeSource(definition.source.name); await AppSettings.instance.setSelectedThemePath(definition.path); await AppSettings.instance.setThemeMode(_themeMode); _notifyThemeChanged(); @@ -448,8 +449,8 @@ class ThemeController extends ChangeNotifier { String? sha256Checksum, ThemeRemoteInstallService? remoteInstallService, }) async { - final installer = remoteInstallService ?? - ThemeRemoteInstallService(_registryService); + final installer = + remoteInstallService ?? ThemeRemoteInstallService(_registryService); final result = await installer.installFromUrl( url, sha256Checksum: sha256Checksum, @@ -624,7 +625,8 @@ class ThemeController extends ChangeNotifier { List _mergeBuiltinThemes(List scanned) { final merged = [..._builtinThemeDefinitions]; for (final definition in scanned) { - if (!_builtinThemeDefinitions.any((builtin) => builtin.id == definition.id)) { + if (!_builtinThemeDefinitions + .any((builtin) => builtin.id == definition.id)) { merged.add(definition); } } diff --git a/lib/core/theme/theme_editor_loader.dart b/lib/core/theme/theme_editor_loader.dart index bfbf843e..de3097bf 100644 --- a/lib/core/theme/theme_editor_loader.dart +++ b/lib/core/theme/theme_editor_loader.dart @@ -7,7 +7,8 @@ import 'package:querya_desktop/core/theme/theme_editor_draft.dart'; /// Builds a [ThemeEditorDraft] from the active theme selection. abstract final class ThemeEditorLoader { - static Future fromController(ThemeController controller) async { + static Future fromController( + ThemeController controller) async { final selectedId = controller.effectiveSelectedThemeId; final definition = _definitionForId(controller, selectedId); final readOnlySource = definition?.source == ThemeSource.builtin; diff --git a/lib/core/theme/theme_folder_watcher.dart b/lib/core/theme/theme_folder_watcher.dart index b8217bfb..df8ac50e 100644 --- a/lib/core/theme/theme_folder_watcher.dart +++ b/lib/core/theme/theme_folder_watcher.dart @@ -34,7 +34,8 @@ class ThemeFolderWatcher { try { await directory.create(recursive: true); } on Object catch (error) { - debugPrint('ThemeFolderWatcher: cannot create themes directory ($error)'); + debugPrint( + 'ThemeFolderWatcher: cannot create themes directory ($error)'); return; } } diff --git a/lib/core/theme/theme_registry_service.dart b/lib/core/theme/theme_registry_service.dart index 431fd58b..ab18e573 100644 --- a/lib/core/theme/theme_registry_service.dart +++ b/lib/core/theme/theme_registry_service.dart @@ -361,8 +361,7 @@ class ThemeRegistryService { return file.readAsString(); } - Future _readAssetString(String assetPath) => - _assetLoader(assetPath); + Future _readAssetString(String assetPath) => _assetLoader(assetPath); static bool _isAssetPath(String path) => path.startsWith('assets/'); @@ -634,7 +633,8 @@ class ThemeRegistryService { if (!await candidate.exists()) return candidate; } return File( - p.join(themesDir.path, '$baseName-${DateTime.now().millisecondsSinceEpoch}.json'), + p.join(themesDir.path, + '$baseName-${DateTime.now().millisecondsSinceEpoch}.json'), ); } diff --git a/lib/core/theme/theme_remote_install_service.dart b/lib/core/theme/theme_remote_install_service.dart index 0939fb22..061c27c3 100644 --- a/lib/core/theme/theme_remote_install_service.dart +++ b/lib/core/theme/theme_remote_install_service.dart @@ -86,7 +86,8 @@ class ThemeRemoteInstallService { final body = response.body; if (body.trim().isEmpty) { - return const ThemeDefinitionImportFailure('Downloaded theme file is empty.'); + return const ThemeDefinitionImportFailure( + 'Downloaded theme file is empty.'); } final actualChecksum = sha256.convert(utf8.encode(body)).toString(); @@ -96,7 +97,8 @@ class ThemeRemoteInstallService { ); } - final tempDir = Directory.systemTemp.createTempSync('querya_theme_remote_'); + final tempDir = + Directory.systemTemp.createTempSync('querya_theme_remote_'); tempFile = File(p.join(tempDir.path, 'remote-theme.json')); await tempFile.writeAsString(body); diff --git a/lib/features/connections/connection_creation_flow.dart b/lib/features/connections/connection_creation_flow.dart index 9a43338e..36d609ac 100644 --- a/lib/features/connections/connection_creation_flow.dart +++ b/lib/features/connections/connection_creation_flow.dart @@ -27,7 +27,8 @@ Future promptCreateConnection( if (!dialogContext.mounted) return null; switch (type) { case ConnectionType.postgresql: - return await showPostgresConnectionForm(dialogContext, folderId: folderId); + return await showPostgresConnectionForm(dialogContext, + folderId: folderId); case ConnectionType.mysql: return await showMysqlConnectionForm(dialogContext, folderId: folderId); case ConnectionType.mongodb: diff --git a/lib/features/connections/connections_panel.dart b/lib/features/connections/connections_panel.dart index fcaf578c..d61d3251 100644 --- a/lib/features/connections/connections_panel.dart +++ b/lib/features/connections/connections_panel.dart @@ -1,4 +1,57 @@ -import 'package:flutter/material.dart' as material show AlertDialog, BoxConstraints, BuildContext, Column, ConstrainedBox, Container, BoxDecoration, Border, BorderSide, InkWell, Icon, Icons, IconData, Image, EdgeInsets, EdgeInsetsGeometry, BorderRadius, CrossAxisAlignment, MainAxisSize, MouseRegion, SystemMouseCursors, TextStyle, CustomScrollView, SliverFillRemaining, SliverPadding, SliverList, SliverChildBuilderDelegate, GestureDetector, HitTestBehavior, SizedBox, AnimatedRotation, Row, BoxFit, Text, TextOverflow, Expanded, CircularProgressIndicator, Material, StatelessWidget, Colors, Tooltip, Color, SelectableText, Padding, Widget, Navigator, ValueKey, FontWeight, VoidCallback, RepaintBoundary, ListView, ClampingScrollPhysics; +import 'package:flutter/material.dart' as material + show + AlertDialog, + BoxConstraints, + BuildContext, + Column, + ConstrainedBox, + Container, + BoxDecoration, + Border, + BorderSide, + InkWell, + Icon, + Icons, + IconData, + Image, + EdgeInsets, + EdgeInsetsGeometry, + BorderRadius, + CrossAxisAlignment, + MainAxisSize, + MouseRegion, + SystemMouseCursors, + TextStyle, + CustomScrollView, + SliverFillRemaining, + SliverPadding, + SliverList, + SliverChildBuilderDelegate, + GestureDetector, + HitTestBehavior, + SizedBox, + AnimatedRotation, + Row, + BoxFit, + Text, + TextOverflow, + Expanded, + CircularProgressIndicator, + Material, + StatelessWidget, + Colors, + Tooltip, + Color, + SelectableText, + Padding, + Widget, + Navigator, + ValueKey, + FontWeight, + VoidCallback, + RepaintBoundary, + ListView, + ClampingScrollPhysics; import 'package:flutter/services.dart' show Clipboard, ClipboardData; import 'package:querya_desktop/core/database/mongodb_service.dart'; import 'package:querya_desktop/core/database/mysql_service.dart'; @@ -96,6 +149,7 @@ class ConnectionsPanel extends StatefulWidget { this.onPostgresOpenSqlWorkspace, this.onMysqlObjectSelected, this.onMysqlOpenSqlWorkspace, + /// When true, [initState] does not call [_loadData]. Widget tests that seed /// SQLite in setUp should call [ConnectionsPanelState.reloadConnectionsFromDb] /// inside [WidgetTester.runAsync] so only one load runs (avoids overlapping @@ -151,6 +205,7 @@ class ConnectionsPanelState extends State { List _connections = []; Map _folderIdByName = {}; final Set _expandedFolders = {}; + /// Ignores stale [setState] when multiple [_loadData] runs overlap (e.g. tests). int _loadDataGeneration = 0; @@ -258,8 +313,8 @@ class ConnectionsPanelState extends State { } Widget _buildConnectionTile(ConnectionRow conn) { - final isSelected = - widget.selectedConnectionId != null && widget.selectedConnectionId == conn.id; + final isSelected = widget.selectedConnectionId != null && + widget.selectedConnectionId == conn.id; if (conn.type == 'postgresql') { return _PostgresConnectionTile( connection: conn, @@ -318,7 +373,8 @@ class ConnectionsPanelState extends State { final theme = Theme.of(context); // Connections without a folder - final rootConnections = _connections.where((c) => c.folderId == null).toList(); + final rootConnections = + _connections.where((c) => c.folderId == null).toList(); final showEmptyState = _connections.isEmpty && _folders.isEmpty; final topLevelCount = _folders.length + rootConnections.length + (showEmptyState ? 1 : 0); @@ -349,95 +405,113 @@ class ConnectionsPanelState extends State { ), ), ), - Divider(height: 1, color: theme.colorScheme.border.withValues(alpha: 0.22)), + Divider( + height: 1, + color: theme.colorScheme.border.withValues(alpha: 0.22)), Expanded( child: material.RepaintBoundary( child: material.CustomScrollView( slivers: [ - material.SliverPadding( - padding: const material.EdgeInsets.symmetric(horizontal: 12, vertical: 12), - sliver: material.SliverList( - delegate: material.SliverChildBuilderDelegate( - (context, index) { - if (showEmptyState && index == 0) { - return const material.Padding( - padding: material.EdgeInsets.only(top: 8), - child: _EmptyState(message: 'No connections yet'), - ); - } - final folderOffset = showEmptyState ? 1 : 0; - final folderIndex = index - folderOffset; - if (folderIndex < _folders.length) { - final name = _folders[folderIndex]; - return _FolderTile( - name: name, - initiallyExpanded: _expandedFolders.contains(name), - onExpansionCommitted: (folderName, expanded) { - if (expanded) { - _expandedFolders.add(folderName); - } else { - _expandedFolders.remove(folderName); - } - }, - connections: _connections - .where((c) => c.folderId == _folderIdByName[name]) - .toList(), - onRemove: () async { - await FoldersStorage.instance.remove(name); - await _loadData(); - }, - onNewConnection: (folderName) async { - final folderId = - await LocalDb.instance.getFolderIdByName(folderName); - await _createConnection(folderId: folderId); - }, - iconForType: _iconForType, - onRemoveConnection: _removeConnection, - onConnectionTap: widget.onConnectionSelected, - onRedisDatabaseTap: widget.onRedisDatabaseSelected, - onMongoDBDatabaseTap: widget.onMongoDBDatabaseSelected, - buildConnectionTile: _buildConnectionTile, - ); - } - final connIndex = folderIndex - _folders.length; - return _buildConnectionTile(rootConnections[connIndex]); - }, - childCount: topLevelCount, + material.SliverPadding( + padding: const material.EdgeInsets.symmetric( + horizontal: 12, vertical: 12), + sliver: material.SliverList( + delegate: material.SliverChildBuilderDelegate( + (context, index) { + if (showEmptyState && index == 0) { + return const material.Padding( + padding: material.EdgeInsets.only(top: 8), + child: _EmptyState(message: 'No connections yet'), + ); + } + final folderOffset = showEmptyState ? 1 : 0; + final folderIndex = index - folderOffset; + if (folderIndex < _folders.length) { + final name = _folders[folderIndex]; + return _FolderTile( + name: name, + initiallyExpanded: + _expandedFolders.contains(name), + onExpansionCommitted: (folderName, expanded) { + if (expanded) { + _expandedFolders.add(folderName); + } else { + _expandedFolders.remove(folderName); + } + }, + connections: _connections + .where((c) => + c.folderId == _folderIdByName[name]) + .toList(), + onRemove: () async { + await FoldersStorage.instance.remove(name); + await _loadData(); + }, + onNewConnection: (folderName) async { + final folderId = await LocalDb.instance + .getFolderIdByName(folderName); + await _createConnection(folderId: folderId); + }, + iconForType: _iconForType, + onRemoveConnection: _removeConnection, + onConnectionTap: widget.onConnectionSelected, + onRedisDatabaseTap: + widget.onRedisDatabaseSelected, + onMongoDBDatabaseTap: + widget.onMongoDBDatabaseSelected, + buildConnectionTile: _buildConnectionTile, + ); + } + final connIndex = folderIndex - _folders.length; + return _buildConnectionTile( + rootConnections[connIndex]); + }, + childCount: topLevelCount, + ), ), ), - ), - material.SliverFillRemaining( - hasScrollBody: false, - child: ContextMenu( - items: [ - MenuButton( - leading: material.Icon(material.Icons.add_rounded, size: 18, color: theme.colorScheme.mutedForeground), - subMenu: [ - MenuButton( - leading: material.Icon(material.Icons.settings_ethernet_rounded, size: 18, color: theme.colorScheme.mutedForeground), - onPressed: (menuContext) async { - await Future.delayed(const Duration(milliseconds: 100)); - if (!mounted) return; - await _createConnection(); - }, - child: const Text('New Connection'), - ), - MenuButton( - leading: material.Icon(material.Icons.folder_rounded, size: 18, color: theme.colorScheme.mutedForeground), - onPressed: (menuContext) => _createFolder(menuContext), - child: const Text('New Folder'), - ), - ], - child: const Text('Create'), + material.SliverFillRemaining( + hasScrollBody: false, + child: ContextMenu( + items: [ + MenuButton( + leading: material.Icon(material.Icons.add_rounded, + size: 18, + color: theme.colorScheme.mutedForeground), + subMenu: [ + MenuButton( + leading: material.Icon( + material.Icons.settings_ethernet_rounded, + size: 18, + color: theme.colorScheme.mutedForeground), + onPressed: (menuContext) async { + await Future.delayed( + const Duration(milliseconds: 100)); + if (!mounted) return; + await _createConnection(); + }, + child: const Text('New Connection'), + ), + MenuButton( + leading: material.Icon( + material.Icons.folder_rounded, + size: 18, + color: theme.colorScheme.mutedForeground), + onPressed: (menuContext) => + _createFolder(menuContext), + child: const Text('New Folder'), + ), + ], + child: const Text('Create'), + ), + ], + child: material.GestureDetector( + behavior: material.HitTestBehavior.opaque, + child: const material.SizedBox.expand(), ), - ], - child: material.GestureDetector( - behavior: material.HitTestBehavior.opaque, - child: const material.SizedBox.expand(), ), ), - ), - ], + ], ), ), ), diff --git a/lib/features/connections/connections_panel_mongo.dart b/lib/features/connections/connections_panel_mongo.dart index ca0a9b46..29ac0343 100644 --- a/lib/features/connections/connections_panel_mongo.dart +++ b/lib/features/connections/connections_panel_mongo.dart @@ -45,7 +45,8 @@ class _MongoConnectionTileState extends State<_MongoConnectionTile> { _error = null; }); try { - final conn = await MongoService.instance.ensureConnected(widget.connection); + final conn = + await MongoService.instance.ensureConnected(widget.connection); final dbs = await conn.listDatabases(); if (!mounted) return; @@ -93,7 +94,8 @@ class _MongoConnectionTileState extends State<_MongoConnectionTile> { ); if (ok != true || !mounted) return; try { - final conn = await MongoService.instance.ensureConnected(widget.connection); + final conn = + await MongoService.instance.ensureConnected(widget.connection); await conn.dropDatabase(dbName); if (mounted) { @@ -124,7 +126,8 @@ class _MongoConnectionTileState extends State<_MongoConnectionTile> { color: theme.colorScheme.primary, ), ) - : material.Icon(widget.icon, size: 16, color: theme.colorScheme.primary); + : material.Icon(widget.icon, + size: 16, color: theme.colorScheme.primary); return ContextMenu( items: [ @@ -235,77 +238,82 @@ class _MongoConnectionTileState extends State<_MongoConnectionTile> { mainAxisSize: material.MainAxisSize.min, crossAxisAlignment: material.CrossAxisAlignment.stretch, children: [ - if (_loading) - material.Padding( - padding: const material.EdgeInsets.only(left: 28, top: 4, bottom: 4), - child: material.Row( - children: [ - const material.SizedBox( - width: 12, - height: 12, - child: material.CircularProgressIndicator(strokeWidth: 1.5), + if (_loading) + material.Padding( + padding: const material.EdgeInsets.only( + left: 28, top: 4, bottom: 4), + child: material.Row( + children: [ + const material.SizedBox( + width: 12, + height: 12, + child: material.CircularProgressIndicator( + strokeWidth: 1.5), + ), + const Gap(8), + const Text('Loading...').muted().xSmall(), + ], ), - const Gap(8), - const Text('Loading...').muted().xSmall(), - ], - ), - ), - if (_error != null) - material.Padding( - padding: const material.EdgeInsets.only(left: 28, top: 4, bottom: 4, right: 8), - child: material.ConstrainedBox( - constraints: const material.BoxConstraints(maxWidth: double.infinity), - child: material.Column( - crossAxisAlignment: material.CrossAxisAlignment.start, - mainAxisSize: material.MainAxisSize.min, - children: [ - material.Row( + ), + if (_error != null) + material.Padding( + padding: const material.EdgeInsets.only( + left: 28, top: 4, bottom: 4, right: 8), + child: material.ConstrainedBox( + constraints: const material.BoxConstraints( + maxWidth: double.infinity), + child: material.Column( crossAxisAlignment: material.CrossAxisAlignment.start, + mainAxisSize: material.MainAxisSize.min, children: [ - material.Icon( - material.Icons.error_outline_rounded, - size: 14, - color: theme.colorScheme.destructive, - ), - const Gap(6), - material.Expanded( - child: material.Text( - 'Could not load databases', - maxLines: 2, - overflow: material.TextOverflow.ellipsis, - style: material.TextStyle( - fontSize: 12, + material.Row( + crossAxisAlignment: + material.CrossAxisAlignment.start, + children: [ + material.Icon( + material.Icons.error_outline_rounded, + size: 14, color: theme.colorScheme.destructive, ), + const Gap(6), + material.Expanded( + child: material.Text( + 'Could not load databases', + maxLines: 2, + overflow: material.TextOverflow.ellipsis, + style: material.TextStyle( + fontSize: 12, + color: theme.colorScheme.destructive, + ), + ), + ), + ], + ), + const Gap(6), + material.SelectableText( + _error!, + style: material.TextStyle( + fontSize: 10, + height: 1.35, + color: theme.colorScheme.mutedForeground, ), ), ], ), - const Gap(6), - material.SelectableText( - _error!, - style: material.TextStyle( - fontSize: 10, - height: 1.35, - color: theme.colorScheme.mutedForeground, - ), - ), - ], + ), ), - ), - ), - for (final db in _databases) - _MongoDatabaseNode( - connection: widget.connection, - name: db, - onTap: () => widget.onDatabaseTap?.call(db), - onDelete: () => _deleteDatabase(db), - onRefreshDatabases: () { - setState(() => _databases = []); - _loadDatabases(); - }, - ), - ], + for (final db in _databases) + _MongoDatabaseNode( + connection: widget.connection, + name: db, + onTap: () => widget.onDatabaseTap?.call(db), + onDelete: () => _deleteDatabase(db), + onRefreshDatabases: () { + setState(() => _databases = []); + _loadDatabases(); + }, + ), + ], ), ), ], diff --git a/lib/features/connections/connections_panel_mysql.dart b/lib/features/connections/connections_panel_mysql.dart index 84b191d8..8e6b6c17 100644 --- a/lib/features/connections/connections_panel_mysql.dart +++ b/lib/features/connections/connections_panel_mysql.dart @@ -91,7 +91,8 @@ class _MysqlConnectionTileState extends State<_MysqlConnectionTile> { color: theme.colorScheme.primary, ), ) - : material.Icon(widget.icon, size: 16, color: theme.colorScheme.primary); + : material.Icon(widget.icon, + size: 16, color: theme.colorScheme.primary); return ContextMenu( items: [ @@ -108,7 +109,8 @@ class _MysqlConnectionTileState extends State<_MysqlConnectionTile> { MenuButton( leading: material.Icon(material.Icons.terminal_rounded, size: 18, color: theme.colorScheme.mutedForeground), - onPressed: (_) => widget.onMysqlOpenSqlWorkspace!(widget.connection), + onPressed: (_) => + widget.onMysqlOpenSqlWorkspace!(widget.connection), child: const Text('Open in SQL'), ), MenuButton( @@ -180,8 +182,7 @@ class _MysqlConnectionTileState extends State<_MysqlConnectionTile> { maxLines: 1, style: material.TextStyle( fontSize: 11, - color: - theme.colorScheme.mutedForeground, + color: theme.colorScheme.mutedForeground, ), ), ], @@ -200,47 +201,47 @@ class _MysqlConnectionTileState extends State<_MysqlConnectionTile> { mainAxisSize: material.MainAxisSize.min, crossAxisAlignment: material.CrossAxisAlignment.stretch, children: [ - if (_loading) - material.Padding( - padding: - const material.EdgeInsets.only(left: 28, top: 4, bottom: 4), - child: material.Row( - children: [ - const material.SizedBox( - width: 12, - height: 12, - child: - material.CircularProgressIndicator(strokeWidth: 1.5), + if (_loading) + material.Padding( + padding: const material.EdgeInsets.only( + left: 28, top: 4, bottom: 4), + child: material.Row( + children: [ + const material.SizedBox( + width: 12, + height: 12, + child: material.CircularProgressIndicator( + strokeWidth: 1.5), + ), + const Gap(8), + const Text('Loading...').muted().xSmall(), + ], ), - const Gap(8), - const Text('Loading...').muted().xSmall(), - ], - ), - ), - if (_error != null) - material.Padding( - padding: - const material.EdgeInsets.only(left: 28, top: 4, bottom: 4), - child: material.Text( - 'Error', - overflow: material.TextOverflow.ellipsis, - maxLines: 1, - style: material.TextStyle( - fontSize: 11, color: theme.colorScheme.destructive), - ), - ), - if (_databases.isNotEmpty) - _MysqlDatabasesNode( - connection: widget.connection, - databases: _databases, - onRefreshDatabases: () { - setState(() => _databases = []); - _loadDatabases(); - }, - onMysqlObjectSelected: widget.onMysqlObjectSelected, - onMysqlOpenSqlWorkspace: widget.onMysqlOpenSqlWorkspace, - ), - ], + ), + if (_error != null) + material.Padding( + padding: const material.EdgeInsets.only( + left: 28, top: 4, bottom: 4), + child: material.Text( + 'Error', + overflow: material.TextOverflow.ellipsis, + maxLines: 1, + style: material.TextStyle( + fontSize: 11, color: theme.colorScheme.destructive), + ), + ), + if (_databases.isNotEmpty) + _MysqlDatabasesNode( + connection: widget.connection, + databases: _databases, + onRefreshDatabases: () { + setState(() => _databases = []); + _loadDatabases(); + }, + onMysqlObjectSelected: widget.onMysqlObjectSelected, + onMysqlOpenSqlWorkspace: widget.onMysqlOpenSqlWorkspace, + ), + ], ), ), ], @@ -395,8 +396,8 @@ class _MysqlDatabaseNodeState extends State<_MysqlDatabaseNode> { label: widget.databaseName, leading: material.AnimatedRotation( turns: _expanded ? 0.25 : 0, - duration: context.motionDuration(QueryaMotion.fast), - curve: context.motionCurve(QueryaMotion.standardCurve), + duration: context.motionDuration(QueryaMotion.fast), + curve: context.motionCurve(QueryaMotion.standardCurve), child: material.Icon( material.Icons.chevron_right_rounded, size: 14, @@ -425,135 +426,135 @@ class _MysqlDatabaseNodeState extends State<_MysqlDatabaseNode> { mainAxisSize: material.MainAxisSize.min, crossAxisAlignment: material.CrossAxisAlignment.stretch, children: [ - if (_loading) - material.Padding( - padding: - const material.EdgeInsets.only(left: 24, top: 2, bottom: 2), - child: material.Row( - children: [ - const material.SizedBox( - width: 10, - height: 10, - child: - material.CircularProgressIndicator(strokeWidth: 1.5), - ), - const Gap(6), - const Text('Loading...').muted().xSmall(), - ], - ), - ), - if (_tables.isNotEmpty || _views.isNotEmpty) - material.Padding( - padding: const material.EdgeInsets.only(left: 16), - child: material.Column( - crossAxisAlignment: material.CrossAxisAlignment.start, - children: [ - if (_tables.isNotEmpty) ...[ - _PgTreeRow( - label: 'Tables (${_tables.length})', - icon: material.Icons.table_chart_rounded, - iconSize: 13, - iconColor: theme.colorScheme.mutedForeground, - textStyle: material.TextStyle( - fontSize: 11, - color: theme.colorScheme.mutedForeground, + if (_loading) + material.Padding( + padding: const material.EdgeInsets.only( + left: 24, top: 2, bottom: 2), + child: material.Row( + children: [ + const material.SizedBox( + width: 10, + height: 10, + child: material.CircularProgressIndicator( + strokeWidth: 1.5), ), - verticalPadding: 3, - onTap: null, - connection: widget.connection, - onContextRefresh: _loadTables, - onOpenSqlWorkspace: null, - ), - lazyConnectionTreeList( - context: context, - itemCount: _tables.length, - itemExtent: kConnectionTreeRowExtent, - padding: const material.EdgeInsets.only(left: 12), - itemBuilder: (context, index) { - final t = _tables[index]; - return _PgTreeRow( - key: material.ValueKey( - 'mysql-table-${widget.connection.id ?? 0}-${widget.databaseName}-$t', - ), - label: t, - icon: material.Icons.grid_on_rounded, - iconSize: 12, + const Gap(6), + const Text('Loading...').muted().xSmall(), + ], + ), + ), + if (_tables.isNotEmpty || _views.isNotEmpty) + material.Padding( + padding: const material.EdgeInsets.only(left: 16), + child: material.Column( + crossAxisAlignment: material.CrossAxisAlignment.start, + children: [ + if (_tables.isNotEmpty) ...[ + _PgTreeRow( + label: 'Tables (${_tables.length})', + icon: material.Icons.table_chart_rounded, + iconSize: 13, iconColor: theme.colorScheme.mutedForeground, textStyle: material.TextStyle( fontSize: 11, - color: theme.colorScheme.foreground, + color: theme.colorScheme.mutedForeground, ), - verticalPadding: 2, - onTap: widget.onMysqlObjectSelected == null - ? null - : () => widget.onMysqlObjectSelected!( - widget.connection, - widget.databaseName, - t, - MysqlObjectKind.table, - ), + verticalPadding: 3, + onTap: null, connection: widget.connection, - onContextRefresh: null, + onContextRefresh: _loadTables, onOpenSqlWorkspace: null, - ); - }, - ), - ], - if (_views.isNotEmpty) ...[ - _PgTreeRow( - label: 'Views (${_views.length})', - icon: material.Icons.view_agenda_rounded, - iconSize: 13, - iconColor: theme.colorScheme.mutedForeground, - textStyle: material.TextStyle( - fontSize: 11, - color: theme.colorScheme.mutedForeground, - ), - verticalPadding: 3, - onTap: null, - connection: widget.connection, - onContextRefresh: _loadTables, - onOpenSqlWorkspace: null, - ), - lazyConnectionTreeList( - context: context, - itemCount: _views.length, - itemExtent: kConnectionTreeRowExtent, - padding: const material.EdgeInsets.only(left: 12), - itemBuilder: (context, index) { - final v = _views[index]; - return _PgTreeRow( - key: material.ValueKey( - 'mysql-view-${widget.connection.id ?? 0}-${widget.databaseName}-$v', - ), - label: v, - icon: material.Icons.view_week_rounded, - iconSize: 12, + ), + lazyConnectionTreeList( + context: context, + itemCount: _tables.length, + itemExtent: kConnectionTreeRowExtent, + padding: const material.EdgeInsets.only(left: 12), + itemBuilder: (context, index) { + final t = _tables[index]; + return _PgTreeRow( + key: material.ValueKey( + 'mysql-table-${widget.connection.id ?? 0}-${widget.databaseName}-$t', + ), + label: t, + icon: material.Icons.grid_on_rounded, + iconSize: 12, + iconColor: theme.colorScheme.mutedForeground, + textStyle: material.TextStyle( + fontSize: 11, + color: theme.colorScheme.foreground, + ), + verticalPadding: 2, + onTap: widget.onMysqlObjectSelected == null + ? null + : () => widget.onMysqlObjectSelected!( + widget.connection, + widget.databaseName, + t, + MysqlObjectKind.table, + ), + connection: widget.connection, + onContextRefresh: null, + onOpenSqlWorkspace: null, + ); + }, + ), + ], + if (_views.isNotEmpty) ...[ + _PgTreeRow( + label: 'Views (${_views.length})', + icon: material.Icons.view_agenda_rounded, + iconSize: 13, iconColor: theme.colorScheme.mutedForeground, textStyle: material.TextStyle( fontSize: 11, - color: theme.colorScheme.foreground, + color: theme.colorScheme.mutedForeground, ), - verticalPadding: 2, - onTap: widget.onMysqlObjectSelected == null - ? null - : () => widget.onMysqlObjectSelected!( - widget.connection, - widget.databaseName, - v, - MysqlObjectKind.view, - ), + verticalPadding: 3, + onTap: null, connection: widget.connection, - onContextRefresh: null, + onContextRefresh: _loadTables, onOpenSqlWorkspace: null, - ); - }, - ), - ], - ], - ), - ), - ], + ), + lazyConnectionTreeList( + context: context, + itemCount: _views.length, + itemExtent: kConnectionTreeRowExtent, + padding: const material.EdgeInsets.only(left: 12), + itemBuilder: (context, index) { + final v = _views[index]; + return _PgTreeRow( + key: material.ValueKey( + 'mysql-view-${widget.connection.id ?? 0}-${widget.databaseName}-$v', + ), + label: v, + icon: material.Icons.view_week_rounded, + iconSize: 12, + iconColor: theme.colorScheme.mutedForeground, + textStyle: material.TextStyle( + fontSize: 11, + color: theme.colorScheme.foreground, + ), + verticalPadding: 2, + onTap: widget.onMysqlObjectSelected == null + ? null + : () => widget.onMysqlObjectSelected!( + widget.connection, + widget.databaseName, + v, + MysqlObjectKind.view, + ), + connection: widget.connection, + onContextRefresh: null, + onOpenSqlWorkspace: null, + ); + }, + ), + ], + ], + ), + ), + ], ), ), ], diff --git a/lib/features/connections/connections_panel_pg_tree.dart b/lib/features/connections/connections_panel_pg_tree.dart index 60859e26..aee015ff 100644 --- a/lib/features/connections/connections_panel_pg_tree.dart +++ b/lib/features/connections/connections_panel_pg_tree.dart @@ -173,12 +173,12 @@ class _PgTreeRow extends material.StatelessWidget { color: theme.colorScheme.mutedForeground, ), onPressed: (_) => onOpenSqlWorkspace!( - connection!, - database: openSqlDatabase, - schema: openSqlSchema, - name: openSqlName, - kind: openSqlKind, - ), + connection!, + database: openSqlDatabase, + schema: openSqlSchema, + name: openSqlName, + kind: openSqlKind, + ), child: const Text('Open in SQL'), ), if (onContextDelete != null) @@ -242,7 +242,8 @@ class _PgDatabasesNodeState extends State<_PgDatabasesNode> { itemBuilder: (context, index) { final db = widget.databases[index]; return _PgDatabaseNode( - key: material.ValueKey('pg-db-${widget.connection.id ?? 0}-$db'), + key: material.ValueKey( + 'pg-db-${widget.connection.id ?? 0}-$db'), connection: widget.connection, databaseName: db, onPostgresObjectSelected: widget.onPostgresObjectSelected, @@ -358,53 +359,54 @@ class _PgDatabaseNodeState extends State<_PgDatabaseNode> { mainAxisSize: material.MainAxisSize.min, crossAxisAlignment: material.CrossAxisAlignment.stretch, children: [ - _PgDbToolRow( - connection: widget.connection, - databaseName: widget.databaseName, - label: 'Extensions', - icon: material.Icons.extension_rounded, - kind: PostgresObjectKind.databaseExtensions, - onPostgresObjectSelected: widget.onPostgresObjectSelected, - onPostgresOpenSqlWorkspace: widget.onPostgresOpenSqlWorkspace, - onContextRefresh: _loadSchemas, - ), - _PgDbToolRow( - connection: widget.connection, - databaseName: widget.databaseName, - label: 'Foreign data', - icon: material.Icons.public_rounded, - kind: PostgresObjectKind.databaseForeignData, - onPostgresObjectSelected: widget.onPostgresObjectSelected, - onPostgresOpenSqlWorkspace: widget.onPostgresOpenSqlWorkspace, - onContextRefresh: _loadSchemas, - ), - if (_loading) - material.Padding( - padding: - const material.EdgeInsets.only(left: 24, top: 2, bottom: 2), - child: material.Row( - children: [ - const material.SizedBox( - width: 10, - height: 10, - child: - material.CircularProgressIndicator(strokeWidth: 1.5), - ), - const Gap(6), - const Text('Loading...').muted().xSmall(), - ], + _PgDbToolRow( + connection: widget.connection, + databaseName: widget.databaseName, + label: 'Extensions', + icon: material.Icons.extension_rounded, + kind: PostgresObjectKind.databaseExtensions, + onPostgresObjectSelected: widget.onPostgresObjectSelected, + onPostgresOpenSqlWorkspace: widget.onPostgresOpenSqlWorkspace, + onContextRefresh: _loadSchemas, ), - ), - if (_schemas.isNotEmpty) - _PgSchemasNode( - connection: widget.connection, - databaseName: widget.databaseName, - schemas: _schemas, - onPostgresObjectSelected: widget.onPostgresObjectSelected, - onPostgresOpenSqlWorkspace: widget.onPostgresOpenSqlWorkspace, - onRefreshSchemas: _loadSchemas, - ), - ], + _PgDbToolRow( + connection: widget.connection, + databaseName: widget.databaseName, + label: 'Foreign data', + icon: material.Icons.public_rounded, + kind: PostgresObjectKind.databaseForeignData, + onPostgresObjectSelected: widget.onPostgresObjectSelected, + onPostgresOpenSqlWorkspace: widget.onPostgresOpenSqlWorkspace, + onContextRefresh: _loadSchemas, + ), + if (_loading) + material.Padding( + padding: const material.EdgeInsets.only( + left: 24, top: 2, bottom: 2), + child: material.Row( + children: [ + const material.SizedBox( + width: 10, + height: 10, + child: material.CircularProgressIndicator( + strokeWidth: 1.5), + ), + const Gap(6), + const Text('Loading...').muted().xSmall(), + ], + ), + ), + if (_schemas.isNotEmpty) + _PgSchemasNode( + connection: widget.connection, + databaseName: widget.databaseName, + schemas: _schemas, + onPostgresObjectSelected: widget.onPostgresObjectSelected, + onPostgresOpenSqlWorkspace: + widget.onPostgresOpenSqlWorkspace, + onRefreshSchemas: _loadSchemas, + ), + ], ), ), ], @@ -625,8 +627,7 @@ class _PgSchemaNodeState extends State<_PgSchemaNode> { final views = await conn.listViews(schema: widget.schemaName); List matviews = []; try { - matviews = - await conn.listMaterializedViews(schema: widget.schemaName); + matviews = await conn.listMaterializedViews(schema: widget.schemaName); } catch (_) { // pg_matviews / permissions may fail on some servers; keep tree usable. } @@ -689,159 +690,167 @@ class _PgSchemaNodeState extends State<_PgSchemaNode> { mainAxisSize: material.MainAxisSize.min, crossAxisAlignment: material.CrossAxisAlignment.stretch, children: [ - if (_loading) - material.Padding( - padding: - const material.EdgeInsets.only(left: 24, top: 2, bottom: 2), - child: material.Row( - children: [ - const material.SizedBox( - width: 10, - height: 10, - child: - material.CircularProgressIndicator(strokeWidth: 1.5), + if (_loading) + material.Padding( + padding: const material.EdgeInsets.only( + left: 24, top: 2, bottom: 2), + child: material.Row( + children: [ + const material.SizedBox( + width: 10, + height: 10, + child: material.CircularProgressIndicator( + strokeWidth: 1.5), + ), + const Gap(6), + const Text('Loading...').muted().xSmall(), + ], ), - const Gap(6), - const Text('Loading...').muted().xSmall(), - ], - ), - ), - if (_loaded) ...[ - _PgObjectGroup( - connection: widget.connection, - databaseName: widget.databaseName, - schemaName: widget.schemaName, - objectKind: PostgresObjectKind.table, - onPostgresOpenSqlWorkspace: widget.onPostgresOpenSqlWorkspace, - onRefresh: _loadObjects, - label: 'Tables', - icon: material.Icons.table_chart_rounded, - items: _tables, - onItemTap: widget.onPostgresObjectSelected != null - ? (name) => widget.onPostgresObjectSelected!( - widget.connection, - widget.databaseName, - widget.schemaName, - name, - PostgresObjectKind.table, - ) - : null, - ), - _PgObjectGroup( - connection: widget.connection, - databaseName: widget.databaseName, - schemaName: widget.schemaName, - objectKind: PostgresObjectKind.view, - onPostgresOpenSqlWorkspace: widget.onPostgresOpenSqlWorkspace, - onRefresh: _loadObjects, - label: 'Views', - icon: material.Icons.view_agenda_rounded, - items: _views, - onItemTap: widget.onPostgresObjectSelected != null - ? (name) => widget.onPostgresObjectSelected!( - widget.connection, - widget.databaseName, - widget.schemaName, - name, - PostgresObjectKind.view, - ) - : null, - ), - _PgObjectGroup( - connection: widget.connection, - databaseName: widget.databaseName, - schemaName: widget.schemaName, - objectKind: PostgresObjectKind.materializedView, - onPostgresOpenSqlWorkspace: widget.onPostgresOpenSqlWorkspace, - onRefresh: _loadObjects, - label: 'Materialized views', - icon: material.Icons.dynamic_feed_rounded, - items: _matviews, - onItemTap: widget.onPostgresObjectSelected != null - ? (name) => widget.onPostgresObjectSelected!( - widget.connection, - widget.databaseName, - widget.schemaName, - name, - PostgresObjectKind.materializedView, - ) - : null, - ), - _PgObjectGroup( - connection: widget.connection, - databaseName: widget.databaseName, - schemaName: widget.schemaName, - objectKind: PostgresObjectKind.function, - onPostgresOpenSqlWorkspace: widget.onPostgresOpenSqlWorkspace, - onRefresh: _loadObjects, - label: 'Functions', - icon: material.Icons.functions_rounded, - items: _functions, - onItemTap: widget.onPostgresObjectSelected != null - ? (name) => widget.onPostgresObjectSelected!( - widget.connection, - widget.databaseName, - widget.schemaName, - name, - PostgresObjectKind.function, - ) - : null, - ), - _PgObjectGroup( - connection: widget.connection, - databaseName: widget.databaseName, - schemaName: widget.schemaName, - objectKind: PostgresObjectKind.sequence, - onPostgresOpenSqlWorkspace: widget.onPostgresOpenSqlWorkspace, - onRefresh: _loadObjects, - label: 'Sequences', - icon: material.Icons.format_list_numbered_rounded, - items: _sequences, - onItemTap: widget.onPostgresObjectSelected != null - ? (name) => widget.onPostgresObjectSelected!( - widget.connection, - widget.databaseName, - widget.schemaName, - name, - PostgresObjectKind.sequence, - ) - : null, - ), - _PgSchemaToolRow( - connection: widget.connection, - databaseName: widget.databaseName, - schemaName: widget.schemaName, - label: 'Indexes', - icon: material.Icons.table_rows_rounded, - kind: PostgresObjectKind.schemaIndexes, - onPostgresObjectSelected: widget.onPostgresObjectSelected, - onPostgresOpenSqlWorkspace: widget.onPostgresOpenSqlWorkspace, - onContextRefresh: _loadObjects, - ), - _PgSchemaToolRow( - connection: widget.connection, - databaseName: widget.databaseName, - schemaName: widget.schemaName, - label: 'Triggers', - icon: material.Icons.bolt_rounded, - kind: PostgresObjectKind.schemaTriggers, - onPostgresObjectSelected: widget.onPostgresObjectSelected, - onPostgresOpenSqlWorkspace: widget.onPostgresOpenSqlWorkspace, - onContextRefresh: _loadObjects, - ), - _PgSchemaToolRow( - connection: widget.connection, - databaseName: widget.databaseName, - schemaName: widget.schemaName, - label: 'Types', - icon: material.Icons.category_rounded, - kind: PostgresObjectKind.schemaTypes, - onPostgresObjectSelected: widget.onPostgresObjectSelected, - onPostgresOpenSqlWorkspace: widget.onPostgresOpenSqlWorkspace, - onContextRefresh: _loadObjects, - ), - ], - ], + ), + if (_loaded) ...[ + _PgObjectGroup( + connection: widget.connection, + databaseName: widget.databaseName, + schemaName: widget.schemaName, + objectKind: PostgresObjectKind.table, + onPostgresOpenSqlWorkspace: + widget.onPostgresOpenSqlWorkspace, + onRefresh: _loadObjects, + label: 'Tables', + icon: material.Icons.table_chart_rounded, + items: _tables, + onItemTap: widget.onPostgresObjectSelected != null + ? (name) => widget.onPostgresObjectSelected!( + widget.connection, + widget.databaseName, + widget.schemaName, + name, + PostgresObjectKind.table, + ) + : null, + ), + _PgObjectGroup( + connection: widget.connection, + databaseName: widget.databaseName, + schemaName: widget.schemaName, + objectKind: PostgresObjectKind.view, + onPostgresOpenSqlWorkspace: + widget.onPostgresOpenSqlWorkspace, + onRefresh: _loadObjects, + label: 'Views', + icon: material.Icons.view_agenda_rounded, + items: _views, + onItemTap: widget.onPostgresObjectSelected != null + ? (name) => widget.onPostgresObjectSelected!( + widget.connection, + widget.databaseName, + widget.schemaName, + name, + PostgresObjectKind.view, + ) + : null, + ), + _PgObjectGroup( + connection: widget.connection, + databaseName: widget.databaseName, + schemaName: widget.schemaName, + objectKind: PostgresObjectKind.materializedView, + onPostgresOpenSqlWorkspace: + widget.onPostgresOpenSqlWorkspace, + onRefresh: _loadObjects, + label: 'Materialized views', + icon: material.Icons.dynamic_feed_rounded, + items: _matviews, + onItemTap: widget.onPostgresObjectSelected != null + ? (name) => widget.onPostgresObjectSelected!( + widget.connection, + widget.databaseName, + widget.schemaName, + name, + PostgresObjectKind.materializedView, + ) + : null, + ), + _PgObjectGroup( + connection: widget.connection, + databaseName: widget.databaseName, + schemaName: widget.schemaName, + objectKind: PostgresObjectKind.function, + onPostgresOpenSqlWorkspace: + widget.onPostgresOpenSqlWorkspace, + onRefresh: _loadObjects, + label: 'Functions', + icon: material.Icons.functions_rounded, + items: _functions, + onItemTap: widget.onPostgresObjectSelected != null + ? (name) => widget.onPostgresObjectSelected!( + widget.connection, + widget.databaseName, + widget.schemaName, + name, + PostgresObjectKind.function, + ) + : null, + ), + _PgObjectGroup( + connection: widget.connection, + databaseName: widget.databaseName, + schemaName: widget.schemaName, + objectKind: PostgresObjectKind.sequence, + onPostgresOpenSqlWorkspace: + widget.onPostgresOpenSqlWorkspace, + onRefresh: _loadObjects, + label: 'Sequences', + icon: material.Icons.format_list_numbered_rounded, + items: _sequences, + onItemTap: widget.onPostgresObjectSelected != null + ? (name) => widget.onPostgresObjectSelected!( + widget.connection, + widget.databaseName, + widget.schemaName, + name, + PostgresObjectKind.sequence, + ) + : null, + ), + _PgSchemaToolRow( + connection: widget.connection, + databaseName: widget.databaseName, + schemaName: widget.schemaName, + label: 'Indexes', + icon: material.Icons.table_rows_rounded, + kind: PostgresObjectKind.schemaIndexes, + onPostgresObjectSelected: widget.onPostgresObjectSelected, + onPostgresOpenSqlWorkspace: + widget.onPostgresOpenSqlWorkspace, + onContextRefresh: _loadObjects, + ), + _PgSchemaToolRow( + connection: widget.connection, + databaseName: widget.databaseName, + schemaName: widget.schemaName, + label: 'Triggers', + icon: material.Icons.bolt_rounded, + kind: PostgresObjectKind.schemaTriggers, + onPostgresObjectSelected: widget.onPostgresObjectSelected, + onPostgresOpenSqlWorkspace: + widget.onPostgresOpenSqlWorkspace, + onContextRefresh: _loadObjects, + ), + _PgSchemaToolRow( + connection: widget.connection, + databaseName: widget.databaseName, + schemaName: widget.schemaName, + label: 'Types', + icon: material.Icons.category_rounded, + kind: PostgresObjectKind.schemaTypes, + onPostgresObjectSelected: widget.onPostgresObjectSelected, + onPostgresOpenSqlWorkspace: + widget.onPostgresOpenSqlWorkspace, + onContextRefresh: _loadObjects, + ), + ], + ], ), ), ], @@ -997,8 +1006,7 @@ class _PgObjectGroupState extends State<_PgObjectGroup> { label: item, icon: widget.icon, iconSize: 12, - iconColor: - theme.colorScheme.primary.withValues(alpha: 0.5), + iconColor: theme.colorScheme.primary.withValues(alpha: 0.5), textStyle: material.TextStyle( fontSize: 11, color: theme.colorScheme.foreground, diff --git a/lib/features/connections/connections_panel_postgres_connection.dart b/lib/features/connections/connections_panel_postgres_connection.dart index 4509361f..925849a8 100644 --- a/lib/features/connections/connections_panel_postgres_connection.dart +++ b/lib/features/connections/connections_panel_postgres_connection.dart @@ -94,7 +94,8 @@ class _PostgresConnectionTileState extends State<_PostgresConnectionTile> { color: theme.colorScheme.primary, ), ) - : material.Icon(widget.icon, size: 16, color: theme.colorScheme.primary); + : material.Icon(widget.icon, + size: 16, color: theme.colorScheme.primary); return ContextMenu( items: [ @@ -195,47 +196,48 @@ class _PostgresConnectionTileState extends State<_PostgresConnectionTile> { mainAxisSize: material.MainAxisSize.min, crossAxisAlignment: material.CrossAxisAlignment.stretch, children: [ - if (_loading) - material.Padding( - padding: - const material.EdgeInsets.only(left: 28, top: 4, bottom: 4), - child: material.Row( - children: [ - const material.SizedBox( - width: 12, - height: 12, - child: - material.CircularProgressIndicator(strokeWidth: 1.5), + if (_loading) + material.Padding( + padding: const material.EdgeInsets.only( + left: 28, top: 4, bottom: 4), + child: material.Row( + children: [ + const material.SizedBox( + width: 12, + height: 12, + child: material.CircularProgressIndicator( + strokeWidth: 1.5), + ), + const Gap(8), + const Text('Loading...').muted().xSmall(), + ], ), - const Gap(8), - const Text('Loading...').muted().xSmall(), - ], - ), - ), - if (_error != null) - material.Padding( - padding: - const material.EdgeInsets.only(left: 28, top: 4, bottom: 4), - child: material.Text( - 'Error', - overflow: material.TextOverflow.ellipsis, - maxLines: 1, - style: material.TextStyle( - fontSize: 11, color: theme.colorScheme.destructive), - ), - ), - if (_databases.isNotEmpty) - _PgDatabasesNode( - connection: widget.connection, - databases: _databases, - onPostgresObjectSelected: widget.onPostgresObjectSelected, - onPostgresOpenSqlWorkspace: widget.onPostgresOpenSqlWorkspace, - onRefreshDatabases: () { - setState(() => _databases = []); - _loadDatabases(); - }, - ), - ], + ), + if (_error != null) + material.Padding( + padding: const material.EdgeInsets.only( + left: 28, top: 4, bottom: 4), + child: material.Text( + 'Error', + overflow: material.TextOverflow.ellipsis, + maxLines: 1, + style: material.TextStyle( + fontSize: 11, color: theme.colorScheme.destructive), + ), + ), + if (_databases.isNotEmpty) + _PgDatabasesNode( + connection: widget.connection, + databases: _databases, + onPostgresObjectSelected: widget.onPostgresObjectSelected, + onPostgresOpenSqlWorkspace: + widget.onPostgresOpenSqlWorkspace, + onRefreshDatabases: () { + setState(() => _databases = []); + _loadDatabases(); + }, + ), + ], ), ), ], diff --git a/lib/features/connections/connections_panel_redis.dart b/lib/features/connections/connections_panel_redis.dart index 4245f5b6..efee18af 100644 --- a/lib/features/connections/connections_panel_redis.dart +++ b/lib/features/connections/connections_panel_redis.dart @@ -109,7 +109,8 @@ class _RedisConnectionTileState extends State<_RedisConnectionTile> { color: theme.colorScheme.primary, ), ) - : material.Icon(widget.icon, size: 16, color: theme.colorScheme.primary); + : material.Icon(widget.icon, + size: 16, color: theme.colorScheme.primary); return ContextMenu( items: [ @@ -214,39 +215,42 @@ class _RedisConnectionTileState extends State<_RedisConnectionTile> { mainAxisSize: material.MainAxisSize.min, crossAxisAlignment: material.CrossAxisAlignment.stretch, children: [ - if (_loading) - material.Padding( - padding: const material.EdgeInsets.only(left: 28, top: 4, bottom: 4), - child: material.Row( - children: [ - const material.SizedBox( - width: 12, - height: 12, - child: material.CircularProgressIndicator(strokeWidth: 1.5), + if (_loading) + material.Padding( + padding: const material.EdgeInsets.only( + left: 28, top: 4, bottom: 4), + child: material.Row( + children: [ + const material.SizedBox( + width: 12, + height: 12, + child: material.CircularProgressIndicator( + strokeWidth: 1.5), + ), + const Gap(8), + const Text('Loading...').muted().xSmall(), + ], ), - const Gap(8), - const Text('Loading...').muted().xSmall(), - ], - ), - ), - if (_error != null) - material.Padding( - padding: const material.EdgeInsets.only(left: 28, top: 4, bottom: 4), - child: material.Text( - 'Error', - overflow: material.TextOverflow.ellipsis, - maxLines: 1, - style: material.TextStyle( - fontSize: 11, color: theme.colorScheme.destructive), - ), - ), - for (final db in _databases) - _RedisDatabaseNode( - index: db.index, - keys: db.keys, - onTap: () => widget.onDatabaseTap?.call(db.index), - ), - ], + ), + if (_error != null) + material.Padding( + padding: const material.EdgeInsets.only( + left: 28, top: 4, bottom: 4), + child: material.Text( + 'Error', + overflow: material.TextOverflow.ellipsis, + maxLines: 1, + style: material.TextStyle( + fontSize: 11, color: theme.colorScheme.destructive), + ), + ), + for (final db in _databases) + _RedisDatabaseNode( + index: db.index, + keys: db.keys, + onTap: () => widget.onDatabaseTap?.call(db.index), + ), + ], ), ), ], @@ -278,8 +282,8 @@ class _RedisDatabaseNode extends StatelessWidget { onTap: onTap, borderRadius: material.BorderRadius.circular(6), child: material.Padding( - padding: const material.EdgeInsets.symmetric( - horizontal: 8, vertical: 5), + padding: + const material.EdgeInsets.symmetric(horizontal: 8, vertical: 5), child: material.Row( children: [ material.Icon( @@ -287,7 +291,8 @@ class _RedisDatabaseNode extends StatelessWidget { size: 14, color: keys > 0 ? theme.colorScheme.primary.withValues(alpha: 0.7) - : theme.colorScheme.mutedForeground.withValues(alpha: 0.5), + : theme.colorScheme.mutedForeground + .withValues(alpha: 0.5), ), const Gap(8), material.Expanded( @@ -307,8 +312,7 @@ class _RedisDatabaseNode extends StatelessWidget { material.Text( '$keys', style: material.TextStyle( - fontSize: 10, - color: theme.colorScheme.mutedForeground), + fontSize: 10, color: theme.colorScheme.mutedForeground), ), ], ), diff --git a/lib/features/connections/connections_panel_sidebar.dart b/lib/features/connections/connections_panel_sidebar.dart index 2e98ec37..d054ee57 100644 --- a/lib/features/connections/connections_panel_sidebar.dart +++ b/lib/features/connections/connections_panel_sidebar.dart @@ -28,6 +28,7 @@ material.Widget _sidebarConnectionShell({ ), ); } + class _EmptyState extends StatelessWidget { const _EmptyState({required this.message}); @@ -37,7 +38,8 @@ class _EmptyState extends StatelessWidget { Widget build(BuildContext context) { final theme = Theme.of(context); return material.Container( - padding: const material.EdgeInsets.symmetric(horizontal: 12, vertical: 14), + padding: + const material.EdgeInsets.symmetric(horizontal: 12, vertical: 14), decoration: material.BoxDecoration( color: theme.colorScheme.muted.withValues(alpha: 0.25), borderRadius: material.BorderRadius.circular(8), @@ -102,7 +104,8 @@ class _ConnectionTile extends StatelessWidget { return ContextMenu( items: [ MenuButton( - leading: material.Icon(material.Icons.delete_outline_rounded, size: 18, color: theme.colorScheme.mutedForeground), + leading: material.Icon(material.Icons.delete_outline_rounded, + size: 18, color: theme.colorScheme.mutedForeground), onPressed: (_) => onRemove(), child: const Text('Remove connection'), ), @@ -181,8 +184,10 @@ class _FolderTile extends StatefulWidget { final material.IconData Function(String type) iconForType; final Future Function(int id) onRemoveConnection; final void Function(ConnectionRow connection)? onConnectionTap; - final void Function(ConnectionRow connection, int database)? onRedisDatabaseTap; - final void Function(ConnectionRow connection, String database)? onMongoDBDatabaseTap; + final void Function(ConnectionRow connection, int database)? + onRedisDatabaseTap; + final void Function(ConnectionRow connection, String database)? + onMongoDBDatabaseTap; final Widget Function(ConnectionRow conn)? buildConnectionTile; @override @@ -218,12 +223,14 @@ class _FolderTileState extends State<_FolderTile> { return ContextMenu( items: [ MenuButton( - leading: material.Icon(material.Icons.settings_ethernet_rounded, size: 18, color: theme.colorScheme.mutedForeground), + leading: material.Icon(material.Icons.settings_ethernet_rounded, + size: 18, color: theme.colorScheme.mutedForeground), onPressed: (menuContext) => widget.onNewConnection(widget.name), child: const Text('New connection'), ), MenuButton( - leading: material.Icon(material.Icons.delete_outline_rounded, size: 18, color: theme.colorScheme.mutedForeground), + leading: material.Icon(material.Icons.delete_outline_rounded, + size: 18, color: theme.colorScheme.mutedForeground), onPressed: (_) => widget.onRemove(), child: const Text('Remove folder'), ), @@ -240,7 +247,8 @@ class _FolderTileState extends State<_FolderTile> { onTap: _toggle, borderRadius: material.BorderRadius.circular(6), child: material.Padding( - padding: const material.EdgeInsets.symmetric(horizontal: 8, vertical: 6), + padding: const material.EdgeInsets.symmetric( + horizontal: 8, vertical: 6), child: material.Row( children: [ material.AnimatedRotation( @@ -254,7 +262,8 @@ class _FolderTileState extends State<_FolderTile> { ), ), const Gap(2), - material.Icon(material.Icons.folder_rounded, size: 18, color: theme.colorScheme.primary), + material.Icon(material.Icons.folder_rounded, + size: 18, color: theme.colorScheme.primary), const Gap(8), material.Expanded( child: material.Text( diff --git a/lib/features/connections/driver_manager_dialog.dart b/lib/features/connections/driver_manager_dialog.dart index b3e18e19..0401e6ba 100644 --- a/lib/features/connections/driver_manager_dialog.dart +++ b/lib/features/connections/driver_manager_dialog.dart @@ -81,12 +81,14 @@ class _DriverManagerDialogContent extends material.StatelessWidget { ), ), material.Padding( - padding: const material.EdgeInsets.symmetric(horizontal: 24, vertical: 12), + padding: const material.EdgeInsets.symmetric( + horizontal: 24, vertical: 12), child: material.Container( decoration: material.BoxDecoration( color: theme.muted.withValues(alpha: 0.15), borderRadius: material.BorderRadius.circular(10), - border: material.Border.all(color: theme.border.withValues(alpha: 0.3)), + border: material.Border.all( + color: theme.border.withValues(alpha: 0.3)), ), child: material.ListView.separated( shrinkWrap: true, @@ -108,10 +110,12 @@ class _DriverManagerDialogContent extends material.StatelessWidget { ), ), material.Container( - padding: const material.EdgeInsets.symmetric(horizontal: 24, vertical: 16), + padding: const material.EdgeInsets.symmetric( + horizontal: 24, vertical: 16), decoration: material.BoxDecoration( border: material.Border( - top: material.BorderSide(color: theme.border.withValues(alpha: 0.3)), + top: material.BorderSide( + color: theme.border.withValues(alpha: 0.3)), ), ), child: material.Row( @@ -145,7 +149,8 @@ class _DriverRow extends material.StatelessWidget { @override material.Widget build(material.BuildContext context) { return material.Padding( - padding: const material.EdgeInsets.symmetric(horizontal: 16, vertical: 12), + padding: + const material.EdgeInsets.symmetric(horizontal: 16, vertical: 12), child: material.Row( crossAxisAlignment: material.CrossAxisAlignment.center, children: [ @@ -174,7 +179,8 @@ class _DriverRow extends material.StatelessWidget { ), const material.SizedBox(width: 8), material.Container( - padding: const material.EdgeInsets.symmetric(horizontal: 10, vertical: 6), + padding: const material.EdgeInsets.symmetric( + horizontal: 10, vertical: 6), decoration: material.BoxDecoration( color: theme.primary.withValues(alpha: 0.12), borderRadius: material.BorderRadius.circular(6), diff --git a/lib/features/connections/new_connection_dialog.dart b/lib/features/connections/new_connection_dialog.dart index 1397c669..76c8931a 100644 --- a/lib/features/connections/new_connection_dialog.dart +++ b/lib/features/connections/new_connection_dialog.dart @@ -27,6 +27,7 @@ extension ConnectionTypeX on ConnectionType { ConnectionType.redis => material.Icons.memory_rounded, ConnectionType.mongodb => material.Icons.eco_rounded, }; + /// Asset path for custom icon (from Downloads). String? get iconAsset => switch (this) { ConnectionType.postgresql => 'assets/images/postgresql_icon.png', @@ -34,12 +35,18 @@ extension ConnectionTypeX on ConnectionType { ConnectionType.redis => 'assets/images/redis_icon.png', ConnectionType.mongodb => 'assets/images/mongodb_icon.png', }; - bool get isSql => this == ConnectionType.postgresql || this == ConnectionType.mysql; + bool get isSql => + this == ConnectionType.postgresql || this == ConnectionType.mysql; } const _sqlTypes = [ConnectionType.postgresql, ConnectionType.mysql]; const _noSqlTypes = [ConnectionType.redis, ConnectionType.mongodb]; -const _allTypes = [ConnectionType.postgresql, ConnectionType.mysql, ConnectionType.redis, ConnectionType.mongodb]; +const _allTypes = [ + ConnectionType.postgresql, + ConnectionType.mysql, + ConnectionType.redis, + ConnectionType.mongodb +]; enum _Category { all, sql, nosql } @@ -60,10 +67,12 @@ class _NewConnectionDialogContent extends material.StatefulWidget { const _NewConnectionDialogContent(); @override - material.State<_NewConnectionDialogContent> createState() => _NewConnectionDialogContentState(); + material.State<_NewConnectionDialogContent> createState() => + _NewConnectionDialogContentState(); } -class _NewConnectionDialogContentState extends material.State<_NewConnectionDialogContent> { +class _NewConnectionDialogContentState + extends material.State<_NewConnectionDialogContent> { _Category _category = _Category.all; ConnectionType? _selectedType; final _searchController = material.TextEditingController(); @@ -84,7 +93,9 @@ class _NewConnectionDialogContentState extends material.State<_NewConnectionDial List get _filteredTypes { if (_searchQuery.trim().isEmpty) return _categoryTypes; final q = _searchQuery.trim().toLowerCase(); - return _categoryTypes.where((t) => t.label.toLowerCase().contains(q)).toList(); + return _categoryTypes + .where((t) => t.label.toLowerCase().contains(q)) + .toList(); } @override @@ -113,143 +124,154 @@ class _NewConnectionDialogContentState extends material.State<_NewConnectionDial child: material.SizedBox( height: dialogH, child: material.Column( - mainAxisSize: material.MainAxisSize.min, - crossAxisAlignment: material.CrossAxisAlignment.stretch, - children: [ - material.Padding( - padding: material.EdgeInsets.fromLTRB(headerPadH, 20, headerPadH, 8), - child: Column( - crossAxisAlignment: material.CrossAxisAlignment.stretch, - children: [ - const Text('Select your database').large().semiBold(), - const material.SizedBox(height: 6), - const Text( - 'Create new database connection. Find your database driver in the list below.', - ).muted().small(), - const material.SizedBox(height: 12), - material.Container( - decoration: material.BoxDecoration( - color: theme.muted.withValues(alpha: 0.2), - borderRadius: material.BorderRadius.circular(8), - border: material.Border.all(color: theme.border.withValues(alpha: 0.4)), - ), - padding: const material.EdgeInsets.symmetric(horizontal: 12, vertical: 4), - child: material.Row( - children: [ - material.Icon( - material.Icons.search_rounded, - size: 20, - color: theme.mutedForeground, - ), - const material.SizedBox(width: 10), - material.Expanded( - child: TextField( - controller: _searchController, - placeholder: const Text('Search...'), - onChanged: (v) => setState(() { - _searchQuery = v; - if (_selectedType != null && - !_filteredTypes.contains(_selectedType)) { - _selectedType = null; - } - }), + mainAxisSize: material.MainAxisSize.min, + crossAxisAlignment: material.CrossAxisAlignment.stretch, + children: [ + material.Padding( + padding: + material.EdgeInsets.fromLTRB(headerPadH, 20, headerPadH, 8), + child: Column( + crossAxisAlignment: material.CrossAxisAlignment.stretch, + children: [ + const Text('Select your database').large().semiBold(), + const material.SizedBox(height: 6), + const Text( + 'Create new database connection. Find your database driver in the list below.', + ).muted().small(), + const material.SizedBox(height: 12), + material.Container( + decoration: material.BoxDecoration( + color: theme.muted.withValues(alpha: 0.2), + borderRadius: material.BorderRadius.circular(8), + border: material.Border.all( + color: theme.border.withValues(alpha: 0.4)), + ), + padding: const material.EdgeInsets.symmetric( + horizontal: 12, vertical: 4), + child: material.Row( + children: [ + material.Icon( + material.Icons.search_rounded, + size: 20, + color: theme.mutedForeground, ), - ), - ], + const material.SizedBox(width: 10), + material.Expanded( + child: TextField( + controller: _searchController, + placeholder: const Text('Search...'), + onChanged: (v) => setState(() { + _searchQuery = v; + if (_selectedType != null && + !_filteredTypes.contains(_selectedType)) { + _selectedType = null; + } + }), + ), + ), + ], + ), ), - ), - const material.SizedBox(height: 12), - _FilterDropdowns( - stackVertically: stackFilters, - category: _category, - selectedType: _selectedType, - filteredTypes: _filteredTypes, - onCategoryChanged: (category) { - setState(() { - _category = category; - if (_selectedType != null && - !_categoryTypes.contains(_selectedType)) { - _selectedType = null; - } - }); - }, - onTypeChanged: (type) => setState(() => _selectedType = type), - ), - ], + const material.SizedBox(height: 12), + _FilterDropdowns( + stackVertically: stackFilters, + category: _category, + selectedType: _selectedType, + filteredTypes: _filteredTypes, + onCategoryChanged: (category) { + setState(() { + _category = category; + if (_selectedType != null && + !_categoryTypes.contains(_selectedType)) { + _selectedType = null; + } + }); + }, + onTypeChanged: (type) => + setState(() => _selectedType = type), + ), + ], + ), ), - ), - material.Expanded( - child: material.LayoutBuilder( - builder: (context, constraints) { - const spacing = 12.0; - final gridPad = dialogMaxW < 420 ? 12.0 : 16.0; - final innerW = math.max(0.0, constraints.maxWidth - gridPad * 2); - final crossAxisCount = - WindowLayout.dbTypeGridCrossAxisCount(innerW); + material.Expanded( + child: material.LayoutBuilder( + builder: (context, constraints) { + const spacing = 12.0; + final gridPad = dialogMaxW < 420 ? 12.0 : 16.0; + final innerW = + math.max(0.0, constraints.maxWidth - gridPad * 2); + final crossAxisCount = + WindowLayout.dbTypeGridCrossAxisCount(innerW); final cardHeight = WindowLayout.dbTypeCardHeight(context, crossAxisCount); - final cardWidth = crossAxisCount > 0 - ? (innerW - spacing * (crossAxisCount - 1)) / crossAxisCount - : innerW; - final aspect = cardHeight > 0 ? cardWidth / cardHeight : 1.0; - return material.Padding( - padding: material.EdgeInsets.all(gridPad), - child: _filteredTypes.isEmpty - ? material.Center( - child: const Text('No databases match your search.') - .muted() - .small(), - ) - : material.GridView.count( - crossAxisCount: crossAxisCount, - mainAxisSpacing: spacing, - crossAxisSpacing: spacing, - childAspectRatio: aspect.clamp(0.4, 4.0), - shrinkWrap: true, - physics: const material.ClampingScrollPhysics(), - children: [ - for (final t in _filteredTypes) - _DbTypeCard( - type: t, - theme: theme, - selected: _selectedType == t, - onTap: () => setState(() => _selectedType = t), - ), - ], - ), - ); - }, - ), - ), - material.Container( - padding: material.EdgeInsets.symmetric( - horizontal: headerPadH, - vertical: 14, - ), - decoration: material.BoxDecoration( - border: material.Border( - top: material.BorderSide(color: theme.border.withValues(alpha: 0.3)), + final cardWidth = crossAxisCount > 0 + ? (innerW - spacing * (crossAxisCount - 1)) / + crossAxisCount + : innerW; + final aspect = + cardHeight > 0 ? cardWidth / cardHeight : 1.0; + return material.Padding( + padding: material.EdgeInsets.all(gridPad), + child: _filteredTypes.isEmpty + ? material.Center( + child: + const Text('No databases match your search.') + .muted() + .small(), + ) + : material.GridView.count( + crossAxisCount: crossAxisCount, + mainAxisSpacing: spacing, + crossAxisSpacing: spacing, + childAspectRatio: aspect.clamp(0.4, 4.0), + shrinkWrap: true, + physics: const material.ClampingScrollPhysics(), + children: [ + for (final t in _filteredTypes) + _DbTypeCard( + type: t, + theme: theme, + selected: _selectedType == t, + onTap: () => + setState(() => _selectedType = t), + ), + ], + ), + ); + }, ), ), - child: material.Row( - mainAxisAlignment: material.MainAxisAlignment.end, - children: [ - GhostButton( - onPressed: () => material.Navigator.of(context).pop(), - child: const Text('Cancel'), - ), - const material.SizedBox(width: 12), - PrimaryButton( - onPressed: _selectedType == null - ? null - : () => material.Navigator.of(context).pop(_selectedType), - child: const Text('Next'), + material.Container( + padding: material.EdgeInsets.symmetric( + horizontal: headerPadH, + vertical: 14, + ), + decoration: material.BoxDecoration( + border: material.Border( + top: material.BorderSide( + color: theme.border.withValues(alpha: 0.3)), ), - ], + ), + child: material.Row( + mainAxisAlignment: material.MainAxisAlignment.end, + children: [ + GhostButton( + onPressed: () => material.Navigator.of(context).pop(), + child: const Text('Cancel'), + ), + const material.SizedBox(width: 12), + PrimaryButton( + onPressed: _selectedType == null + ? null + : () => + material.Navigator.of(context).pop(_selectedType), + child: const Text('Next'), + ), + ], + ), ), - ), - ], - ), + ], + ), ), ), ); @@ -387,12 +409,17 @@ class _DbTypeCardState extends material.State<_DbTypeCard> { child: material.AnimatedContainer( duration: context.motionDuration(QueryaMotion.fast), curve: context.motionCurve(QueryaMotion.enter), - padding: const material.EdgeInsets.symmetric(vertical: 10, horizontal: 8), + padding: + const material.EdgeInsets.symmetric(vertical: 10, horizontal: 8), decoration: material.BoxDecoration( - color: highlighted ? t.muted.withValues(alpha: 0.4) : t.muted.withValues(alpha: 0.12), + color: highlighted + ? t.muted.withValues(alpha: 0.4) + : t.muted.withValues(alpha: 0.12), borderRadius: material.BorderRadius.circular(10), border: material.Border.all( - color: widget.selected ? t.primary.withValues(alpha: 0.6) : t.border.withValues(alpha: 0.35), + color: widget.selected + ? t.primary.withValues(alpha: 0.6) + : t.border.withValues(alpha: 0.35), width: widget.selected ? 1.5 : 1, ), ), @@ -410,7 +437,8 @@ class _DbTypeCardState extends material.State<_DbTypeCard> { fit: material.BoxFit.contain, filterQuality: material.FilterQuality.medium, ) - : material.Icon(widget.type.icon, size: 52, color: t.primary), + : material.Icon(widget.type.icon, + size: 52, color: t.primary), ), ), ), diff --git a/lib/features/connections/new_folder_dialog.dart b/lib/features/connections/new_folder_dialog.dart index db316e78..f4c6a851 100644 --- a/lib/features/connections/new_folder_dialog.dart +++ b/lib/features/connections/new_folder_dialog.dart @@ -19,10 +19,12 @@ class _NewFolderDialogContent extends material.StatefulWidget { const _NewFolderDialogContent(); @override - material.State<_NewFolderDialogContent> createState() => _NewFolderDialogContentState(); + material.State<_NewFolderDialogContent> createState() => + _NewFolderDialogContentState(); } -class _NewFolderDialogContentState extends material.State<_NewFolderDialogContent> { +class _NewFolderDialogContentState + extends material.State<_NewFolderDialogContent> { final _nameController = material.TextEditingController(); String get _name => _nameController.text.trim(); @@ -68,9 +70,11 @@ class _NewFolderDialogContentState extends material.State<_NewFolderDialogConten decoration: material.BoxDecoration( color: theme.muted.withValues(alpha: 0.2), borderRadius: material.BorderRadius.circular(8), - border: material.Border.all(color: theme.border.withValues(alpha: 0.4)), + border: material.Border.all( + color: theme.border.withValues(alpha: 0.4)), ), - padding: const material.EdgeInsets.symmetric(horizontal: 12, vertical: 4), + padding: const material.EdgeInsets.symmetric( + horizontal: 12, vertical: 4), child: material.Row( children: [ material.Icon( @@ -93,10 +97,12 @@ class _NewFolderDialogContentState extends material.State<_NewFolderDialogConten ), ), material.Container( - padding: const material.EdgeInsets.symmetric(horizontal: 24, vertical: 16), + padding: const material.EdgeInsets.symmetric( + horizontal: 24, vertical: 16), decoration: material.BoxDecoration( border: material.Border( - top: material.BorderSide(color: theme.border.withValues(alpha: 0.3)), + top: material.BorderSide( + color: theme.border.withValues(alpha: 0.3)), ), ), child: material.Row( diff --git a/lib/features/main_screen/main_screen.dart b/lib/features/main_screen/main_screen.dart index 30bec54d..f4ad632e 100644 --- a/lib/features/main_screen/main_screen.dart +++ b/lib/features/main_screen/main_screen.dart @@ -267,8 +267,7 @@ class _MainContentSplitState extends State<_MainContentSplit> { selectedRedisDb: ws.activeRedisDb, selectedMongoDb: ws.activeMongoDB, selectedPostgresObject: ws.selectedPostgresObject, - postgresSqlTabRequestToken: - ws.postgresSqlTabRequestToken, + postgresSqlTabRequestToken: ws.postgresSqlTabRequestToken, postgresSqlEditorContext: ws.postgresSqlEditorContext, postgresSqlEditorContextToken: ws.postgresSqlEditorContextToken, diff --git a/lib/features/main_screen/main_screen_workspace_state.dart b/lib/features/main_screen/main_screen_workspace_state.dart index dd8bb418..4be397d9 100644 --- a/lib/features/main_screen/main_screen_workspace_state.dart +++ b/lib/features/main_screen/main_screen_workspace_state.dart @@ -21,16 +21,27 @@ class MainScreenWorkspaceState { final ConnectionRow? activeConnection; final int? activeRedisDb; final String? activeMongoDB; - final ({String database, String schema, String name, PostgresObjectKind kind})? - selectedPostgresObject; + final ({ + String database, + String schema, + String name, + PostgresObjectKind kind + })? selectedPostgresObject; final int postgresSqlTabRequestToken; /// Seeds the SQL editor when using "Open in SQL" (table/view/matview from current tree selection). - final ({String database, String schema, String name, PostgresObjectKind kind})? - postgresSqlEditorContext; + final ({ + String database, + String schema, + String name, + PostgresObjectKind kind + })? postgresSqlEditorContext; final int postgresSqlEditorContextToken; - final ({String database, String name, MysqlObjectKind kind})? - selectedMysqlObject; + final ({ + String database, + String name, + MysqlObjectKind kind + })? selectedMysqlObject; final int mysqlSqlTabRequestToken; static const empty = MainScreenWorkspaceState(); @@ -133,8 +144,12 @@ class MainScreenWorkspaceState { String? seedName, PostgresObjectKind? seedKind, }) { - ({String database, String schema, String name, PostgresObjectKind kind})? - seed; + ({ + String database, + String schema, + String name, + PostgresObjectKind kind + })? seed; final explicit = seedDatabase != null && seedSchema != null && diff --git a/lib/features/main_screen/query_editor_tab.dart b/lib/features/main_screen/query_editor_tab.dart index 10f8f7b7..1a0f78a9 100644 --- a/lib/features/main_screen/query_editor_tab.dart +++ b/lib/features/main_screen/query_editor_tab.dart @@ -1,4 +1,5 @@ -import 'package:flutter/material.dart' as material show EdgeInsets, Padding, TextEditingController; +import 'package:flutter/material.dart' as material + show EdgeInsets, Padding, TextEditingController; import 'package:querya_desktop/core/editor/querya_code_editor.dart'; import 'package:querya_desktop/core/editor/querya_code_language.dart'; import 'package:querya_desktop/features/main_screen/sql_editor_chrome.dart'; diff --git a/lib/features/main_screen/querya_window_title_bar.dart b/lib/features/main_screen/querya_window_title_bar.dart index e471749a..1d957b88 100644 --- a/lib/features/main_screen/querya_window_title_bar.dart +++ b/lib/features/main_screen/querya_window_title_bar.dart @@ -1,12 +1,6 @@ import 'package:bitsdojo_window/bitsdojo_window.dart'; import 'package:flutter/material.dart' as material - show - BuildContext, - Container, - Icon, - Icons, - MainAxisSize, - Widget; + show BuildContext, Container, Icon, Icons, MainAxisSize, Widget; import 'package:querya_desktop/core/theme/querya_theme_scope.dart'; import 'package:querya_desktop/features/connections/driver_manager_dialog.dart'; import 'package:querya_desktop/features/settings/preferences_dialog.dart'; @@ -111,7 +105,8 @@ class QueryaWindowTitleBar extends StatelessWidget { subMenu: [ MenuButton( leading: const material.Icon( - material.Icons.add_link_rounded, size: 18), + material.Icons.add_link_rounded, + size: 18), trailing: const Text('Shift+Ctrl+N').xSmall().muted(), onPressed: (_) => onNewDatabaseConnection(), @@ -119,13 +114,15 @@ class QueryaWindowTitleBar extends StatelessWidget { ), MenuButton( leading: const material.Icon( - material.Icons.link_rounded, size: 18), + material.Icons.link_rounded, + size: 18), onPressed: (_) {}, child: const Text('New Connection from URL'), ), MenuButton( leading: const material.Icon( - material.Icons.settings_rounded, size: 18), + material.Icons.settings_rounded, + size: 18), onPressed: (ctx) => showDriverManagerDialog(ctx), child: const Text('Driver Manager'), ), @@ -133,19 +130,22 @@ class QueryaWindowTitleBar extends StatelessWidget { MenuButton( enabled: false, leading: const material.Icon( - material.Icons.power_rounded, size: 18), + material.Icons.power_rounded, + size: 18), onPressed: (_) {}, child: const Text('Connect'), ), MenuButton( leading: const material.Icon( - material.Icons.refresh_rounded, size: 18), + material.Icons.refresh_rounded, + size: 18), onPressed: (_) {}, child: const Text('Invalidate/Reconnect'), ), MenuButton( leading: const material.Icon( - material.Icons.power_off_rounded, size: 18), + material.Icons.power_off_rounded, + size: 18), onPressed: (_) {}, child: const Text('Disconnect'), ), diff --git a/lib/features/main_screen/result_grid_view.dart b/lib/features/main_screen/result_grid_view.dart index eedeba8a..962027a1 100644 --- a/lib/features/main_screen/result_grid_view.dart +++ b/lib/features/main_screen/result_grid_view.dart @@ -72,8 +72,7 @@ class _VirtualResultGridState extends material.State { @override void didUpdateWidget(VirtualResultGrid oldWidget) { super.didUpdateWidget(oldWidget); - if (oldWidget.columns != widget.columns || - oldWidget.rows != widget.rows) { + if (oldWidget.columns != widget.columns || oldWidget.rows != widget.rows) { _widthsNeedUpdate = true; } } diff --git a/lib/features/main_screen/sql_query_history_dialog.dart b/lib/features/main_screen/sql_query_history_dialog.dart index 2a8aa146..98952a6c 100644 --- a/lib/features/main_screen/sql_query_history_dialog.dart +++ b/lib/features/main_screen/sql_query_history_dialog.dart @@ -152,8 +152,7 @@ class _SqlQueryHistoryDialogContentState const material.SizedBox(height: 4), const Text( 'Successful runs from this workspace (newest first).', - ).muted() - .small(), + ).muted().small(), ], ), ), @@ -183,8 +182,7 @@ class _SqlQueryHistoryDialogContentState return material.Center( child: const Text( 'No queries yet. Run SQL to build history.', - ).muted() - .small(), + ).muted().small(), ); } return material.Scrollbar( diff --git a/lib/features/main_screen/workspace_empty_hero.dart b/lib/features/main_screen/workspace_empty_hero.dart index dc89ffca..63e65049 100644 --- a/lib/features/main_screen/workspace_empty_hero.dart +++ b/lib/features/main_screen/workspace_empty_hero.dart @@ -29,7 +29,8 @@ class WorkspaceEmptyHero extends StatelessWidget { final mockH = WindowLayout.heroMockWindowHeight(maxContent); final compact = vw < WindowLayout.compactWindowWidth; return material.SingleChildScrollView( - padding: material.EdgeInsets.symmetric(horizontal: padH, vertical: padV), + padding: + material.EdgeInsets.symmetric(horizontal: padH, vertical: padV), child: material.Align( alignment: material.Alignment.topCenter, child: material.ConstrainedBox( diff --git a/lib/features/main_screen/workspace_panel.dart b/lib/features/main_screen/workspace_panel.dart index 1ec7e518..e0cd83b8 100644 --- a/lib/features/main_screen/workspace_panel.dart +++ b/lib/features/main_screen/workspace_panel.dart @@ -1,4 +1,25 @@ -import 'package:flutter/material.dart' as material show Alignment, Axis, Container, EdgeInsets, BoxDecoration, GestureDetector, Padding, BorderRadius, Center, Icon, Icons, MouseRegion, AnimatedContainer, AnimatedScale, SystemMouseCursors, SizedBox, SingleChildScrollView, Row, MainAxisSize; +import 'package:flutter/material.dart' as material + show + Alignment, + Axis, + Container, + EdgeInsets, + BoxDecoration, + GestureDetector, + Padding, + BorderRadius, + Center, + Icon, + Icons, + MouseRegion, + AnimatedContainer, + AnimatedScale, + SystemMouseCursors, + SizedBox, + SingleChildScrollView, + Row, + MainAxisSize, + Widget; 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'; @@ -49,20 +70,31 @@ class WorkspacePanel extends StatefulWidget { /// When set, the user selected a PostgreSQL object in the sidebar tree. /// null = show stats, non-null = show table/grid or definition view. - final ({String database, String schema, String name, PostgresObjectKind kind})? - selectedPostgresObject; + final ({ + String database, + String schema, + String name, + PostgresObjectKind kind + })? selectedPostgresObject; /// Incremented by [MainScreen] to switch the PostgreSQL home view to the SQL tab. final int postgresSqlTabRequestToken; /// Seeds the SQL editor from the last table/view/matview tree selection. - final ({String database, String schema, String name, PostgresObjectKind kind})? - postgresSqlEditorContext; + final ({ + String database, + String schema, + String name, + PostgresObjectKind kind + })? postgresSqlEditorContext; final int postgresSqlEditorContextToken; /// When set, the user selected a MySQL table or view in the sidebar tree. - final ({String database, String name, MysqlObjectKind kind})? - selectedMysqlObject; + final ({ + String database, + String name, + MysqlObjectKind kind + })? selectedMysqlObject; /// Incremented by [MainScreen] to switch the MySQL home view to the SQL tab. final int mysqlSqlTabRequestToken; @@ -89,110 +121,85 @@ class _WorkspacePanelState extends State { Widget build(BuildContext context) { final theme = Theme.of(context); - if (widget.activeConnection == null) { + final activeConn = widget.activeConnection; + if (activeConn == null) { return material.Container( color: theme.colorScheme.background, alignment: material.Alignment.topCenter, child: WorkspaceEmptyHero( - onNewConnection: - widget.onRequestNewConnection ?? () {}, + onNewConnection: widget.onRequestNewConnection ?? () {}, ), ); } - // If a PostgreSQL connection is selected → stats, table/view, function, or sequence - if (widget.activeConnection != null && - widget.activeConnection!.type == 'postgresql') { - final pg = widget.selectedPostgresObject; - return material.Container( - color: theme.colorScheme.background, - child: material.SizedBox.expand( - child: pg == null - ? PostgresWorkspaceHome( - key: ValueKey('pg_home_${widget.activeConnection!.id}'), - connectionRow: widget.activeConnection!, - postgresSqlEditorContext: widget.postgresSqlEditorContext, - postgresSqlEditorContextToken: - widget.postgresSqlEditorContextToken, - sqlTabRequestToken: widget.postgresSqlTabRequestToken, - ) - : buildPostgresObjectWorkspace( - connection: widget.activeConnection!, - pg: pg, - ), - ), - ); - } - - // MySQL / MariaDB - if (widget.activeConnection != null && - widget.activeConnection!.type == 'mysql') { - final my = widget.selectedMysqlObject; - return material.Container( - color: theme.colorScheme.background, - child: material.SizedBox.expand( - child: my == null - ? MysqlWorkspaceHome( - key: ValueKey('mysql_home_${widget.activeConnection!.id}'), - connectionRow: widget.activeConnection!, - sqlTabRequestToken: widget.mysqlSqlTabRequestToken, - ) - : MysqlTableView( - key: ValueKey( - 'mysql_${widget.activeConnection!.id}_${my.database}_${my.name}_${my.kind}', - ), - connectionRow: widget.activeConnection!, - database: my.database, - tableName: my.name, - isView: my.kind == MysqlObjectKind.view, - ), - ), - ); - } - - // If a MongoDB connection is selected - if (widget.activeConnection != null && - widget.activeConnection!.type == 'mongodb') { - final mongoDb = widget.selectedMongoDb; - return material.Container( - color: theme.colorScheme.background, - child: material.SizedBox.expand( - // DB selected → data explorer; no DB → stats - child: mongoDb != null - ? MongoExplorerView( - key: ValueKey( - 'mongo_${widget.activeConnection!.id}_db_$mongoDb'), - connectionRow: widget.activeConnection!, - database: mongoDb, - ) - : MongoStatsView( - key: ValueKey(widget.activeConnection!.id), - connectionRow: widget.activeConnection!, + material.Widget? driverWorkspace; + switch (activeConn.type) { + case 'postgresql': + final pg = widget.selectedPostgresObject; + driverWorkspace = pg == null + ? PostgresWorkspaceHome( + key: ValueKey('pg_home_${activeConn.id}'), + connectionRow: activeConn, + postgresSqlEditorContext: widget.postgresSqlEditorContext, + postgresSqlEditorContextToken: + widget.postgresSqlEditorContextToken, + sqlTabRequestToken: widget.postgresSqlTabRequestToken, + ) + : buildPostgresObjectWorkspace( + connection: activeConn, + pg: pg, + ); + break; + case 'mysql': + final my = widget.selectedMysqlObject; + driverWorkspace = my == null + ? MysqlWorkspaceHome( + key: ValueKey('mysql_home_${activeConn.id}'), + connectionRow: activeConn, + sqlTabRequestToken: widget.mysqlSqlTabRequestToken, + ) + : MysqlTableView( + key: ValueKey( + 'mysql_${activeConn.id}_${my.database}_${my.name}_${my.kind}', ), - ), - ); + connectionRow: activeConn, + database: my.database, + tableName: my.name, + isView: my.kind == MysqlObjectKind.view, + ); + break; + case 'mongodb': + final mongoDb = widget.selectedMongoDb; + driverWorkspace = mongoDb != null + ? MongoExplorerView( + key: ValueKey('mongo_${activeConn.id}_db_$mongoDb'), + connectionRow: activeConn, + database: mongoDb, + ) + : MongoStatsView( + key: ValueKey(activeConn.id), + connectionRow: activeConn, + ); + break; + case 'redis': + final redisDb = widget.selectedRedisDb; + driverWorkspace = redisDb != null + ? RedisExplorerView( + key: ValueKey('redis_${activeConn.id}_db_$redisDb'), + connectionRow: activeConn, + database: redisDb, + ) + : RedisView( + key: ValueKey(activeConn.id), + connectionRow: activeConn, + ); + break; } - // If a Redis connection is selected - if (widget.activeConnection != null && - widget.activeConnection!.type == 'redis') { - final redisDb = widget.selectedRedisDb; + if (driverWorkspace != null) { return material.Container( color: theme.colorScheme.background, - child: material.SizedBox.expand( - // DB selected → data explorer; no DB → stats - child: redisDb != null - ? RedisExplorerView( - key: ValueKey( - 'redis_${widget.activeConnection!.id}_db_$redisDb'), - connectionRow: widget.activeConnection!, - database: redisDb, - ) - : RedisView( - key: ValueKey(widget.activeConnection!.id), - connectionRow: widget.activeConnection!, - ), - ), + child: material.SizedBox.expand(child: driverWorkspace), ); } @@ -346,7 +353,8 @@ class _TabButtonState extends State<_TabButton> { child: material.AnimatedContainer( duration: context.motionDuration(QueryaMotion.fast), curve: context.motionCurve(QueryaMotion.enter), - padding: const material.EdgeInsets.symmetric(horizontal: 12, vertical: 8), + padding: const material.EdgeInsets.symmetric( + horizontal: 12, vertical: 8), decoration: material.BoxDecoration( color: bgColor, borderRadius: material.BorderRadius.circular(6), diff --git a/lib/features/mongodb/mongo_collections_view.dart b/lib/features/mongodb/mongo_collections_view.dart index 0bb4cb33..f41188b1 100644 --- a/lib/features/mongodb/mongo_collections_view.dart +++ b/lib/features/mongodb/mongo_collections_view.dart @@ -76,8 +76,7 @@ class _MongoCollectionsViewState extends material.State { _statsTotal = 0; }); try { - final names = - await widget.connection.listCollections(widget.database); + final names = await widget.connection.listCollections(widget.database); if (!mounted || gen != _loadGeneration) return; setState(() { @@ -112,7 +111,8 @@ class _MongoCollectionsViewState extends material.State { size: _toInt(stats['size']), ); } catch (_) { - return _CollectionInfo(name: name, documentCount: null, size: null); + return _CollectionInfo( + name: name, documentCount: null, size: null); } }), ); @@ -281,9 +281,7 @@ class _MongoCollectionsViewState extends material.State { padding: const material.EdgeInsets.only(top: 4), child: Text( 'Loading stats $_statsProgress / $_statsTotal…', - ) - .muted() - .xSmall(), + ).muted().xSmall(), ), ], ), @@ -299,8 +297,8 @@ class _MongoCollectionsViewState extends material.State { PrimaryButton( onPressed: _createCollection, size: ButtonSize.small, - leading: const material.Icon(material.Icons.add_rounded, - size: 16), + leading: + const material.Icon(material.Icons.add_rounded, size: 16), child: const Text('Create'), ), ], @@ -317,14 +315,12 @@ class _MongoCollectionsViewState extends material.State { children: [ const material.SizedBox(width: 80), material.Expanded( - child: - const Text('Collection Name').semiBold().xSmall()), + child: const Text('Collection Name').semiBold().xSmall()), material.SizedBox( width: 100, child: const Text('Documents').semiBold().xSmall()), material.SizedBox( - width: 100, - child: const Text('Size').semiBold().xSmall()), + width: 100, child: const Text('Size').semiBold().xSmall()), const material.SizedBox(width: 60), ], ), @@ -346,10 +342,9 @@ class _MongoCollectionsViewState extends material.State { return _CollectionRow( collection: _collections[i], colorScheme: cs, - onView: () => widget.onCollectionTap - ?.call(_collections[i].name), - onDrop: () => - _dropCollection(_collections[i].name), + onView: () => + widget.onCollectionTap?.call(_collections[i].name), + onDrop: () => _dropCollection(_collections[i].name), ); }, ), @@ -384,8 +379,8 @@ class _CollectionRow extends StatelessWidget { onTap: onView, hoverColor: cs.muted.withValues(alpha: 0.15), child: material.Padding( - padding: const material.EdgeInsets.symmetric( - horizontal: 20, vertical: 10), + padding: + const material.EdgeInsets.symmetric(horizontal: 20, vertical: 10), child: Row( children: [ _ActionButton( @@ -464,8 +459,8 @@ class _ActionButton extends StatelessWidget { onTap: onTap, borderRadius: material.BorderRadius.circular(6), child: material.Container( - padding: const material.EdgeInsets.symmetric( - horizontal: 12, vertical: 6), + padding: + const material.EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: material.BoxDecoration( color: color.withValues(alpha: 0.8), borderRadius: material.BorderRadius.circular(6), diff --git a/lib/features/mongodb/mongo_database_dialog.dart b/lib/features/mongodb/mongo_database_dialog.dart index 620de32f..af10e3d9 100644 --- a/lib/features/mongodb/mongo_database_dialog.dart +++ b/lib/features/mongodb/mongo_database_dialog.dart @@ -18,10 +18,12 @@ class _CreateMongoDBDialogContent extends material.StatefulWidget { const _CreateMongoDBDialogContent(); @override - material.State<_CreateMongoDBDialogContent> createState() => _CreateMongoDBDialogContentState(); + material.State<_CreateMongoDBDialogContent> createState() => + _CreateMongoDBDialogContentState(); } -class _CreateMongoDBDialogContentState extends material.State<_CreateMongoDBDialogContent> { +class _CreateMongoDBDialogContentState + extends material.State<_CreateMongoDBDialogContent> { final _nameController = material.TextEditingController(); @override @@ -71,13 +73,16 @@ class _CreateMongoDBDialogContentState extends material.State<_CreateMongoDBDial children: [ material.Row( children: [ - material.Icon(material.Icons.storage_rounded, size: 24, color: theme.primary), + material.Icon(material.Icons.storage_rounded, + size: 24, color: theme.primary), const Gap(12), const Text('Create Database').large().semiBold(), ], ), const Gap(8), - const Text('Enter the name for the new MongoDB database.').muted().small(), + const Text('Enter the name for the new MongoDB database.') + .muted() + .small(), ], ), ), @@ -98,7 +103,8 @@ class _CreateMongoDBDialogContentState extends material.State<_CreateMongoDBDial ), const material.Divider(height: 1), material.Container( - padding: const material.EdgeInsets.symmetric(horizontal: 24, vertical: 16), + padding: const material.EdgeInsets.symmetric( + horizontal: 24, vertical: 16), child: material.Row( mainAxisAlignment: material.MainAxisAlignment.end, children: [ diff --git a/lib/features/mongodb/mongo_databases_view.dart b/lib/features/mongodb/mongo_databases_view.dart index 087ac0dc..3c4ece47 100644 --- a/lib/features/mongodb/mongo_databases_view.dart +++ b/lib/features/mongodb/mongo_databases_view.dart @@ -79,7 +79,8 @@ class _MongoDatabasesViewState extends State { await conn.connect(); } } else { - conn = await MongoService.instance.ensureConnected(widget.connectionRow); + conn = + await MongoService.instance.ensureConnected(widget.connectionRow); } if (!mounted) return; _connection = conn; @@ -226,8 +227,8 @@ class _MongoDatabasesViewState extends State { const Gap(8), material.SelectableText( _error!, - style: material.TextStyle( - color: cs.mutedForeground, fontSize: 13), + style: + material.TextStyle(color: cs.mutedForeground, fontSize: 13), ), const Gap(24), OutlineButton( @@ -295,8 +296,8 @@ class _MongoDatabasesViewState extends State { PrimaryButton( onPressed: _createDatabase, size: ButtonSize.small, - leading: const material.Icon(material.Icons.add_rounded, - size: 16), + leading: + const material.Icon(material.Icons.add_rounded, size: 16), child: const Text('Create'), ), ], @@ -315,8 +316,7 @@ class _MongoDatabasesViewState extends State { material.Expanded( child: const Text('Database Name').semiBold().xSmall()), material.SizedBox( - width: 120, - child: const Text('Size').semiBold().xSmall()), + width: 120, child: const Text('Size').semiBold().xSmall()), const material.SizedBox(width: 60), ], ), @@ -324,9 +324,7 @@ class _MongoDatabasesViewState extends State { // Database rows for (var i = 0; i < _databases.length; i++) ...[ if (i > 0) - Divider( - height: 1, - color: cs.border.withValues(alpha: 0.15)), + Divider(height: 1, color: cs.border.withValues(alpha: 0.15)), _DatabaseRow( database: _databases[i], colorScheme: cs, @@ -371,8 +369,8 @@ class _DatabaseRow extends StatelessWidget { onTap: onView, hoverColor: cs.muted.withValues(alpha: 0.15), child: material.Padding( - padding: const material.EdgeInsets.symmetric( - horizontal: 20, vertical: 10), + padding: + const material.EdgeInsets.symmetric(horizontal: 20, vertical: 10), child: Row( children: [ _ActionButton( @@ -445,8 +443,8 @@ class _ActionButton extends StatelessWidget { onTap: onTap, borderRadius: material.BorderRadius.circular(6), child: material.Container( - padding: const material.EdgeInsets.symmetric( - horizontal: 12, vertical: 6), + padding: + const material.EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: material.BoxDecoration( color: color.withValues(alpha: 0.8), borderRadius: material.BorderRadius.circular(6), diff --git a/lib/features/mongodb/mongo_document_editor.dart b/lib/features/mongodb/mongo_document_editor.dart index 3c98336b..bba3ba4d 100644 --- a/lib/features/mongodb/mongo_document_editor.dart +++ b/lib/features/mongodb/mongo_document_editor.dart @@ -267,8 +267,8 @@ class _MongoDocumentEditorState extends material.State { ? const material.SizedBox( width: 14, height: 14, - child: material.CircularProgressIndicator( - strokeWidth: 2), + child: + material.CircularProgressIndicator(strokeWidth: 2), ) : const material.Icon(material.Icons.save_rounded, size: 14), @@ -322,8 +322,7 @@ class _MongoDocumentEditorState extends material.State { const Gap(8), material.Expanded( child: Text(_success!, - style: material.TextStyle( - color: success, fontSize: 12)), + style: material.TextStyle(color: success, fontSize: 12)), ), ], ), @@ -360,8 +359,8 @@ class _MongoDocumentEditorState extends material.State { const Spacer(), if (_dirty) Text('Modified', - style: material.TextStyle( - color: cs.primary, fontSize: 11)) + style: + material.TextStyle(color: cs.primary, fontSize: 11)) .xSmall(), ], ), diff --git a/lib/features/mongodb/mongo_documents_view.dart b/lib/features/mongodb/mongo_documents_view.dart index a5b98262..67c44444 100644 --- a/lib/features/mongodb/mongo_documents_view.dart +++ b/lib/features/mongodb/mongo_documents_view.dart @@ -230,8 +230,7 @@ class _MongoDocumentsViewState extends material.State { index: _skip + i, colorScheme: cs, shadcnCs: shadcnCs, - onView: () => - widget.onDocumentTap?.call(_documents[i]), + onView: () => widget.onDocumentTap?.call(_documents[i]), onDelete: () => _deleteDocument(_documents[i]), ); }, @@ -246,7 +245,8 @@ class _MongoDocumentsViewState extends material.State { Widget _buildFilterBar(ColorScheme cs) { final shadcnCs = shadcn.Theme.of(context).colorScheme; return material.Container( - padding: const material.EdgeInsets.symmetric(horizontal: 16, vertical: 10), + padding: + const material.EdgeInsets.symmetric(horizontal: 16, vertical: 10), decoration: material.BoxDecoration( color: shadcnCs.muted.withValues(alpha: 0.15), ), @@ -278,8 +278,7 @@ class _MongoDocumentsViewState extends material.State { PrimaryButton( onPressed: _addDocument, size: ButtonSize.small, - leading: - const material.Icon(material.Icons.add_rounded, size: 16), + leading: const material.Icon(material.Icons.add_rounded, size: 16), child: const Text('Add Document'), ), ], @@ -342,9 +341,8 @@ class _MongoDocumentsViewState extends material.State { child: material.Icon( material.Icons.chevron_left_rounded, size: 20, - color: _skip > 0 - ? shadcnCs.foreground - : shadcnCs.mutedForeground, + color: + _skip > 0 ? shadcnCs.foreground : shadcnCs.mutedForeground, ), ), ), diff --git a/lib/features/mongodb/mongo_explorer_view.dart b/lib/features/mongodb/mongo_explorer_view.dart index b7baea71..b14a5422 100644 --- a/lib/features/mongodb/mongo_explorer_view.dart +++ b/lib/features/mongodb/mongo_explorer_view.dart @@ -367,8 +367,8 @@ class _BreadcrumbBar extends StatelessWidget { for (var i = 0; i < crumbs.length; i++) ...[ if (i > 0) ...[ material.Padding( - padding: const material.EdgeInsets.symmetric( - horizontal: 6), + padding: + const material.EdgeInsets.symmetric(horizontal: 6), child: material.Icon( material.Icons.chevron_right_rounded, size: 16, @@ -444,7 +444,8 @@ class _CrumbChip extends material.StatelessWidget { if (onTap == null) { return material.Padding( - padding: const material.EdgeInsets.symmetric(horizontal: 8, vertical: 4), + padding: + const material.EdgeInsets.symmetric(horizontal: 8, vertical: 4), child: child, ); } diff --git a/lib/features/mongodb/mongo_stats_view.dart b/lib/features/mongodb/mongo_stats_view.dart index d8418cbf..923638b9 100644 --- a/lib/features/mongodb/mongo_stats_view.dart +++ b/lib/features/mongodb/mongo_stats_view.dart @@ -90,7 +90,8 @@ class _MongoStatsViewState extends material.State { if (supplied != null && supplied.isConnected) { conn = supplied; } else { - conn = await MongoService.instance.ensureConnected(widget.connectionRow); + conn = + await MongoService.instance.ensureConnected(widget.connectionRow); } if (!mounted) { return; @@ -211,15 +212,19 @@ class _MongoStatsViewState extends material.State { child: material.Column( mainAxisSize: material.MainAxisSize.min, children: [ - material.Icon(material.Icons.error_outline_rounded, size: 48, color: cs.destructive), + material.Icon(material.Icons.error_outline_rounded, + size: 48, color: cs.destructive), const Gap(16), const Text('Connection Error').large().semiBold(), const Gap(8), - material.SelectableText(err, style: material.TextStyle(color: cs.mutedForeground, fontSize: 13)), + material.SelectableText(err, + style: material.TextStyle( + color: cs.mutedForeground, fontSize: 13)), const Gap(24), OutlineButton( onPressed: _load, - leading: const material.Icon(material.Icons.refresh_rounded, size: 18), + leading: const material.Icon(material.Icons.refresh_rounded, + size: 18), child: const Text('Retry'), ), ], @@ -283,14 +288,16 @@ class _MongoStatsViewState extends material.State { ), const Gap(16), material.Expanded( - child: _sectionCard(context, 'Replication', replication), + child: + _sectionCard(context, 'Replication', replication), ), ], ); }, ), const Gap(12), - _sectionCard(context, 'WiredTiger', _extractWiredTigerInfo(status)), + _sectionCard( + context, 'WiredTiger', _extractWiredTigerInfo(status)), ], ), ), @@ -314,7 +321,8 @@ class _MongoStatsViewState extends material.State { color: cs.primary.withValues(alpha: 0.12), borderRadius: material.BorderRadius.circular(12), ), - child: material.Icon(material.Icons.eco_rounded, size: 28, color: cs.primary), + child: material.Icon(material.Icons.eco_rounded, + size: 28, color: cs.primary), ), const Gap(16), material.Expanded( @@ -346,8 +354,7 @@ class _MongoStatsViewState extends material.State { if (widget.onBack != null) OutlineButton( onPressed: widget.onBack, - leading: const material.Icon( - material.Icons.grid_view_rounded, + leading: const material.Icon(material.Icons.grid_view_rounded, size: 18), child: const Text('Explorer'), ), @@ -359,7 +366,8 @@ class _MongoStatsViewState extends material.State { height: 16, child: material.CircularProgressIndicator(strokeWidth: 2), ) - : const material.Icon(material.Icons.refresh_rounded, size: 18), + : const material.Icon(material.Icons.refresh_rounded, + size: 18), child: const Text('Refresh now'), ), material.SizedBox( @@ -368,11 +376,16 @@ class _MongoStatsViewState extends material.State { expandToParent: true, value: _autoRefreshInterval, items: const [ - QueryaDropdownItem(value: null, label: 'Auto: off'), - QueryaDropdownItem(value: Duration(seconds: 3), label: 'Auto: 3 s'), - QueryaDropdownItem(value: Duration(seconds: 10), label: 'Auto: 10 s'), - QueryaDropdownItem(value: Duration(seconds: 30), label: 'Auto: 30 s'), - QueryaDropdownItem(value: Duration(seconds: 60), label: 'Auto: 60 s'), + QueryaDropdownItem( + value: null, label: 'Auto: off'), + QueryaDropdownItem( + value: Duration(seconds: 3), label: 'Auto: 3 s'), + QueryaDropdownItem( + value: Duration(seconds: 10), label: 'Auto: 10 s'), + QueryaDropdownItem( + value: Duration(seconds: 30), label: 'Auto: 30 s'), + QueryaDropdownItem( + value: Duration(seconds: 60), label: 'Auto: 60 s'), ], onSelected: (value) { setState(() => _autoRefreshInterval = value); @@ -409,7 +422,8 @@ class _MongoStatsViewState extends material.State { return '${minutes}m'; } - material.Widget _summaryChips(material.BuildContext context, Map status) { + material.Widget _summaryChips( + material.BuildContext context, Map status) { final cs = shadcn.Theme.of(context).colorScheme; final version = _getString(status, 'version') ?? '—'; final uptime = _formatUptime(_getInt(status, 'uptime') ?? 0); @@ -419,13 +433,16 @@ class _MongoStatsViewState extends material.State { material.Widget chip(String label, String value, material.IconData icon) { return material.Expanded( child: material.ConstrainedBox( - constraints: const material.BoxConstraints(minHeight: _summaryChipHeight), + constraints: + const material.BoxConstraints(minHeight: _summaryChipHeight), child: material.Container( - padding: const material.EdgeInsets.symmetric(horizontal: 16, vertical: 12), + padding: const material.EdgeInsets.symmetric( + horizontal: 16, vertical: 12), decoration: material.BoxDecoration( color: cs.card, borderRadius: material.BorderRadius.circular(10), - border: material.Border.all(color: cs.border.withValues(alpha: 0.5)), + border: + material.Border.all(color: cs.border.withValues(alpha: 0.5)), ), child: material.Row( crossAxisAlignment: material.CrossAxisAlignment.start, @@ -463,6 +480,7 @@ class _MongoStatsViewState extends material.State { ), ); } + return material.Row( crossAxisAlignment: material.CrossAxisAlignment.start, children: [ @@ -470,7 +488,8 @@ class _MongoStatsViewState extends material.State { const Gap(12), chip('Uptime', uptime, material.Icons.schedule_rounded), const Gap(12), - chip('Connections', '$connections / $available', material.Icons.people_outline_rounded), + chip('Connections', '$connections / $available', + material.Icons.people_outline_rounded), const Gap(12), chip('Queries', '$ops', material.Icons.speed_rounded), ], @@ -526,7 +545,8 @@ class _MongoStatsViewState extends material.State { ); } - material.Widget _memoryCard(material.BuildContext context, Map status) { + material.Widget _memoryCard( + material.BuildContext context, Map status) { final mem = status['mem'] as Map?; final resident = _getInt(mem, 'resident') ?? 0; final virtual = _getInt(mem, 'virtual') ?? 0; @@ -541,7 +561,8 @@ class _MongoStatsViewState extends material.State { _metricRow(context, 'Resident', _formatBytes(resident)), _metricRow(context, 'Virtual', _formatBytes(virtual)), _metricRow(context, 'Mapped', _formatBytes(mapped)), - _metricRow(context, 'Mapped + Journal', _formatBytes(mappedWithJournal)), + _metricRow( + context, 'Mapped + Journal', _formatBytes(mappedWithJournal)), ], stretch: true, ), @@ -550,7 +571,8 @@ class _MongoStatsViewState extends material.State { ); } - material.Widget _operationsCard(material.BuildContext context, Map status) { + material.Widget _operationsCard( + material.BuildContext context, Map status) { final opcounters = status['opcounters'] as Map?; final inserts = _getInt(opcounters, 'insert') ?? 0; final queries = _getInt(opcounters, 'query') ?? 0; @@ -574,11 +596,13 @@ class _MongoStatsViewState extends material.State { ); } - material.Widget _connectionsCard(material.BuildContext context, Map status) { + material.Widget _connectionsCard( + material.BuildContext context, Map status) { final connections = status['connections'] as Map?; final current = _getInt(connections, 'current') ?? 0; final available = _getInt(connections, 'available') ?? 0; - final active = _getNestedInt(status, 'globalLock', 'activeClients', 'total') ?? 0; + final active = + _getNestedInt(status, 'globalLock', 'activeClients', 'total') ?? 0; return _card( context, 'Connections', @@ -596,7 +620,8 @@ class _MongoStatsViewState extends material.State { ); } - material.Widget _networkCard(material.BuildContext context, Map status) { + material.Widget _networkCard( + material.BuildContext context, Map status) { final network = status['network'] as Map?; final bytesIn = _getInt(network, 'bytesIn') ?? 0; final bytesOut = _getInt(network, 'bytesOut') ?? 0; @@ -618,7 +643,8 @@ class _MongoStatsViewState extends material.State { ); } - material.Widget _sectionCard(material.BuildContext context, String title, Map? data) { + material.Widget _sectionCard( + material.BuildContext context, String title, Map? data) { if (data == null || data.isEmpty) return const material.SizedBox.shrink(); return _card( context, @@ -637,7 +663,9 @@ class _MongoStatsViewState extends material.State { if (entries.length <= 4) { return material.Column( crossAxisAlignment: material.CrossAxisAlignment.stretch, - children: [for (final e in entries) _metricRow(context, e.key, e.value)], + children: [ + for (final e in entries) _metricRow(context, e.key, e.value) + ], ); } final mid = (entries.length / 2).ceil(); @@ -649,21 +677,26 @@ class _MongoStatsViewState extends material.State { material.Expanded( child: material.Column( crossAxisAlignment: material.CrossAxisAlignment.stretch, - children: [for (final e in left) _metricRow(context, e.key, e.value)], + children: [ + for (final e in left) _metricRow(context, e.key, e.value) + ], ), ), const Gap(24), material.Expanded( child: material.Column( crossAxisAlignment: material.CrossAxisAlignment.stretch, - children: [for (final e in right) _metricRow(context, e.key, e.value)], + children: [ + for (final e in right) _metricRow(context, e.key, e.value) + ], ), ), ], ); } - material.Widget _metricRow(material.BuildContext context, String label, String value) { + material.Widget _metricRow( + material.BuildContext context, String label, String value) { final cs = shadcn.Theme.of(context).colorScheme; return material.Padding( padding: const material.EdgeInsets.symmetric(vertical: 6), @@ -711,7 +744,8 @@ class _MongoStatsViewState extends material.State { return null; } - int? _getNestedInt(Map? map, String key1, String key2, [String? key3]) { + int? _getNestedInt(Map? map, String key1, String key2, + [String? key3]) { final m1 = map?[key1] as Map?; if (m1 == null) return null; if (key3 != null) { @@ -731,8 +765,10 @@ class _MongoStatsViewState extends material.State { Map _extractServerInfo(Map status) { final result = {}; if (status['host'] != null) result['Host'] = status['host'].toString(); - if (status['version'] != null) result['Version'] = status['version'].toString(); - if (status['process'] != null) result['Process'] = status['process'].toString(); + if (status['version'] != null) + result['Version'] = status['version'].toString(); + if (status['process'] != null) + result['Process'] = status['process'].toString(); final uptime = _getInt(status, 'uptime'); if (uptime != null) { result['Uptime'] = '${_formatUptime(uptime)} ($uptime s)'; @@ -755,9 +791,12 @@ class _MongoStatsViewState extends material.State { final result = {}; final repl = status['repl'] as Map?; if (repl != null) { - if (repl['setName'] != null) result['Replica set'] = repl['setName'].toString(); - if (repl['ismaster'] != null) result['Is master'] = repl['ismaster'].toString(); - if (repl['secondary'] != null) result['Secondary'] = repl['secondary'].toString(); + if (repl['setName'] != null) + result['Replica set'] = repl['setName'].toString(); + if (repl['ismaster'] != null) + result['Is master'] = repl['ismaster'].toString(); + if (repl['secondary'] != null) + result['Secondary'] = repl['secondary'].toString(); } return result; } @@ -780,7 +819,8 @@ class _MongoStatsViewState extends material.State { String _formatBytes(int bytes) { if (bytes < 1024) return '$bytes B'; if (bytes < 1024 * 1024) return '${(bytes / 1024).toStringAsFixed(1)} KB'; - if (bytes < 1024 * 1024 * 1024) return '${(bytes / (1024 * 1024)).toStringAsFixed(1)} MB'; + if (bytes < 1024 * 1024 * 1024) + return '${(bytes / (1024 * 1024)).toStringAsFixed(1)} MB'; return '${(bytes / (1024 * 1024 * 1024)).toStringAsFixed(1)} GB'; } } diff --git a/lib/features/mongodb/mongodb_connection_form.dart b/lib/features/mongodb/mongodb_connection_form.dart index ab5e0362..23566d10 100644 --- a/lib/features/mongodb/mongodb_connection_form.dart +++ b/lib/features/mongodb/mongodb_connection_form.dart @@ -61,10 +61,12 @@ class _MongoConnectionFormContent extends material.StatefulWidget { final int? folderId; @override - material.State<_MongoConnectionFormContent> createState() => _MongoConnectionFormContentState(); + material.State<_MongoConnectionFormContent> createState() => + _MongoConnectionFormContentState(); } -class _MongoConnectionFormContentState extends material.State<_MongoConnectionFormContent> { +class _MongoConnectionFormContentState + extends material.State<_MongoConnectionFormContent> { final _nameController = material.TextEditingController(); final _hostController = material.TextEditingController(text: 'localhost'); final _portController = material.TextEditingController(text: '27017'); @@ -124,10 +126,17 @@ class _MongoConnectionFormContentState extends material.State<_MongoConnectionFo name: _nameController.text.trim(), host: _hostController.text.trim(), port: int.tryParse(_portController.text.trim()) ?? 27017, - username: _usernameController.text.trim().isEmpty ? null : _usernameController.text.trim(), - password: _passwordController.text.isEmpty ? null : _passwordController.text, - database: _databaseController.text.trim().isEmpty ? null : _databaseController.text.trim(), - authSource: _authSourceController.text.trim().isEmpty ? null : _authSourceController.text.trim(), + username: _usernameController.text.trim().isEmpty + ? null + : _usernameController.text.trim(), + password: + _passwordController.text.isEmpty ? null : _passwordController.text, + database: _databaseController.text.trim().isEmpty + ? null + : _databaseController.text.trim(), + authSource: _authSourceController.text.trim().isEmpty + ? null + : _authSourceController.text.trim(), useSSL: _useSSL, connectionString: _connectionStringController.text.trim().isEmpty ? null @@ -187,9 +196,8 @@ class _MongoConnectionFormContentState extends material.State<_MongoConnectionFo final data = _formData; if (!data.isValid) return; - final displayName = data.name.isNotEmpty - ? data.name - : 'MongoDB ${data.host}:${data.port}'; + final displayName = + data.name.isNotEmpty ? data.name : 'MongoDB ${data.host}:${data.port}'; final row = ConnectionRow( type: 'mongodb', @@ -247,7 +255,9 @@ class _MongoConnectionFormContentState extends material.State<_MongoConnectionFo ], ), const Gap(8), - const Text('Configure your MongoDB connection settings').muted().small(), + const Text('Configure your MongoDB connection settings') + .muted() + .small(), ], ), ), @@ -278,7 +288,8 @@ class _MongoConnectionFormContentState extends material.State<_MongoConnectionFo const Gap(8), TextField( controller: _connectionStringController, - placeholder: const Text('mongodb://username:password@host:port/database'), + placeholder: const Text( + 'mongodb://username:password@host:port/database'), maxLines: 2, ), ] else ...[ @@ -296,7 +307,8 @@ class _MongoConnectionFormContentState extends material.State<_MongoConnectionFo material.Expanded( flex: 3, child: material.Column( - crossAxisAlignment: material.CrossAxisAlignment.stretch, + crossAxisAlignment: + material.CrossAxisAlignment.stretch, mainAxisSize: material.MainAxisSize.min, children: [ const Text('Host').small().semiBold(), @@ -312,7 +324,8 @@ class _MongoConnectionFormContentState extends material.State<_MongoConnectionFo material.Expanded( flex: 1, child: material.Column( - crossAxisAlignment: material.CrossAxisAlignment.stretch, + crossAxisAlignment: + material.CrossAxisAlignment.stretch, mainAxisSize: material.MainAxisSize.min, children: [ const Text('Port').small().semiBold(), @@ -328,7 +341,9 @@ class _MongoConnectionFormContentState extends material.State<_MongoConnectionFo ), const Gap(16), // Authentication - const Text('Authentication (Optional)').small().semiBold(), + const Text('Authentication (Optional)') + .small() + .semiBold(), const Gap(8), TextField( controller: _usernameController, @@ -349,10 +364,13 @@ class _MongoConnectionFormContentState extends material.State<_MongoConnectionFo child: material.Center( child: material.IconButton( icon: material.Icon( - _showPassword ? material.Icons.visibility_off : material.Icons.visibility, + _showPassword + ? material.Icons.visibility_off + : material.Icons.visibility, size: 20, ), - onPressed: () => setState(() => _showPassword = !_showPassword), + onPressed: () => setState( + () => _showPassword = !_showPassword), padding: material.EdgeInsets.zero, constraints: const material.BoxConstraints(), ), @@ -366,10 +384,13 @@ class _MongoConnectionFormContentState extends material.State<_MongoConnectionFo children: [ material.Expanded( child: material.Column( - crossAxisAlignment: material.CrossAxisAlignment.stretch, + crossAxisAlignment: + material.CrossAxisAlignment.stretch, mainAxisSize: material.MainAxisSize.min, children: [ - const Text('Default Database (Optional)').small().semiBold(), + const Text('Default Database (Optional)') + .small() + .semiBold(), const Gap(8), TextField( controller: _databaseController, @@ -381,10 +402,13 @@ class _MongoConnectionFormContentState extends material.State<_MongoConnectionFo const Gap(12), material.Expanded( child: material.Column( - crossAxisAlignment: material.CrossAxisAlignment.stretch, + crossAxisAlignment: + material.CrossAxisAlignment.stretch, mainAxisSize: material.MainAxisSize.min, children: [ - const Text('Auth Source (Optional)').small().semiBold(), + const Text('Auth Source (Optional)') + .small() + .semiBold(), const Gap(8), TextField( controller: _authSourceController, @@ -401,7 +425,8 @@ class _MongoConnectionFormContentState extends material.State<_MongoConnectionFo children: [ material.Checkbox( value: _useSSL, - onChanged: (v) => setState(() => _useSSL = v ?? false), + onChanged: (v) => + setState(() => _useSSL = v ?? false), ), const Gap(8), const Text('Use SSL/TLS').small(), @@ -422,7 +447,8 @@ class _MongoConnectionFormContentState extends material.State<_MongoConnectionFo onTap: _dismissResult, borderRadius: material.BorderRadius.circular(8), child: material.Container( - padding: const material.EdgeInsets.symmetric(horizontal: 12, vertical: 10), + padding: const material.EdgeInsets.symmetric( + horizontal: 12, vertical: 10), decoration: material.BoxDecoration( color: _testResult == 'success' ? theme.primary.withValues(alpha: 0.12) @@ -479,7 +505,8 @@ class _MongoConnectionFormContentState extends material.State<_MongoConnectionFo ), ), material.Container( - padding: const material.EdgeInsets.symmetric(horizontal: 24, vertical: 16), + padding: const material.EdgeInsets.symmetric( + horizontal: 24, vertical: 16), child: ValueListenableBuilder( valueListenable: _formValidNotifier.listenable, builder: (context, formValid, _) { diff --git a/lib/features/mysql/mysql_connection_form.dart b/lib/features/mysql/mysql_connection_form.dart index a143e511..7eef3b28 100644 --- a/lib/features/mysql/mysql_connection_form.dart +++ b/lib/features/mysql/mysql_connection_form.dart @@ -120,9 +120,8 @@ class _MysqlConnectionFormContentState username: _usernameController.text.trim().isEmpty ? null : _usernameController.text.trim(), - password: _passwordController.text.isEmpty - ? null - : _passwordController.text, + password: + _passwordController.text.isEmpty ? null : _passwordController.text, useSSL: _useSSL, connectionString: uri.isEmpty ? null : uri, ); @@ -155,9 +154,8 @@ class _MysqlConnectionFormContentState : _usernameController.text.trim(), password: _passwordController.text.isEmpty ? null : _passwordController.text, - databaseName: uri.isNotEmpty - ? null - : (database.isEmpty ? null : database), + databaseName: + uri.isNotEmpty ? null : (database.isEmpty ? null : database), useSSL: _useSSL, connectionString: uri.isEmpty ? null : uri, folderId: widget.folderId, @@ -196,12 +194,11 @@ class _MysqlConnectionFormContentState final radius = Theme.of(context).radiusXxl; return material.Container( - constraints: - WindowLayout.dialogConstraints( - context, - maxWidth: 600, - maxHeight: 640, - ), + constraints: WindowLayout.dialogConstraints( + context, + maxWidth: 600, + maxHeight: 640, + ), decoration: material.BoxDecoration( color: theme.popover, borderRadius: material.BorderRadius.circular(radius), @@ -347,8 +344,8 @@ class _MysqlConnectionFormContentState : material.Icons.visibility, size: 20, ), - onPressed: () => - setState(() => _showPassword = !_showPassword), + onPressed: () => setState( + () => _showPassword = !_showPassword), padding: material.EdgeInsets.zero, constraints: const material.BoxConstraints(), ), @@ -361,8 +358,7 @@ class _MysqlConnectionFormContentState children: [ material.Checkbox( value: _useSSL, - onChanged: (v) => - setState(() => _useSSL = v ?? true), + onChanged: (v) => setState(() => _useSSL = v ?? true), ), const Gap(8), const Text('Use SSL/TLS').small(), diff --git a/lib/features/mysql/mysql_sql_editor_dialog.dart b/lib/features/mysql/mysql_sql_editor_dialog.dart index 3f653e3d..e1c09ef8 100644 --- a/lib/features/mysql/mysql_sql_editor_dialog.dart +++ b/lib/features/mysql/mysql_sql_editor_dialog.dart @@ -115,7 +115,8 @@ class _MysqlSqlEditorDialogState extends material.State<_MysqlSqlEditorDialog> { child: material.SizedBox( height: 280, child: material.Container( - decoration: SqlEditorChrome.inlineFieldDecorationFromContext( + decoration: + SqlEditorChrome.inlineFieldDecorationFromContext( context, ), child: QueryaCodeEditor( @@ -132,8 +133,7 @@ class _MysqlSqlEditorDialogState extends material.State<_MysqlSqlEditorDialog> { ), if (_error != null) material.Padding( - padding: - const material.EdgeInsets.fromLTRB(24, 8, 24, 0), + padding: const material.EdgeInsets.fromLTRB(24, 8, 24, 0), child: material.Text( _error!, style: material.TextStyle( @@ -146,8 +146,7 @@ class _MysqlSqlEditorDialogState extends material.State<_MysqlSqlEditorDialog> { mainAxisAlignment: material.MainAxisAlignment.end, children: [ OutlineButton( - onPressed: () => - material.Navigator.of(context).pop(), + onPressed: () => material.Navigator.of(context).pop(), child: const Text('Cancel'), ), const Gap(8), diff --git a/lib/features/mysql/mysql_sql_workspace.dart b/lib/features/mysql/mysql_sql_workspace.dart index 2a474351..886bcd76 100644 --- a/lib/features/mysql/mysql_sql_workspace.dart +++ b/lib/features/mysql/mysql_sql_workspace.dart @@ -27,8 +27,7 @@ class MysqlSqlWorkspace extends material.StatefulWidget { final ConnectionRow connectionRow; @override - material.State createState() => - _MysqlSqlWorkspaceState(); + material.State createState() => _MysqlSqlWorkspaceState(); } class _MysqlSqlWorkspaceState extends material.State { @@ -101,12 +100,14 @@ class _MysqlSqlWorkspaceState extends material.State { _lease = lease; } - Duration? _statementTimeout() => - _queryTimeoutSeconds == null ? null : Duration(seconds: _queryTimeoutSeconds!); + Duration? _statementTimeout() => _queryTimeoutSeconds == null + ? null + : Duration(seconds: _queryTimeoutSeconds!); @override void dispose() { - SqlWorkspaceSettingsRevision.listenable.removeListener(_appSettingsListener); + SqlWorkspaceSettingsRevision.listenable + .removeListener(_appSettingsListener); _topFraction.dispose(); if (_running) { MysqlService.instance.interrupt( @@ -147,7 +148,8 @@ class _MysqlSqlWorkspaceState extends material.State { } final to = _statementTimeout(); - final rs = await conn.executeWithTimeout(userSql, timeout: to, iterable: true); + final rs = + await conn.executeWithTimeout(userSql, timeout: to, iterable: true); if (!mounted) return; diff --git a/lib/features/mysql/mysql_stats_view.dart b/lib/features/mysql/mysql_stats_view.dart index 5617644a..6fc74221 100644 --- a/lib/features/mysql/mysql_stats_view.dart +++ b/lib/features/mysql/mysql_stats_view.dart @@ -162,12 +162,14 @@ class _MysqlStatsViewState extends material.State { const Gap(8), material.SelectableText( err, - style: material.TextStyle(color: cs.mutedForeground, fontSize: 13), + style: + material.TextStyle(color: cs.mutedForeground, fontSize: 13), ), const Gap(24), OutlineButton( onPressed: _load, - leading: const material.Icon(material.Icons.refresh_rounded, size: 18), + leading: const material.Icon(material.Icons.refresh_rounded, + size: 18), child: const Text('Retry'), ), ], @@ -255,7 +257,8 @@ class _MysqlStatsViewState extends material.State { ), OutlineButton( onPressed: _load, - leading: const material.Icon(material.Icons.refresh_rounded, size: 18), + leading: + const material.Icon(material.Icons.refresh_rounded, size: 18), child: const Text('Refresh'), ), ], @@ -304,13 +307,16 @@ class _MysqlStatsViewState extends material.State { material.Widget chip(String label, String value, material.IconData icon) { return material.Expanded( child: material.ConstrainedBox( - constraints: const material.BoxConstraints(minHeight: _summaryChipHeight), + constraints: + const material.BoxConstraints(minHeight: _summaryChipHeight), child: material.Container( - padding: const material.EdgeInsets.symmetric(horizontal: 16, vertical: 12), + padding: const material.EdgeInsets.symmetric( + horizontal: 16, vertical: 12), decoration: material.BoxDecoration( color: cs.card, borderRadius: material.BorderRadius.circular(10), - border: material.Border.all(color: cs.border.withValues(alpha: 0.5)), + border: + material.Border.all(color: cs.border.withValues(alpha: 0.5)), ), child: material.Row( crossAxisAlignment: material.CrossAxisAlignment.start, @@ -354,9 +360,11 @@ class _MysqlStatsViewState extends material.State { children: [ chip('Version', versionShort, material.Icons.tag_rounded), const Gap(12), - chip('Uptime', _formatUptime(uptimeSec), material.Icons.schedule_rounded), + chip('Uptime', _formatUptime(uptimeSec), + material.Icons.schedule_rounded), const Gap(12), - chip('Connections', '$connected / $maxConn', material.Icons.people_outline_rounded), + chip('Connections', '$connected / $maxConn', + material.Icons.people_outline_rounded), const Gap(12), chip('Queries', questions, material.Icons.speed_rounded), ], @@ -422,8 +430,10 @@ class _MysqlStatsViewState extends material.State { [ _metricRow(context, 'Connected', _status(stats, 'Threads_connected')), _metricRow(context, 'Running', _status(stats, 'Threads_running')), - _metricRow(context, 'Max used', _status(stats, 'Max_used_connections')), - _metricRow(context, 'Max allowed', _variable(stats, 'max_connections')), + _metricRow( + context, 'Max used', _status(stats, 'Max_used_connections')), + _metricRow( + context, 'Max allowed', _variable(stats, 'max_connections')), ], stretch: true, ), @@ -443,7 +453,8 @@ class _MysqlStatsViewState extends material.State { _metricRow(context, 'Questions', _status(stats, 'Questions')), _metricRow(context, 'Slow queries', _status(stats, 'Slow_queries')), _metricRow(context, 'Open tables', _status(stats, 'Open_tables')), - _metricRow(context, 'Aborted connects', _status(stats, 'Aborted_connects')), + _metricRow( + context, 'Aborted connects', _status(stats, 'Aborted_connects')), ], stretch: true, ), @@ -483,8 +494,10 @@ class _MysqlStatsViewState extends material.State { context, [ _metricRow(context, 'InnoDB buffer pool', _formatBytes(pool)), - _metricRow(context, 'Charset', _variable(stats, 'character_set_server')), - _metricRow(context, 'Collation', _variable(stats, 'collation_server')), + _metricRow( + context, 'Charset', _variable(stats, 'character_set_server')), + _metricRow( + context, 'Collation', _variable(stats, 'collation_server')), _metricRow(context, 'Port', _variable(stats, 'port')), ], stretch: true, @@ -496,8 +509,7 @@ class _MysqlStatsViewState extends material.State { material.Widget _databasesCard( material.BuildContext context, Map stats) { - final databases = - stats['databases'] as List>? ?? []; + final databases = stats['databases'] as List>? ?? []; if (databases.isEmpty) return const material.SizedBox.shrink(); final cs = shadcn.Theme.of(context).colorScheme; @@ -535,7 +547,8 @@ class _MysqlStatsViewState extends material.State { width: 180, child: material.Text( '${db['name']}', - style: material.TextStyle(fontSize: 13, color: cs.foreground), + style: material.TextStyle( + fontSize: 13, color: cs.foreground), overflow: material.TextOverflow.ellipsis, ), ), diff --git a/lib/features/mysql/mysql_table_view.dart b/lib/features/mysql/mysql_table_view.dart index d02c88a9..06d30413 100644 --- a/lib/features/mysql/mysql_table_view.dart +++ b/lib/features/mysql/mysql_table_view.dart @@ -176,9 +176,8 @@ class _MysqlTableViewState extends material.State { int totalRows; if (refreshCount || _totalRowCount == null) { final countRs = await conn.execute(countSql); - totalRows = countRs.rows.isEmpty - ? 0 - : _asInt(countRs.rows.first.colAt(0)); + totalRows = + countRs.rows.isEmpty ? 0 : _asInt(countRs.rows.first.colAt(0)); } else { totalRows = _totalRowCount!; } @@ -305,8 +304,7 @@ class _MysqlTableViewState extends material.State { unawaited(_fetch()); } - bool get _canGoPrevious => - !_customSqlActive && _offset > 0 && !_loading; + bool get _canGoPrevious => !_customSqlActive && _offset > 0 && !_loading; bool get _canGoNext { if (_customSqlActive) return false; @@ -480,14 +478,15 @@ class _MysqlTableViewState extends material.State { ], const Gap(6), GhostButton( - onPressed: (!_canGoPrevious || _loading) - ? null - : _goToPreviousPage, - child: const Icon(material.Icons.chevron_left_rounded, size: 20), + onPressed: + (!_canGoPrevious || _loading) ? null : _goToPreviousPage, + child: + const Icon(material.Icons.chevron_left_rounded, size: 20), ), GhostButton( onPressed: (!_canGoNext || _loading) ? null : _goToNextPage, - child: const Icon(material.Icons.chevron_right_rounded, size: 20), + child: const Icon(material.Icons.chevron_right_rounded, + size: 20), ), ], ), @@ -555,8 +554,7 @@ class _MysqlTableViewState extends material.State { _rowNumberCell(cs, '$displayRowNum'), for (var c = 0; c < colCount; c++) _dataCell( - cs, - row.length > c ? row[c] : ''), + cs, row.length > c ? row[c] : ''), ], ), ); diff --git a/lib/features/postgresql/postgres_browser_views.dart b/lib/features/postgresql/postgres_browser_views.dart index 1e63af48..4d67d575 100644 --- a/lib/features/postgresql/postgres_browser_views.dart +++ b/lib/features/postgresql/postgres_browser_views.dart @@ -29,7 +29,8 @@ class PostgresIndexListView extends material.StatefulWidget { _PostgresIndexListViewState(); } -class _PostgresIndexListViewState extends material.State { +class _PostgresIndexListViewState + extends material.State { PgLease? _lease; bool _loading = true; @@ -208,7 +209,8 @@ class PostgresTriggerListView extends material.StatefulWidget { _PostgresTriggerListViewState(); } -class _PostgresTriggerListViewState extends material.State { +class _PostgresTriggerListViewState + extends material.State { PgLease? _lease; bool _loading = true; @@ -868,7 +870,8 @@ material.Widget _browserToolbar( OutlineButton( size: ButtonSize.small, onPressed: onRefresh, - leading: const material.Icon(material.Icons.refresh_rounded, size: 14), + leading: + const material.Icon(material.Icons.refresh_rounded, size: 14), child: const Text('Refresh'), ), ], diff --git a/lib/features/postgresql/postgres_object_kind.dart b/lib/features/postgresql/postgres_object_kind.dart index 63eeefdf..2b3745cb 100644 --- a/lib/features/postgresql/postgres_object_kind.dart +++ b/lib/features/postgresql/postgres_object_kind.dart @@ -2,22 +2,31 @@ enum PostgresObjectKind { /// Base table (data grid). table, + /// View (read-only grid). view, + /// Materialized view (data + REFRESH MATERIALIZED VIEW). materializedView, + /// Function or aggregate ([pg_get_functiondef]). function, + /// Sequence (metadata + current value). sequence, + /// All indexes in a schema (metadata list). schemaIndexes, + /// All triggers in a schema. schemaTriggers, + /// User-defined types in a schema. schemaTypes, + /// [pg_extension] for current database. databaseExtensions, + /// Foreign data wrappers and servers. databaseForeignData, } diff --git a/lib/features/postgresql/postgres_object_workspace.dart b/lib/features/postgresql/postgres_object_workspace.dart index 61a19166..5ddab61e 100644 --- a/lib/features/postgresql/postgres_object_workspace.dart +++ b/lib/features/postgresql/postgres_object_workspace.dart @@ -9,7 +9,12 @@ import 'package:querya_desktop/features/postgresql/postgres_table_view.dart'; /// Workspace content for a single PostgreSQL tree object (grid, routine, lists). Widget buildPostgresObjectWorkspace({ required ConnectionRow connection, - required ({String database, String schema, String name, PostgresObjectKind kind}) pg, + required ({ + String database, + String schema, + String name, + PostgresObjectKind kind + }) pg, }) { switch (pg.kind) { case PostgresObjectKind.table: diff --git a/lib/features/postgresql/postgres_routine_view.dart b/lib/features/postgresql/postgres_routine_view.dart index 34c14316..a52b59de 100644 --- a/lib/features/postgresql/postgres_routine_view.dart +++ b/lib/features/postgresql/postgres_routine_view.dart @@ -201,8 +201,8 @@ class _PostgresRoutineViewState extends material.State { OutlineButton( size: ButtonSize.small, onPressed: _connectAndLoad, - leading: const material.Icon( - material.Icons.refresh_rounded, size: 14), + leading: const material.Icon(material.Icons.refresh_rounded, + size: 14), child: const Text('Refresh'), ), ], diff --git a/lib/features/postgresql/postgres_sequence_view.dart b/lib/features/postgresql/postgres_sequence_view.dart index 79aa40cd..407c855d 100644 --- a/lib/features/postgresql/postgres_sequence_view.dart +++ b/lib/features/postgresql/postgres_sequence_view.dart @@ -205,8 +205,8 @@ class _PostgresSequenceViewState extends material.State { OutlineButton( size: ButtonSize.small, onPressed: _connectAndLoad, - leading: const material.Icon( - material.Icons.refresh_rounded, size: 14), + leading: const material.Icon(material.Icons.refresh_rounded, + size: 14), child: const Text('Refresh'), ), ], diff --git a/lib/features/postgresql/postgres_sql_editor_dialog.dart b/lib/features/postgresql/postgres_sql_editor_dialog.dart index f0e5a84a..1f28fefb 100644 --- a/lib/features/postgresql/postgres_sql_editor_dialog.dart +++ b/lib/features/postgresql/postgres_sql_editor_dialog.dart @@ -66,7 +66,8 @@ class _PostgresSqlEditorDialog extends material.StatefulWidget { _PostgresSqlEditorDialogState(); } -class _PostgresSqlEditorDialogState extends material.State<_PostgresSqlEditorDialog> { +class _PostgresSqlEditorDialogState + extends material.State<_PostgresSqlEditorDialog> { late final material.TextEditingController _controller; String? _error; @@ -142,7 +143,8 @@ class _PostgresSqlEditorDialogState extends material.State<_PostgresSqlEditorDia child: material.SizedBox( height: 280, child: material.Container( - decoration: SqlEditorChrome.inlineFieldDecorationFromContext( + decoration: + SqlEditorChrome.inlineFieldDecorationFromContext( context, ), child: QueryaCodeEditor( @@ -159,8 +161,7 @@ class _PostgresSqlEditorDialogState extends material.State<_PostgresSqlEditorDia ), if (_error != null) material.Padding( - padding: - const material.EdgeInsets.fromLTRB(24, 8, 24, 0), + padding: const material.EdgeInsets.fromLTRB(24, 8, 24, 0), child: material.Text( _error!, style: material.TextStyle( @@ -173,8 +174,7 @@ class _PostgresSqlEditorDialogState extends material.State<_PostgresSqlEditorDia mainAxisAlignment: material.MainAxisAlignment.end, children: [ OutlineButton( - onPressed: () => - material.Navigator.of(context).pop(), + onPressed: () => material.Navigator.of(context).pop(), child: const Text('Cancel'), ), const Gap(8), diff --git a/lib/features/postgresql/postgres_sql_workspace.dart b/lib/features/postgresql/postgres_sql_workspace.dart index c9158d67..532e6ec8 100644 --- a/lib/features/postgresql/postgres_sql_workspace.dart +++ b/lib/features/postgresql/postgres_sql_workspace.dart @@ -42,8 +42,12 @@ class PostgresSqlWorkspace extends material.StatefulWidget { final material.ValueNotifier? transactionOpenNotifier; /// Table/view/matview from the tree: sets session DB (if non-empty) and editor template. - final ({String database, String schema, String name, PostgresObjectKind kind})? - postgresSqlEditorContext; + final ({ + String database, + String schema, + String name, + PostgresObjectKind kind + })? postgresSqlEditorContext; /// Increments when [postgresSqlEditorContext] should be re-applied to the editor. final int postgresSqlEditorContextToken; @@ -181,8 +185,9 @@ class _PostgresSqlWorkspaceState extends material.State { _interruptDatabase = db; } - Duration? _statementTimeout() => - _queryTimeoutSeconds == null ? null : Duration(seconds: _queryTimeoutSeconds!); + Duration? _statementTimeout() => _queryTimeoutSeconds == null + ? null + : Duration(seconds: _queryTimeoutSeconds!); Future _refreshTxStatus() async { final conn = _lease?.connection; @@ -244,7 +249,8 @@ class _PostgresSqlWorkspaceState extends material.State { @override void dispose() { - SqlWorkspaceSettingsRevision.listenable.removeListener(_appSettingsListener); + SqlWorkspaceSettingsRevision.listenable + .removeListener(_appSettingsListener); _topFraction.dispose(); if (_running) { PostgresService.instance.interrupt( @@ -390,8 +396,7 @@ class _PostgresSqlWorkspaceState extends material.State { onExecute: _running ? null : _execute, running: _running, autocommit: _autocommit, - onAutocommitChanged: (v) => - setState(() => _autocommit = v), + onAutocommitChanged: (v) => setState(() => _autocommit = v), queryTimeoutSeconds: _queryTimeoutSeconds, onQueryTimeoutChanged: _onStmtTimeoutChanged, onOpenPreferences: () => showPreferencesDialog(context), @@ -406,10 +411,8 @@ class _PostgresSqlWorkspaceState extends material.State { } : null, txOpen: _txOpen, - onBegin: - _running ? null : () => _runTxCommand('BEGIN'), - onCommit: - _running ? null : () => _runTxCommand('COMMIT'), + onBegin: _running ? null : () => _runTxCommand('BEGIN'), + onCommit: _running ? null : () => _runTxCommand('COMMIT'), onRollback: _running ? null : () => _runTxCommand('ROLLBACK'), ), diff --git a/lib/features/postgresql/postgres_stats_view.dart b/lib/features/postgresql/postgres_stats_view.dart index 31c253ba..9af3317b 100644 --- a/lib/features/postgresql/postgres_stats_view.dart +++ b/lib/features/postgresql/postgres_stats_view.dart @@ -208,8 +208,7 @@ class _PostgresStatsViewState extends material.State { material.Row( crossAxisAlignment: material.CrossAxisAlignment.start, children: [ - material.Expanded( - child: _connectionsCard(context, stats)), + material.Expanded(child: _connectionsCard(context, stats)), const Gap(16), material.Expanded( child: _serverSettingsCard(context, stats)), @@ -261,16 +260,14 @@ class _PostgresStatsViewState extends material.State { ), const Gap(16), material.ConstrainedBox( - constraints: - const material.BoxConstraints(maxWidth: 420), + constraints: const material.BoxConstraints(maxWidth: 420), child: material.Column( crossAxisAlignment: material.CrossAxisAlignment.start, mainAxisSize: material.MainAxisSize.min, children: [ Text(widget.connectionRow.name).large().semiBold(), const Gap(4), - Text( - '${widget.connectionRow.host ?? 'localhost'}:${widget.connectionRow.port ?? 5432}') + Text('${widget.connectionRow.host ?? 'localhost'}:${widget.connectionRow.port ?? 5432}') .muted() .small(), ], @@ -280,8 +277,8 @@ class _PostgresStatsViewState extends material.State { ), OutlineButton( onPressed: _load, - leading: const material.Icon( - material.Icons.refresh_rounded, size: 18), + leading: const material.Icon(material.Icons.refresh_rounded, + size: 18), child: const Text('Refresh'), ), ], @@ -307,8 +304,7 @@ class _PostgresStatsViewState extends material.State { final dbSize = stats['current_db_size']; final dbSizeStr = dbSize != null ? _formatBytes(dbSize as int) : '—'; - material.Widget chip( - String label, String value, material.IconData icon) { + material.Widget chip(String label, String value, material.IconData icon) { return material.Expanded( child: material.ConstrainedBox( constraints: const material.BoxConstraints( @@ -320,8 +316,8 @@ class _PostgresStatsViewState extends material.State { decoration: material.BoxDecoration( color: cs.card, borderRadius: material.BorderRadius.circular(10), - border: material.Border.all( - color: cs.border.withValues(alpha: 0.5)), + border: + material.Border.all(color: cs.border.withValues(alpha: 0.5)), ), child: material.Row( crossAxisAlignment: material.CrossAxisAlignment.start, @@ -374,8 +370,8 @@ class _PostgresStatsViewState extends material.State { ); } - material.Widget _card(material.BuildContext context, String title, - material.Widget body, + material.Widget _card( + material.BuildContext context, String title, material.Widget body, {double? minHeight}) { final cs = shadcn.Theme.of(context).colorScheme; return material.Container( @@ -387,8 +383,7 @@ class _PostgresStatsViewState extends material.State { decoration: material.BoxDecoration( color: cs.card, borderRadius: material.BorderRadius.circular(12), - border: - material.Border.all(color: cs.border.withValues(alpha: 0.4)), + border: material.Border.all(color: cs.border.withValues(alpha: 0.4)), ), child: material.Column( mainAxisSize: material.MainAxisSize.min, @@ -450,8 +445,7 @@ class _PostgresStatsViewState extends material.State { material.Widget _databasesCard( material.BuildContext context, Map stats) { - final databases = - stats['databases'] as List>? ?? []; + final databases = stats['databases'] as List>? ?? []; if (databases.isEmpty) return const material.SizedBox.shrink(); final cs = shadcn.Theme.of(context).colorScheme; @@ -472,21 +466,17 @@ class _PostgresStatsViewState extends material.State { material.SizedBox( width: 80, child: const Text('Size').muted().xSmall()), material.SizedBox( - width: 70, - child: const Text('Backends').muted().xSmall()), + width: 70, child: const Text('Backends').muted().xSmall()), material.SizedBox( - width: 80, - child: const Text('Commits').muted().xSmall()), + width: 80, child: const Text('Commits').muted().xSmall()), material.SizedBox( - width: 80, - child: const Text('Rollbacks').muted().xSmall()), + width: 80, child: const Text('Rollbacks').muted().xSmall()), material.Expanded( child: const Text('Hit ratio').muted().xSmall()), ], ), ), - material.Divider( - height: 1, color: cs.border.withValues(alpha: 0.3)), + material.Divider(height: 1, color: cs.border.withValues(alpha: 0.3)), for (final db in databases) material.Padding( padding: const material.EdgeInsets.symmetric(vertical: 5), @@ -503,28 +493,21 @@ class _PostgresStatsViewState extends material.State { ), material.SizedBox( width: 80, - child: Text(_formatBytes( - (db['size'] as int?) ?? 0)) + child: Text(_formatBytes((db['size'] as int?) ?? 0)) .muted() .xSmall(), ), material.SizedBox( width: 70, - child: Text('${db['numbackends'] ?? 0}') - .muted() - .xSmall(), + child: Text('${db['numbackends'] ?? 0}').muted().xSmall(), ), material.SizedBox( width: 80, - child: Text('${db['xact_commit'] ?? 0}') - .muted() - .xSmall(), + child: Text('${db['xact_commit'] ?? 0}').muted().xSmall(), ), material.SizedBox( width: 80, - child: Text('${db['xact_rollback'] ?? 0}') - .muted() - .xSmall(), + child: Text('${db['xact_rollback'] ?? 0}').muted().xSmall(), ), material.Expanded( child: Text(_hitRatio(db)).muted().xSmall(), @@ -548,8 +531,8 @@ class _PostgresStatsViewState extends material.State { material.SizedBox(width: 160, child: Text(key).muted().small()), material.Expanded( child: material.SelectableText(value, - style: material.TextStyle( - fontSize: 13, color: cs.foreground))), + style: + material.TextStyle(fontSize: 13, color: cs.foreground))), ], ), ); diff --git a/lib/features/postgresql/postgres_table_toolbar.dart b/lib/features/postgresql/postgres_table_toolbar.dart index 24020e72..20c1b508 100644 --- a/lib/features/postgresql/postgres_table_toolbar.dart +++ b/lib/features/postgresql/postgres_table_toolbar.dart @@ -122,7 +122,8 @@ class PostgresTableToolbar extends material.StatelessWidget { const Gap(4), OutlineButton( size: ButtonSize.small, - onPressed: loading ? null : onRefreshMaterializedView, + onPressed: + loading ? null : onRefreshMaterializedView, leading: const material.Icon( material.Icons.sync_rounded, size: 15, diff --git a/lib/features/postgresql/postgres_workspace_home.dart b/lib/features/postgresql/postgres_workspace_home.dart index e80ee431..a3fdeb68 100644 --- a/lib/features/postgresql/postgres_workspace_home.dart +++ b/lib/features/postgresql/postgres_workspace_home.dart @@ -23,8 +23,12 @@ class PostgresWorkspaceHome extends material.StatefulWidget { final ConnectionRow connectionRow; /// Set when opening SQL from the tree (e.g. "Open in SQL") to seed session DB + template. - final ({String database, String schema, String name, PostgresObjectKind kind})? - postgresSqlEditorContext; + final ({ + String database, + String schema, + String name, + PostgresObjectKind kind + })? postgresSqlEditorContext; /// Bumps when [postgresSqlEditorContext] should be applied to the editor. final int postgresSqlEditorContextToken; @@ -37,7 +41,8 @@ class PostgresWorkspaceHome extends material.StatefulWidget { _PostgresWorkspaceHomeState(); } -class _PostgresWorkspaceHomeState extends material.State { +class _PostgresWorkspaceHomeState + extends material.State { int _tab = 0; late final material.ValueNotifier _sqlTxNotifier; int _lastAppliedSqlTabToken = 0; @@ -140,9 +145,7 @@ class _PostgresWorkspaceHomeState extends material.State ), child: selected ? Text(labels[i]).small().semiBold() - : Text(labels[i]) - .small() - .muted(), + : Text(labels[i]).small().muted(), ), ), ), diff --git a/lib/features/postgresql/postgresql_connection_form.dart b/lib/features/postgresql/postgresql_connection_form.dart index e89f990f..4f72544a 100644 --- a/lib/features/postgresql/postgresql_connection_form.dart +++ b/lib/features/postgresql/postgresql_connection_form.dart @@ -122,9 +122,8 @@ class _PostgresConnectionFormContentState username: _usernameController.text.trim().isEmpty ? null : _usernameController.text.trim(), - password: _passwordController.text.isEmpty - ? null - : _passwordController.text, + password: + _passwordController.text.isEmpty ? null : _passwordController.text, useSSL: _useSSL, connectionString: uri.isEmpty ? null : uri, ); @@ -157,7 +156,8 @@ class _PostgresConnectionFormContentState : _usernameController.text.trim(), password: _passwordController.text.isEmpty ? null : _passwordController.text, - databaseName: uri.isNotEmpty ? null : (database.isEmpty ? null : database), + databaseName: + uri.isNotEmpty ? null : (database.isEmpty ? null : database), useSSL: _useSSL, connectionString: uri.isEmpty ? null : uri, folderId: widget.folderId, @@ -196,12 +196,11 @@ class _PostgresConnectionFormContentState final radius = Theme.of(context).radiusXxl; return material.Container( - constraints: - WindowLayout.dialogConstraints( - context, - maxWidth: 600, - maxHeight: 640, - ), + constraints: WindowLayout.dialogConstraints( + context, + maxWidth: 600, + maxHeight: 640, + ), decoration: material.BoxDecoration( color: theme.popover, borderRadius: material.BorderRadius.circular(radius), @@ -356,8 +355,8 @@ class _PostgresConnectionFormContentState : material.Icons.visibility, size: 20, ), - onPressed: () => - setState(() => _showPassword = !_showPassword), + onPressed: () => setState( + () => _showPassword = !_showPassword), padding: material.EdgeInsets.zero, constraints: const material.BoxConstraints(), ), diff --git a/lib/features/redis/redis_connection_form.dart b/lib/features/redis/redis_connection_form.dart index 2e4cceca..eca2fd61 100644 --- a/lib/features/redis/redis_connection_form.dart +++ b/lib/features/redis/redis_connection_form.dart @@ -28,10 +28,12 @@ class _RedisConnectionFormContent extends material.StatefulWidget { final int? folderId; @override - material.State<_RedisConnectionFormContent> createState() => _RedisConnectionFormContentState(); + material.State<_RedisConnectionFormContent> createState() => + _RedisConnectionFormContentState(); } -class _RedisConnectionFormContentState extends material.State<_RedisConnectionFormContent> { +class _RedisConnectionFormContentState + extends material.State<_RedisConnectionFormContent> { final _nameController = material.TextEditingController(); final _hostController = material.TextEditingController(text: 'localhost'); final _portController = material.TextEditingController(text: '6379'); @@ -88,11 +90,16 @@ class _RedisConnectionFormContentState extends material.State<_RedisConnectionFo try { final conn = RedisConnection( id: 0, - name: _nameController.text.trim().isEmpty ? 'test' : _nameController.text.trim(), + name: _nameController.text.trim().isEmpty + ? 'test' + : _nameController.text.trim(), host: _hostController.text.trim(), port: int.tryParse(_portController.text.trim()) ?? 6379, - username: _usernameController.text.trim().isEmpty ? null : _usernameController.text.trim(), - password: _passwordController.text.isEmpty ? null : _passwordController.text, + username: _usernameController.text.trim().isEmpty + ? null + : _usernameController.text.trim(), + password: + _passwordController.text.isEmpty ? null : _passwordController.text, ); final ok = await conn.testConnection(); if (mounted) _showTestResult(ok ? 'success' : 'failed'); @@ -112,8 +119,11 @@ class _RedisConnectionFormContentState extends material.State<_RedisConnectionFo name: displayName, host: host, port: port, - username: _usernameController.text.trim().isEmpty ? null : _usernameController.text.trim(), - password: _passwordController.text.isEmpty ? null : _passwordController.text, + username: _usernameController.text.trim().isEmpty + ? null + : _usernameController.text.trim(), + password: + _passwordController.text.isEmpty ? null : _passwordController.text, folderId: widget.folderId, createdAt: DateTime.now().toUtc().toIso8601String(), ); @@ -163,7 +173,8 @@ class _RedisConnectionFormContentState extends material.State<_RedisConnectionFo children: [ material.Row( children: [ - material.Icon(material.Icons.memory_rounded, size: 24, color: theme.primary), + material.Icon(material.Icons.memory_rounded, + size: 24, color: theme.primary), const Gap(12), const Text('Redis Connection').large().semiBold(), ], @@ -192,7 +203,8 @@ class _RedisConnectionFormContentState extends material.State<_RedisConnectionFo material.Expanded( flex: 3, child: material.Column( - crossAxisAlignment: material.CrossAxisAlignment.stretch, + crossAxisAlignment: + material.CrossAxisAlignment.stretch, mainAxisSize: material.MainAxisSize.min, children: [ const Text('Host').small().semiBold(), @@ -208,7 +220,8 @@ class _RedisConnectionFormContentState extends material.State<_RedisConnectionFo material.Expanded( flex: 1, child: material.Column( - crossAxisAlignment: material.CrossAxisAlignment.stretch, + crossAxisAlignment: + material.CrossAxisAlignment.stretch, mainAxisSize: material.MainAxisSize.min, children: [ const Text('Port').small().semiBold(), @@ -223,11 +236,14 @@ class _RedisConnectionFormContentState extends material.State<_RedisConnectionFo ], ), const Gap(16), - const Text('Username (optional, Redis 6+ ACL)').small().semiBold(), + const Text('Username (optional, Redis 6+ ACL)') + .small() + .semiBold(), const Gap(8), TextField( controller: _usernameController, - placeholder: const Text('default (leave empty for default user)'), + placeholder: + const Text('default (leave empty for default user)'), ), const Gap(16), const Text('Password (optional)').small().semiBold(), @@ -251,7 +267,8 @@ class _RedisConnectionFormContentState extends material.State<_RedisConnectionFo : material.Icons.visibility, size: 20, ), - onPressed: () => setState(() => _showPassword = !_showPassword), + onPressed: () => setState( + () => _showPassword = !_showPassword), padding: material.EdgeInsets.zero, constraints: const material.BoxConstraints(), ), @@ -273,7 +290,8 @@ class _RedisConnectionFormContentState extends material.State<_RedisConnectionFo onTap: _dismissResult, borderRadius: material.BorderRadius.circular(8), child: material.Container( - padding: const material.EdgeInsets.symmetric(horizontal: 12, vertical: 10), + padding: const material.EdgeInsets.symmetric( + horizontal: 12, vertical: 10), decoration: material.BoxDecoration( color: _testResult == 'success' ? theme.primary.withValues(alpha: 0.12) @@ -330,7 +348,8 @@ class _RedisConnectionFormContentState extends material.State<_RedisConnectionFo ), ), material.Container( - padding: const material.EdgeInsets.symmetric(horizontal: 24, vertical: 16), + padding: const material.EdgeInsets.symmetric( + horizontal: 24, vertical: 16), child: ValueListenableBuilder( valueListenable: _formValidNotifier.listenable, builder: (context, formValid, _) { diff --git a/lib/features/redis/redis_databases_view.dart b/lib/features/redis/redis_databases_view.dart index 3c4b46de..eb15fd96 100644 --- a/lib/features/redis/redis_databases_view.dart +++ b/lib/features/redis/redis_databases_view.dart @@ -72,7 +72,8 @@ class _RedisDatabasesViewState extends material.State { } } } - dbs.add(_DbInfo(index: i, keys: keys, expires: expires, hasData: data != null)); + dbs.add(_DbInfo( + index: i, keys: keys, expires: expires, hasData: data != null)); } if (!mounted) return; @@ -123,8 +124,8 @@ class _RedisDatabasesViewState extends material.State { size: 48, color: cs.destructive), const Gap(16), Text(_error!, - style: material.TextStyle( - color: cs.destructive, fontSize: 13)), + style: + material.TextStyle(color: cs.destructive, fontSize: 13)), const Gap(16), OutlineButton( onPressed: _load, @@ -138,8 +139,7 @@ class _RedisDatabasesViewState extends material.State { final dbsWithData = _databases.where((d) => d.hasData).toList(); final dbsEmpty = _databases.where((d) => !d.hasData).toList(); - final totalKeys = - _databases.fold(0, (sum, d) => sum + d.keys); + final totalKeys = _databases.fold(0, (sum, d) => sum + d.keys); return material.SingleChildScrollView( padding: const material.EdgeInsets.all(16), @@ -172,8 +172,8 @@ class _RedisDatabasesViewState extends material.State { // Databases with data material.Container( decoration: material.BoxDecoration( - border: material.Border.all( - color: cs.border.withValues(alpha: 0.3)), + border: + material.Border.all(color: cs.border.withValues(alpha: 0.3)), borderRadius: const material.BorderRadius.only( bottomLeft: Radius.circular(8), bottomRight: Radius.circular(8), @@ -261,8 +261,8 @@ class _DatabaseTile extends material.StatelessWidget { onTap: onTap, hoverColor: shadcnCs.primary.withValues(alpha: 0.06), child: material.Padding( - padding: const material.EdgeInsets.symmetric( - horizontal: 20, vertical: 10), + padding: + const material.EdgeInsets.symmetric(horizontal: 20, vertical: 10), child: material.Row( children: [ material.Icon( diff --git a/lib/features/redis/redis_explorer_view.dart b/lib/features/redis/redis_explorer_view.dart index 08c960f9..fcd3af45 100644 --- a/lib/features/redis/redis_explorer_view.dart +++ b/lib/features/redis/redis_explorer_view.dart @@ -91,8 +91,7 @@ class _RedisExplorerViewState extends material.State { _showStats = false; }); try { - final conn = - RedisService.instance.createConnection(widget.connectionRow); + final conn = RedisService.instance.createConnection(widget.connectionRow); await conn.connect(); if (!mounted) { conn.disconnect(); @@ -132,8 +131,8 @@ class _RedisExplorerViewState extends material.State { List<_Crumb> get _crumbs { final list = <_Crumb>[ - _Crumb('${widget.connectionRow.name} › db${widget.database}', - _Level.keys), + _Crumb( + '${widget.connectionRow.name} › db${widget.database}', _Level.keys), ]; if (_selectedKey != null) { list.add(_Crumb(_selectedKey!, _Level.key)); @@ -297,8 +296,8 @@ class _BreadcrumbBar extends StatelessWidget { for (var i = 0; i < crumbs.length; i++) ...[ if (i > 0) ...[ material.Padding( - padding: const material.EdgeInsets.symmetric( - horizontal: 6), + padding: + const material.EdgeInsets.symmetric(horizontal: 6), child: material.Icon( material.Icons.chevron_right_rounded, size: 16, @@ -374,7 +373,8 @@ class _CrumbChip extends material.StatelessWidget { if (onTap == null) { return material.Padding( - padding: const material.EdgeInsets.symmetric(horizontal: 8, vertical: 4), + padding: + const material.EdgeInsets.symmetric(horizontal: 8, vertical: 4), child: child, ); } diff --git a/lib/features/redis/redis_key_editor.dart b/lib/features/redis/redis_key_editor.dart index 5987b07d..54292855 100644 --- a/lib/features/redis/redis_key_editor.dart +++ b/lib/features/redis/redis_key_editor.dart @@ -85,14 +85,13 @@ class _RedisKeyEditorState extends material.State { case 'hash': _hashValue = await widget.connection.hgetall(widget.keyName); case 'list': - _listValue = - await widget.connection.lrange(widget.keyName, 0, -1); + _listValue = await widget.connection.lrange(widget.keyName, 0, -1); case 'set': _setValue = await widget.connection.smembers(widget.keyName); _setValue.sort(); case 'zset': - _zsetValue = await widget.connection - .zrangeWithScores(widget.keyName, 0, -1); + _zsetValue = + await widget.connection.zrangeWithScores(widget.keyName, 0, -1); default: _stringValue = await widget.connection.get(widget.keyName); _stringController.text = _stringValue ?? ''; @@ -335,12 +334,11 @@ class _RedisKeyEditorState extends material.State { ), child: material.Row( children: [ - material.Icon(_typeIcon(widget.keyType), - size: 18, color: typeCol), + material.Icon(_typeIcon(widget.keyType), size: 18, color: typeCol), const Gap(8), material.Container( - padding: const material.EdgeInsets.symmetric( - horizontal: 6, vertical: 2), + padding: + const material.EdgeInsets.symmetric(horizontal: 6, vertical: 2), decoration: material.BoxDecoration( color: typeCol.withValues(alpha: 0.12), borderRadius: material.BorderRadius.circular(4), @@ -371,8 +369,8 @@ class _RedisKeyEditorState extends material.State { const Gap(8), // TTL badge material.Container( - padding: const material.EdgeInsets.symmetric( - horizontal: 8, vertical: 3), + padding: + const material.EdgeInsets.symmetric(horizontal: 8, vertical: 3), decoration: material.BoxDecoration( color: scs.muted.withValues(alpha: 0.3), borderRadius: material.BorderRadius.circular(6), @@ -462,19 +460,18 @@ class _RedisKeyEditorState extends material.State { PrimaryButton( onPressed: _saveString, size: ButtonSize.small, - leading: const material.Icon(material.Icons.save_rounded, - size: 14), + leading: + const material.Icon(material.Icons.save_rounded, size: 14), child: const Text('Save'), ), ], ), const Gap(8), material.Container( - constraints: - const material.BoxConstraints(minHeight: 200), + constraints: const material.BoxConstraints(minHeight: 200), decoration: material.BoxDecoration( - border: material.Border.all( - color: cs.border.withValues(alpha: 0.3)), + border: + material.Border.all(color: cs.border.withValues(alpha: 0.3)), borderRadius: material.BorderRadius.circular(8), ), child: material.TextField( @@ -683,8 +680,7 @@ class _RedisKeyEditorState extends material.State { PrimaryButton( onPressed: () { final m = _newValueController.text.trim(); - final s = - double.tryParse(_newFieldController.text.trim()); + final s = double.tryParse(_newFieldController.text.trim()); if (m.isEmpty || s == null) return; _zsetAdd(m, s); _newValueController.clear(); @@ -781,7 +777,8 @@ class _RedisTtlDialogContent extends material.StatefulWidget { _RedisTtlDialogContentState(); } -class _RedisTtlDialogContentState extends material.State<_RedisTtlDialogContent> { +class _RedisTtlDialogContentState + extends material.State<_RedisTtlDialogContent> { late final material.TextEditingController _controller; @override @@ -854,8 +851,7 @@ class _FieldRow extends StatelessWidget { @override material.Widget build(material.BuildContext context) { return material.Container( - padding: - const material.EdgeInsets.symmetric(horizontal: 12, vertical: 8), + padding: const material.EdgeInsets.symmetric(horizontal: 12, vertical: 8), decoration: material.BoxDecoration( color: colorScheme.card, borderRadius: material.BorderRadius.circular(6), @@ -920,8 +916,7 @@ class _IndexedValueRow extends StatelessWidget { @override material.Widget build(material.BuildContext context) { return material.Container( - padding: - const material.EdgeInsets.symmetric(horizontal: 12, vertical: 8), + padding: const material.EdgeInsets.symmetric(horizontal: 12, vertical: 8), decoration: material.BoxDecoration( color: colorScheme.card, borderRadius: material.BorderRadius.circular(6), @@ -974,8 +969,7 @@ class _MemberRow extends StatelessWidget { @override material.Widget build(material.BuildContext context) { return material.Container( - padding: - const material.EdgeInsets.symmetric(horizontal: 12, vertical: 8), + padding: const material.EdgeInsets.symmetric(horizontal: 12, vertical: 8), decoration: material.BoxDecoration( color: colorScheme.card, borderRadius: material.BorderRadius.circular(6), @@ -1028,8 +1022,7 @@ class _ScoredMemberRow extends StatelessWidget { @override material.Widget build(material.BuildContext context) { return material.Container( - padding: - const material.EdgeInsets.symmetric(horizontal: 12, vertical: 8), + padding: const material.EdgeInsets.symmetric(horizontal: 12, vertical: 8), decoration: material.BoxDecoration( color: colorScheme.card, borderRadius: material.BorderRadius.circular(6), @@ -1039,8 +1032,8 @@ class _ScoredMemberRow extends StatelessWidget { child: material.Row( children: [ material.Container( - padding: const material.EdgeInsets.symmetric( - horizontal: 6, vertical: 2), + padding: + const material.EdgeInsets.symmetric(horizontal: 6, vertical: 2), decoration: material.BoxDecoration( color: shadcnCs.muted.withValues(alpha: 0.3), borderRadius: material.BorderRadius.circular(4), diff --git a/lib/features/redis/redis_keys_view.dart b/lib/features/redis/redis_keys_view.dart index 48e7d25e..9316b801 100644 --- a/lib/features/redis/redis_keys_view.dart +++ b/lib/features/redis/redis_keys_view.dart @@ -199,8 +199,8 @@ class _RedisKeysViewState extends material.State { keyInfo: _keys[i], colorScheme: cs, shadcnCs: shadcnCs, - onTap: () => widget.onKeyTap?.call( - _keys[i].name, _keys[i].type), + onTap: () => + widget.onKeyTap?.call(_keys[i].name, _keys[i].type), onDelete: () => _deleteKey(_keys[i]), ), ); @@ -251,8 +251,7 @@ class _RedisKeysViewState extends material.State { Widget _buildErrorBanner(ColorScheme cs) { return material.Container( - padding: - const material.EdgeInsets.symmetric(horizontal: 16, vertical: 8), + padding: const material.EdgeInsets.symmetric(horizontal: 16, vertical: 8), color: cs.destructive.withValues(alpha: 0.1), child: Row( children: [ @@ -262,8 +261,7 @@ class _RedisKeysViewState extends material.State { material.Expanded( child: Text( _error!, - style: - material.TextStyle(color: cs.destructive, fontSize: 13), + style: material.TextStyle(color: cs.destructive, fontSize: 13), ), ), material.InkWell( @@ -393,8 +391,8 @@ class _KeyTile extends material.StatelessWidget { hoverColor: shadcnCs.muted.withValues(alpha: 0.15), borderRadius: material.BorderRadius.circular(8), child: material.Padding( - padding: const material.EdgeInsets.symmetric( - horizontal: 16, vertical: 10), + padding: + const material.EdgeInsets.symmetric(horizontal: 16, vertical: 10), child: material.Row( children: [ material.Icon(_typeIcon(ki.type), size: 16, color: typeCol), diff --git a/lib/features/redis/redis_view.dart b/lib/features/redis/redis_view.dart index 1e100dbd..10cbbb13 100644 --- a/lib/features/redis/redis_view.dart +++ b/lib/features/redis/redis_view.dart @@ -192,15 +192,19 @@ class _RedisViewState extends material.State { child: material.Column( mainAxisSize: material.MainAxisSize.min, children: [ - material.Icon(material.Icons.error_outline_rounded, size: 48, color: cs.destructive), + material.Icon(material.Icons.error_outline_rounded, + size: 48, color: cs.destructive), const Gap(16), const Text('Connection Error').large().semiBold(), const Gap(8), - material.SelectableText(err, style: material.TextStyle(color: cs.mutedForeground, fontSize: 13)), + material.SelectableText(err, + style: material.TextStyle( + color: cs.mutedForeground, fontSize: 13)), const Gap(24), OutlineButton( onPressed: _load, - leading: const material.Icon(material.Icons.refresh_rounded, size: 18), + leading: const material.Icon(material.Icons.refresh_rounded, + size: 18), child: const Text('Retry'), ), ], @@ -240,7 +244,12 @@ class _RedisViewState extends material.State { ), const Gap(24), _sectionCard(context, 'Server', info['Server'], keys: const [ - 'redis_version', 'redis_mode', 'os', 'tcp_port', 'uptime_in_days', 'config_file', + 'redis_version', + 'redis_mode', + 'os', + 'tcp_port', + 'uptime_in_days', + 'config_file', ]), const Gap(12), material.LayoutBuilder( @@ -252,10 +261,15 @@ class _RedisViewState extends material.State { children: [ _sectionCard(context, 'Clients', info['Clients']), const Gap(12), - _sectionCard(context, 'Persistence', info['Persistence'], keys: const [ - 'rdb_bgsave_in_progress', 'rdb_last_save_time', 'rdb_last_bgsave_status', - 'aof_enabled', 'aof_last_rewrite_time_sec', - ]), + _sectionCard( + context, 'Persistence', info['Persistence'], + keys: const [ + 'rdb_bgsave_in_progress', + 'rdb_last_save_time', + 'rdb_last_bgsave_status', + 'aof_enabled', + 'aof_last_rewrite_time_sec', + ]), ], ); } @@ -263,14 +277,20 @@ class _RedisViewState extends material.State { crossAxisAlignment: material.CrossAxisAlignment.start, children: [ material.Expanded( - child: _sectionCard(context, 'Clients', info['Clients']), + child: + _sectionCard(context, 'Clients', info['Clients']), ), const Gap(16), material.Expanded( - child: _sectionCard(context, 'Persistence', info['Persistence'], keys: const [ - 'rdb_bgsave_in_progress', 'rdb_last_save_time', 'rdb_last_bgsave_status', - 'aof_enabled', 'aof_last_rewrite_time_sec', - ]), + child: _sectionCard( + context, 'Persistence', info['Persistence'], + keys: const [ + 'rdb_bgsave_in_progress', + 'rdb_last_save_time', + 'rdb_last_bgsave_status', + 'aof_enabled', + 'aof_last_rewrite_time_sec', + ]), ), ], ); @@ -298,7 +318,8 @@ class _RedisViewState extends material.State { color: cs.primary.withValues(alpha: 0.12), borderRadius: material.BorderRadius.circular(12), ), - child: material.Icon(material.Icons.memory_rounded, size: 28, color: cs.primary), + child: material.Icon(material.Icons.memory_rounded, + size: 28, color: cs.primary), ), const Gap(16), material.Expanded( @@ -309,23 +330,24 @@ class _RedisViewState extends material.State { Text(widget.connectionRow.name).large().semiBold(), const Gap(4), Text('${widget.connectionRow.host ?? 'localhost'}:${widget.connectionRow.port ?? 6379}') - .muted().small(), + .muted() + .small(), ], ), ), if (widget.onBack != null) ...[ OutlineButton( onPressed: widget.onBack, - leading: const material.Icon( - material.Icons.grid_view_rounded, size: 18), + leading: + const material.Icon(material.Icons.grid_view_rounded, size: 18), child: const Text('Explorer'), ), const Gap(8), ], OutlineButton( onPressed: _load, - leading: const material.Icon( - material.Icons.refresh_rounded, size: 18), + leading: + const material.Icon(material.Icons.refresh_rounded, size: 18), child: const Text('Refresh'), ), ], @@ -356,9 +378,11 @@ class _RedisViewState extends material.State { return '${minutes}m'; } - String _labelFor(String key) => _redisFieldLabels[key] ?? key.replaceAll('_', ' '); + String _labelFor(String key) => + _redisFieldLabels[key] ?? key.replaceAll('_', ' '); - material.Widget _summaryChips(material.BuildContext context, RedisInfoSections info) { + material.Widget _summaryChips( + material.BuildContext context, RedisInfoSections info) { final cs = shadcn.Theme.of(context).colorScheme; final version = sectionValue(info, 'Server', 'redis_version') ?? '—'; final uptime = _formatUptime(info); @@ -368,13 +392,16 @@ class _RedisViewState extends material.State { material.Widget chip(String label, String value, material.IconData icon) { return material.Expanded( child: material.ConstrainedBox( - constraints: const material.BoxConstraints(minHeight: _summaryChipHeight), + constraints: + const material.BoxConstraints(minHeight: _summaryChipHeight), child: material.Container( - padding: const material.EdgeInsets.symmetric(horizontal: 16, vertical: 12), + padding: const material.EdgeInsets.symmetric( + horizontal: 16, vertical: 12), decoration: material.BoxDecoration( color: cs.card, borderRadius: material.BorderRadius.circular(10), - border: material.Border.all(color: cs.border.withValues(alpha: 0.5)), + border: + material.Border.all(color: cs.border.withValues(alpha: 0.5)), ), child: material.Row( crossAxisAlignment: material.CrossAxisAlignment.start, @@ -412,6 +439,7 @@ class _RedisViewState extends material.State { ), ); } + return material.Row( crossAxisAlignment: material.CrossAxisAlignment.start, children: [ @@ -419,7 +447,8 @@ class _RedisViewState extends material.State { const Gap(12), chip('Uptime', uptime, material.Icons.schedule_rounded), const Gap(12), - chip('Clients', '$clients / $maxClients', material.Icons.people_outline_rounded), + chip('Clients', '$clients / $maxClients', + material.Icons.people_outline_rounded), const Gap(12), chip('Ops/s', '$ops', material.Icons.speed_rounded), ], @@ -475,10 +504,13 @@ class _RedisViewState extends material.State { ); } - material.Widget _memoryCard(material.BuildContext context, RedisInfoSections info) { + material.Widget _memoryCard( + material.BuildContext context, RedisInfoSections info) { final usedHuman = sectionValue(info, 'Memory', 'used_memory_human') ?? '—'; - final peakHuman = sectionValue(info, 'Memory', 'used_memory_peak_human') ?? '—'; - final frag = sectionDouble(info, 'Memory', 'mem_fragmentation_ratio') ?? 0.0; + final peakHuman = + sectionValue(info, 'Memory', 'used_memory_peak_human') ?? '—'; + final frag = + sectionDouble(info, 'Memory', 'mem_fragmentation_ratio') ?? 0.0; final rss = sectionValue(info, 'Memory', 'used_memory_rss_human') ?? '—'; return _card( context, @@ -498,13 +530,15 @@ class _RedisViewState extends material.State { ); } - material.Widget _statsCard(material.BuildContext context, RedisInfoSections info) { + material.Widget _statsCard( + material.BuildContext context, RedisInfoSections info) { final ops = sectionInt(info, 'Stats', 'instantaneous_ops_per_sec') ?? 0; final totalOps = sectionInt(info, 'Stats', 'total_commands_processed'); final keyspaceHits = sectionInt(info, 'Stats', 'keyspace_hits') ?? 0; final keyspaceMisses = sectionInt(info, 'Stats', 'keyspace_misses') ?? 0; final total = keyspaceHits + keyspaceMisses; - final hitRate = total > 0 ? (keyspaceHits / total * 100).toStringAsFixed(1) : '—'; + final hitRate = + total > 0 ? (keyspaceHits / total * 100).toStringAsFixed(1) : '—'; return _card( context, 'Performance', @@ -513,8 +547,10 @@ class _RedisViewState extends material.State { [ _metricRow(context, 'Ops/s', '$ops'), _metricRow(context, 'Total commands', totalOps?.toString() ?? '—'), - _metricRow(context, 'Hits / misses', '$keyspaceHits / $keyspaceMisses'), - _metricRow(context, 'Hit rate', hitRate == '—' ? hitRate : '$hitRate%'), + _metricRow( + context, 'Hits / misses', '$keyspaceHits / $keyspaceMisses'), + _metricRow( + context, 'Hit rate', hitRate == '—' ? hitRate : '$hitRate%'), ], stretch: true, ), @@ -523,14 +559,16 @@ class _RedisViewState extends material.State { ); } - material.Widget _keyspaceCard(material.BuildContext context, RedisInfoSections info) { + material.Widget _keyspaceCard( + material.BuildContext context, RedisInfoSections info) { final keyspace = info['Keyspace']; final rows = []; if (keyspace != null && keyspace.isNotEmpty) { final entries = keyspace.entries.toList() ..sort((a, b) => a.key.compareTo(b.key)); for (final entry in entries) { - rows.add(_metricRow(context, entry.key, _formatKeyspaceEntry(entry.value))); + rows.add( + _metricRow(context, entry.key, _formatKeyspaceEntry(entry.value))); } } else { rows.add( @@ -581,7 +619,8 @@ class _RedisViewState extends material.State { return '${(seconds / 86400).toStringAsFixed(1)}d'; } - material.Widget _cpuCard(material.BuildContext context, RedisInfoSections info) { + material.Widget _cpuCard( + material.BuildContext context, RedisInfoSections info) { final sys = sectionDouble(info, 'CPU', 'used_cpu_sys_main_thread'); final user = sectionDouble(info, 'CPU', 'used_cpu_user_main_thread'); return _card( @@ -600,10 +639,16 @@ class _RedisViewState extends material.State { ); } - material.Widget _sectionCard(material.BuildContext context, String title, Map? data, {List? keys}) { + material.Widget _sectionCard( + material.BuildContext context, String title, Map? data, + {List? keys}) { if (data == null || data.isEmpty) return const material.SizedBox.shrink(); final entries = keys != null - ? keys.map((k) => MapEntry(k, data[k])).where((e) => e.value != null).map((e) => MapEntry(e.key, e.value as String)).toList() + ? keys + .map((k) => MapEntry(k, data[k])) + .where((e) => e.value != null) + .map((e) => MapEntry(e.key, e.value as String)) + .toList() : data.entries.toList(); if (entries.isEmpty) return const material.SizedBox.shrink(); return _card( @@ -623,7 +668,9 @@ class _RedisViewState extends material.State { if (entries.length <= 4) { return material.Column( crossAxisAlignment: material.CrossAxisAlignment.stretch, - children: [for (final e in entries) _metricRow(context, e.key, e.value)], + children: [ + for (final e in entries) _metricRow(context, e.key, e.value) + ], ); } final mid = (entries.length / 2).ceil(); @@ -635,21 +682,26 @@ class _RedisViewState extends material.State { material.Expanded( child: material.Column( crossAxisAlignment: material.CrossAxisAlignment.stretch, - children: [for (final e in left) _metricRow(context, e.key, e.value)], + children: [ + for (final e in left) _metricRow(context, e.key, e.value) + ], ), ), const Gap(24), material.Expanded( child: material.Column( crossAxisAlignment: material.CrossAxisAlignment.stretch, - children: [for (final e in right) _metricRow(context, e.key, e.value)], + children: [ + for (final e in right) _metricRow(context, e.key, e.value) + ], ), ), ], ); } - material.Widget _errorStatsCard(material.BuildContext context, RedisInfoSections info) { + material.Widget _errorStatsCard( + material.BuildContext context, RedisInfoSections info) { final data = info['Errorstats']; if (data == null || data.isEmpty) return const material.SizedBox.shrink(); return _card( @@ -665,7 +717,8 @@ class _RedisViewState extends material.State { ); } - material.Widget _metricRow(material.BuildContext context, String label, String value) { + material.Widget _metricRow( + material.BuildContext context, String label, String value) { final cs = shadcn.Theme.of(context).colorScheme; return material.Padding( padding: const material.EdgeInsets.symmetric(vertical: 6), diff --git a/lib/features/settings/preferences_appearance_section.dart b/lib/features/settings/preferences_appearance_section.dart index d0a62a6a..b2409005 100644 --- a/lib/features/settings/preferences_appearance_section.dart +++ b/lib/features/settings/preferences_appearance_section.dart @@ -320,9 +320,10 @@ class _PreferencesAppearanceSectionState ), ), OutlineButton( - onPressed: (_importing || _installingFromUrl || _openingThemesFolder) - ? null - : () => unawaited(_openThemesFolder()), + onPressed: + (_importing || _installingFromUrl || _openingThemesFolder) + ? null + : () => unawaited(_openThemesFolder()), child: material.Text( _openingThemesFolder ? 'Opening…' : 'Open themes folder', ), diff --git a/lib/features/settings/preferences_controls.dart b/lib/features/settings/preferences_controls.dart index ad30a932..9b03c939 100644 --- a/lib/features/settings/preferences_controls.dart +++ b/lib/features/settings/preferences_controls.dart @@ -163,55 +163,53 @@ class _InterfaceScaleSliderState extends material.State { final fine = _fineControl; return material.Row( - children: [ - material.Expanded( - child: Slider( - value: SliderValue.single( - _sliderPosition(displayScale, fine: fine), - ), - min: fine ? kMinUiScale : 0, - max: fine - ? kMaxUiScale - : (kUiScalePresets.length - 1).toDouble(), - divisions: fine ? _fineDivisions : kUiScalePresets.length - 1, - hintValue: const SliderValue.single(kDefaultUiScale), - onChanged: (value) { - final next = _scaleFromSlider( - value.value, - fine: _shiftHeld, - ); - setState(() => _dragScale = next); - }, - onChangeEnd: (value) { - final next = _scaleFromSlider( - value.value, - fine: _shiftHeld, - ); - setState(() => _dragScale = null); - unawaited( - UiScaleController.instance.commitScale( - next, - fine: _shiftHeld, - ), - ); - }, - ), + children: [ + material.Expanded( + child: Slider( + value: SliderValue.single( + _sliderPosition(displayScale, fine: fine), ), - const material.SizedBox(width: 8), - material.SizedBox( - width: 44, - child: material.Text( - '$pct%', - textAlign: material.TextAlign.right, - style: material.TextStyle( - fontSize: 13, - fontWeight: material.FontWeight.w600, - color: cs.popoverForeground, + min: fine ? kMinUiScale : 0, + max: fine ? kMaxUiScale : (kUiScalePresets.length - 1).toDouble(), + divisions: fine ? _fineDivisions : kUiScalePresets.length - 1, + hintValue: const SliderValue.single(kDefaultUiScale), + onChanged: (value) { + final next = _scaleFromSlider( + value.value, + fine: _shiftHeld, + ); + setState(() => _dragScale = next); + }, + onChangeEnd: (value) { + final next = _scaleFromSlider( + value.value, + fine: _shiftHeld, + ); + setState(() => _dragScale = null); + unawaited( + UiScaleController.instance.commitScale( + next, + fine: _shiftHeld, ), - ), + ); + }, + ), + ), + const material.SizedBox(width: 8), + material.SizedBox( + width: 44, + child: material.Text( + '$pct%', + textAlign: material.TextAlign.right, + style: material.TextStyle( + fontSize: 13, + fontWeight: material.FontWeight.w600, + color: cs.popoverForeground, ), - ], - ); + ), + ), + ], + ); } } diff --git a/lib/features/settings/preferences_dialog.dart b/lib/features/settings/preferences_dialog.dart index 78fc0762..1db44f32 100644 --- a/lib/features/settings/preferences_dialog.dart +++ b/lib/features/settings/preferences_dialog.dart @@ -202,7 +202,8 @@ class _PreferencesDialogContentState } }, entries: [ - for (final n in kSqlHistoryMaxEntriesPresets) + for (final n + in kSqlHistoryMaxEntriesPresets) material.DropdownMenuEntry( value: n, label: '$n entries', diff --git a/lib/features/settings/theme_color_picker_dialog.dart b/lib/features/settings/theme_color_picker_dialog.dart index fb6187f9..4426f3eb 100644 --- a/lib/features/settings/theme_color_picker_dialog.dart +++ b/lib/features/settings/theme_color_picker_dialog.dart @@ -27,7 +27,8 @@ Future showThemeColorPickerDialog({ child: const material.Text('Cancel'), ), material.TextButton( - onPressed: () => material.Navigator.pop(dialogContext, picked.toColor()), + onPressed: () => + material.Navigator.pop(dialogContext, picked.toColor()), child: const material.Text('Apply'), ), ], diff --git a/lib/features/settings/theme_editor_section.dart b/lib/features/settings/theme_editor_section.dart index c87f8cac..71ca1710 100644 --- a/lib/features/settings/theme_editor_section.dart +++ b/lib/features/settings/theme_editor_section.dart @@ -17,7 +17,8 @@ class ThemeEditorSection extends material.StatefulWidget { const ThemeEditorSection({super.key}); @override - material.State createState() => _ThemeEditorSectionState(); + material.State createState() => + _ThemeEditorSectionState(); } class _ThemeEditorSectionState extends material.State { diff --git a/lib/features/settings/theme_picker_button.dart b/lib/features/settings/theme_picker_button.dart index 5dfac989..a8b93197 100644 --- a/lib/features/settings/theme_picker_button.dart +++ b/lib/features/settings/theme_picker_button.dart @@ -57,7 +57,9 @@ List filterThemeDefinitions( theme.name.toLowerCase().contains(normalized) || theme.id.toLowerCase().contains(normalized) || theme.source.name.toLowerCase().contains(normalized) || - _sourceBadgeLabel(theme.source).toLowerCase().contains(normalized) || + _sourceBadgeLabel(theme.source) + .toLowerCase() + .contains(normalized) || (theme.metadata?.author?.toLowerCase().contains(normalized) ?? false) || (theme.metadata?.tags.any( @@ -70,7 +72,8 @@ List filterThemeDefinitions( class _ThemePickerButtonState extends material.State { final material.MenuController _controller = material.MenuController(); - final material.ScrollController _scrollController = material.ScrollController(); + final material.ScrollController _scrollController = + material.ScrollController(); final material.TextEditingController _searchController = material.TextEditingController(); bool _triggerHovered = false; @@ -210,7 +213,8 @@ class _ThemePickerButtonState extends material.State { minimumSize: material.WidgetStatePropertyAll( material.Size(menuWidth, 0), ), - padding: const material.WidgetStatePropertyAll(material.EdgeInsets.zero), + padding: + const material.WidgetStatePropertyAll(material.EdgeInsets.zero), shape: material.WidgetStatePropertyAll( material.RoundedRectangleBorder( borderRadius: material.BorderRadius.circular(radius), @@ -245,7 +249,8 @@ class _ThemePickerButtonState extends material.State { return anchor; } - material.Widget _buildMenuPanel(material.BuildContext context, ColorScheme cs) { + material.Widget _buildMenuPanel( + material.BuildContext context, ColorScheme cs) { final filteredThemes = _filteredThemes; final radius = context.scaled(QueryaDropdownTokens.menuBorderRadius); @@ -494,8 +499,8 @@ class _ThemePickerRowState extends material.State<_ThemePickerRow> { curve: context.motionCurve(QueryaMotion.enter), constraints: material.BoxConstraints(minHeight: itemHeight), padding: material.EdgeInsets.symmetric( - horizontal: - context.scaled(QueryaDropdownTokens.menuItemPadding.horizontal), + horizontal: context + .scaled(QueryaDropdownTokens.menuItemPadding.horizontal), vertical: context.scaled(QueryaDropdownTokens.menuItemPadding.vertical), ), @@ -531,7 +536,8 @@ class _ThemePickerRowState extends material.State<_ThemePickerRow> { colorScheme: cs, ), material.SizedBox(width: context.scaled(6)), - _BrightnessLabel(isDark: widget.definition.isDark, colorScheme: cs), + _BrightnessLabel( + isDark: widget.definition.isDark, colorScheme: cs), ], ), ), diff --git a/lib/features/settings/theme_preview_card.dart b/lib/features/settings/theme_preview_card.dart index c8d8afa7..b1668c75 100644 --- a/lib/features/settings/theme_preview_card.dart +++ b/lib/features/settings/theme_preview_card.dart @@ -8,7 +8,8 @@ import 'package:shadcn_flutter/shadcn_flutter.dart'; sealed class ThemePreviewResult { const ThemePreviewResult(); - const factory ThemePreviewResult.theme(QueryaTheme theme) = ThemePreviewSuccess; + const factory ThemePreviewResult.theme(QueryaTheme theme) = + ThemePreviewSuccess; const factory ThemePreviewResult.error(String message) = ThemePreviewFailure; const factory ThemePreviewResult.loading() = ThemePreviewLoading; } diff --git a/lib/shared/widgets/app_dialog.dart b/lib/shared/widgets/app_dialog.dart index 36ff20e3..01ffa2fb 100644 --- a/lib/shared/widgets/app_dialog.dart +++ b/lib/shared/widgets/app_dialog.dart @@ -69,7 +69,8 @@ class _BlurredDialogScaffold extends StatelessWidget { final t = curved.value; return ClipRect( child: BackdropFilter( - filter: ImageFilter.blur(sigmaX: 8.0 * t, sigmaY: 8.0 * t), + filter: + ImageFilter.blur(sigmaX: 8.0 * t, sigmaY: 8.0 * t), child: Container( color: Colors.black.withValues(alpha: 0.32 * t), ), diff --git a/lib/shared/widgets/querya_dropdown.dart b/lib/shared/widgets/querya_dropdown.dart index 1e6eb8c1..486715f6 100644 --- a/lib/shared/widgets/querya_dropdown.dart +++ b/lib/shared/widgets/querya_dropdown.dart @@ -59,7 +59,7 @@ class QueryaDropdown extends material.StatefulWidget { class _QueryaDropdownState extends material.State> { late material.MenuController _controller; bool _triggerHovered = false; - bool _menuOpen = false; + final material.ValueNotifier _menuOpen = material.ValueNotifier(false); List? _cachedMenuChildren; List>? _cachedMenuItems; T? _cachedMenuValue; @@ -70,6 +70,12 @@ class _QueryaDropdownState extends material.State> { _controller = widget.controller ?? material.MenuController(); } + @override + void dispose() { + _menuOpen.dispose(); + super.dispose(); + } + @override void didUpdateWidget(covariant QueryaDropdown oldWidget) { super.didUpdateWidget(oldWidget); @@ -154,8 +160,11 @@ class _QueryaDropdownState extends material.State> { cursor: widget.enabled ? material.SystemMouseCursors.click : material.SystemMouseCursors.basic, - onEnter: widget.enabled ? (_) => setState(() => _triggerHovered = true) : null, - onExit: widget.enabled ? (_) => setState(() => _triggerHovered = false) : null, + onEnter: + widget.enabled ? (_) => setState(() => _triggerHovered = true) : null, + onExit: widget.enabled + ? (_) => setState(() => _triggerHovered = false) + : null, child: material.AnimatedContainer( duration: context.motionDuration(QueryaMotion.fast), curve: context.motionCurve(QueryaMotion.enter), @@ -230,8 +239,8 @@ class _QueryaDropdownState extends material.State> { final anchor = material.MenuAnchor( controller: _controller, - onOpen: () => setState(() => _menuOpen = true), - onClose: () => setState(() => _menuOpen = false), + onOpen: () => _menuOpen.value = true, + onClose: () => _menuOpen.value = false, crossAxisUnconstrained: false, alignmentOffset: material.Offset( widget.alignmentOffset.dx, @@ -262,7 +271,7 @@ class _QueryaDropdownState extends material.State> { ), menuChildren: [ _QueryaDropdownMenuEnter( - open: _menuOpen, + openNotifier: _menuOpen, child: material.Column( mainAxisSize: material.MainAxisSize.min, crossAxisAlignment: material.CrossAxisAlignment.stretch, @@ -294,28 +303,33 @@ class _QueryaDropdownState extends material.State> { class _QueryaDropdownMenuEnter extends material.StatelessWidget { const _QueryaDropdownMenuEnter({ - required this.open, + required this.openNotifier, required this.child, }); - final bool open; + final material.ValueNotifier openNotifier; final material.Widget child; @override material.Widget build(material.BuildContext context) { final duration = context.motionDuration(QueryaMotion.standard); final curve = context.motionCurve(QueryaMotion.enter); - return material.AnimatedScale( - scale: open ? 1 : 0.96, - alignment: material.Alignment.topCenter, - duration: duration, - curve: curve, - child: material.AnimatedOpacity( - opacity: open ? 1 : 0, - duration: duration, - curve: curve, - child: child, - ), + return material.ValueListenableBuilder( + valueListenable: openNotifier, + builder: (context, open, _) { + return material.AnimatedScale( + scale: open ? 1 : 0.96, + alignment: material.Alignment.topCenter, + duration: duration, + curve: curve, + child: material.AnimatedOpacity( + opacity: open ? 1 : 0, + duration: duration, + curve: curve, + child: child, + ), + ); + }, ); } } @@ -340,7 +354,8 @@ class _QueryaDropdownMenuItem extends material.StatefulWidget { _QueryaDropdownMenuItemState(); } -class _QueryaDropdownMenuItemState extends material.State<_QueryaDropdownMenuItem> { +class _QueryaDropdownMenuItemState + extends material.State<_QueryaDropdownMenuItem> { bool _hovered = false; material.Widget _leading(material.BuildContext context, ColorScheme cs) { @@ -394,8 +409,10 @@ class _QueryaDropdownMenuItemState extends material.State<_QueryaDropdownMenu curve: context.motionCurve(QueryaMotion.enter), constraints: material.BoxConstraints(minHeight: itemHeight), padding: material.EdgeInsets.symmetric( - horizontal: context.scaled(QueryaDropdownTokens.menuItemPadding.horizontal), - vertical: context.scaled(QueryaDropdownTokens.menuItemPadding.vertical), + horizontal: + context.scaled(QueryaDropdownTokens.menuItemPadding.horizontal), + vertical: + context.scaled(QueryaDropdownTokens.menuItemPadding.vertical), ), decoration: material.BoxDecoration( color: bg, diff --git a/lib/shared/widgets/querya_dropdown_tokens.dart b/lib/shared/widgets/querya_dropdown_tokens.dart index 6a4a3721..e29cc700 100644 --- a/lib/shared/widgets/querya_dropdown_tokens.dart +++ b/lib/shared/widgets/querya_dropdown_tokens.dart @@ -52,7 +52,8 @@ abstract final class QueryaDropdownTokens { static double scaledMenuMaxHeight(material.BuildContext context) => context.scaled(menuMaxHeight); - static material.EdgeInsets scaledTriggerPadding(material.BuildContext context) => + static material.EdgeInsets scaledTriggerPadding( + material.BuildContext context) => material.EdgeInsets.symmetric( horizontal: context.scaled(triggerPaddingHorizontal), ); @@ -79,7 +80,8 @@ abstract final class QueryaDropdownTokens { return material.TextStyle( fontSize: size, height: lineHeight, - fontWeight: selected ? material.FontWeight.w600 : material.FontWeight.w400, + fontWeight: + selected ? material.FontWeight.w600 : material.FontWeight.w400, color: color, ); } diff --git a/lib/shared/widgets/widgets.dart b/lib/shared/widgets/widgets.dart index 4c4619cf..515b6ed5 100644 --- a/lib/shared/widgets/widgets.dart +++ b/lib/shared/widgets/widgets.dart @@ -10,5 +10,9 @@ library; export 'app_dialog.dart'; export 'querya_dropdown.dart' - show QueryaDropdown, QueryaDropdownItem, QueryaDropdownTokens, kPreferencesLabelWidth; + show + QueryaDropdown, + QueryaDropdownItem, + QueryaDropdownTokens, + kPreferencesLabelWidth; export 'package:shadcn_flutter/shadcn_flutter.dart'; diff --git a/test/core/csv/result_grid_csv_test.dart b/test/core/csv/result_grid_csv_test.dart index d459d072..e1be60cb 100644 --- a/test/core/csv/result_grid_csv_test.dart +++ b/test/core/csv/result_grid_csv_test.dart @@ -18,7 +18,11 @@ void main() { test('pads short rows', () { expect( - resultGridAsCsv(const ['x', 'y', 'z'], const [ + resultGridAsCsv(const [ + 'x', + 'y', + 'z' + ], const [ ['only'], ]), 'x,y,z\nonly,,', diff --git a/test/core/database/postgres_connection_pool_test.dart b/test/core/database/postgres_connection_pool_test.dart index eec675a0..0542de50 100644 --- a/test/core/database/postgres_connection_pool_test.dart +++ b/test/core/database/postgres_connection_pool_test.dart @@ -97,8 +97,10 @@ void main() { final pool = PostgresConnectionPool(createAndConnect: factory); final r = _row(); - final ro = await pool.acquire(r, database: 'postgres', mode: PgSessionMode.readOnly); - final rw = await pool.acquire(r, database: 'postgres', mode: PgSessionMode.readWrite); + final ro = await pool.acquire(r, + database: 'postgres', mode: PgSessionMode.readOnly); + final rw = await pool.acquire(r, + database: 'postgres', mode: PgSessionMode.readWrite); expect(identical(ro.connection, rw.connection), isFalse); expect(created.length, 2); ro.release(); diff --git a/test/core/database/postgres_connection_test.dart b/test/core/database/postgres_connection_test.dart index 136d8bf7..bcb68fca 100644 --- a/test/core/database/postgres_connection_test.dart +++ b/test/core/database/postgres_connection_test.dart @@ -378,7 +378,8 @@ void main() { }); group('PostgresConnection.connectToDatabase', () { - test('returns new PostgresConnection with same params and given database', () async { + test('returns new PostgresConnection with same params and given database', + () async { final conn = PostgresConnection( id: 10, name: 'Server', diff --git a/test/core/database/postgres_metadata_test.dart b/test/core/database/postgres_metadata_test.dart index 162b484e..07563a42 100644 --- a/test/core/database/postgres_metadata_test.dart +++ b/test/core/database/postgres_metadata_test.dart @@ -76,7 +76,8 @@ void main() { group('PgForeignServerRow', () { test('stores server and FDW name', () { - const row = PgForeignServerRow(serverName: 'remote_pg', fdwName: 'postgres_fdw'); + const row = + PgForeignServerRow(serverName: 'remote_pg', fdwName: 'postgres_fdw'); expect(row.serverName, 'remote_pg'); expect(row.fdwName, 'postgres_fdw'); }); diff --git a/test/core/database/postgres_sql_test.dart b/test/core/database/postgres_sql_test.dart index 0d49cac0..0ad57b89 100644 --- a/test/core/database/postgres_sql_test.dart +++ b/test/core/database/postgres_sql_test.dart @@ -82,28 +82,37 @@ void main() { group('injectSqlLimit', () { test('appends LIMIT to select query without limit', () { - expect(injectSqlLimit('SELECT * FROM users', 5000), 'SELECT * FROM users\nLIMIT 5000'); + expect(injectSqlLimit('SELECT * FROM users', 5000), + 'SELECT * FROM users\nLIMIT 5000'); }); test('handles trailing semicolons', () { - expect(injectSqlLimit('SELECT * FROM users;', 5000), 'SELECT * FROM users\nLIMIT 5000;'); - expect(injectSqlLimit('SELECT * FROM users; ', 5000), 'SELECT * FROM users\nLIMIT 5000;'); - expect(injectSqlLimit('SELECT * FROM users;;', 5000), 'SELECT * FROM users\nLIMIT 5000;;'); + expect(injectSqlLimit('SELECT * FROM users;', 5000), + 'SELECT * FROM users\nLIMIT 5000;'); + expect(injectSqlLimit('SELECT * FROM users; ', 5000), + 'SELECT * FROM users\nLIMIT 5000;'); + expect(injectSqlLimit('SELECT * FROM users;;', 5000), + 'SELECT * FROM users\nLIMIT 5000;;'); }); test('does not append LIMIT if LIMIT already exists', () { - expect(injectSqlLimit('SELECT * FROM users LIMIT 10', 5000), 'SELECT * FROM users LIMIT 10'); - expect(injectSqlLimit('SELECT * FROM users limit 10;', 5000), 'SELECT * FROM users limit 10;'); + expect(injectSqlLimit('SELECT * FROM users LIMIT 10', 5000), + 'SELECT * FROM users LIMIT 10'); + expect(injectSqlLimit('SELECT * FROM users limit 10;', 5000), + 'SELECT * FROM users limit 10;'); }); test('does not modify non-select/non-read queries', () { - expect(injectSqlLimit('INSERT INTO users VALUES (1)', 5000), 'INSERT INTO users VALUES (1)'); - expect(injectSqlLimit('UPDATE users SET x = 1', 5000), 'UPDATE users SET x = 1'); + expect(injectSqlLimit('INSERT INTO users VALUES (1)', 5000), + 'INSERT INTO users VALUES (1)'); + expect(injectSqlLimit('UPDATE users SET x = 1', 5000), + 'UPDATE users SET x = 1'); }); test('appends LIMIT to WITH query', () { expect( - injectSqlLimit('WITH t AS (SELECT * FROM users) SELECT * FROM t;', 5000), + injectSqlLimit( + 'WITH t AS (SELECT * FROM users) SELECT * FROM t;', 5000), 'WITH t AS (SELECT * FROM users) SELECT * FROM t\nLIMIT 5000;', ); }); diff --git a/test/core/database/redis_info_test.dart b/test/core/database/redis_info_test.dart index f559be34..069f0e01 100644 --- a/test/core/database/redis_info_test.dart +++ b/test/core/database/redis_info_test.dart @@ -50,16 +50,18 @@ void main() { }); test('parses all sections', () { - expect(info.keys, containsAll([ - 'Server', - 'Clients', - 'Memory', - 'Stats', - 'CPU', - 'Errorstats', - 'Keyspace', - 'Replication', - ])); + expect( + info.keys, + containsAll([ + 'Server', + 'Clients', + 'Memory', + 'Stats', + 'CPU', + 'Errorstats', + 'Keyspace', + 'Replication', + ])); }); test('parses key:value pairs correctly', () { diff --git a/test/core/editor/highlighter_theme_from_querya_test.dart b/test/core/editor/highlighter_theme_from_querya_test.dart index c12ddcfb..99587453 100644 --- a/test/core/editor/highlighter_theme_from_querya_test.dart +++ b/test/core/editor/highlighter_theme_from_querya_test.dart @@ -11,7 +11,8 @@ void main() { }); test('highlighterThemeFromQueryaEditor produces SQL spans', () { - final theme = highlighterThemeFromQueryaEditor(QueryaTheme.darkDefault.editor); + final theme = + highlighterThemeFromQueryaEditor(QueryaTheme.darkDefault.editor); final highlighter = Highlighter(language: 'sql', theme: theme); final span = highlighter.highlight('SELECT 1 -- comment'); expect(span.children, isNotNull); @@ -19,7 +20,8 @@ void main() { }); test('highlighterThemeFromQueryaEditor produces JSON spans', () { - final theme = highlighterThemeFromQueryaEditor(QueryaTheme.darkDefault.editor); + final theme = + highlighterThemeFromQueryaEditor(QueryaTheme.darkDefault.editor); final highlighter = Highlighter(language: 'json', theme: theme); const sample = '{"name": "x", "count": 1, "ok": true, "nil": null}'; final span = highlighter.highlight(sample); diff --git a/test/core/editor/querya_code_editor_test.dart b/test/core/editor/querya_code_editor_test.dart index 4c376dab..2eac736e 100644 --- a/test/core/editor/querya_code_editor_test.dart +++ b/test/core/editor/querya_code_editor_test.dart @@ -14,7 +14,8 @@ void main() { TestWidgetsFlutterBinding.ensureInitialized(); await SyntaxHighlightService.ensureInitialized(); }); - testWidgets('QueryaCodeEditor shadcn applies fontSize from props', (tester) async { + testWidgets('QueryaCodeEditor shadcn applies fontSize from props', + (tester) async { await tester.pumpWidget( queryaThemeTestShell( child: const material.SizedBox( @@ -35,7 +36,8 @@ void main() { expect(editable.style.fontSize, 17); }); - testWidgets('QueryaCodeEditor material variant uses editor foreground', (tester) async { + testWidgets('QueryaCodeEditor material variant uses editor foreground', + (tester) async { final theme = QueryaTheme.darkDefault.copyWith( editor: QueryaTheme.darkDefault.editor.copyWith( foreground: const Color(0xFFABCDEF), @@ -56,11 +58,13 @@ void main() { ); await tester.pumpAndSettle(); - final field = tester.widget(find.byType(material.TextField)); + final field = + tester.widget(find.byType(material.TextField)); expect(field.style?.color, const Color(0xFFABCDEF)); }); - testWidgets('SQL highlighting keeps external controller in sync', (tester) async { + testWidgets('SQL highlighting keeps external controller in sync', + (tester) async { final external = material.TextEditingController(); await tester.pumpWidget( queryaThemeTestShell( @@ -84,7 +88,10 @@ void main() { await tester.pump(); await pumpSyntaxHighlightDebounce(tester); expect( - tester.widget(find.byType(material.EditableText)).controller.text, + tester + .widget(find.byType(material.EditableText)) + .controller + .text, 'UPDATE x', ); }); diff --git a/test/core/json/result_grid_json_test.dart b/test/core/json/result_grid_json_test.dart index 4a480fde..6d8eb982 100644 --- a/test/core/json/result_grid_json_test.dart +++ b/test/core/json/result_grid_json_test.dart @@ -40,7 +40,11 @@ void main() { }); test('pads short rows', () { - final s = resultGridAsJson(const ['x', 'y', 'z'], const [ + final s = resultGridAsJson(const [ + 'x', + 'y', + 'z' + ], const [ ['only'], ]); expect(jsonDecode(s), { diff --git a/test/core/layout/window_layout_scale_test.dart b/test/core/layout/window_layout_scale_test.dart index f064d83e..65edb881 100644 --- a/test/core/layout/window_layout_scale_test.dart +++ b/test/core/layout/window_layout_scale_test.dart @@ -4,7 +4,8 @@ import 'package:querya_desktop/core/layout/ui_scale.dart'; import 'package:querya_desktop/core/layout/window_layout.dart'; void main() { - testWidgets('dialogConstraints scales with QueryaUiScaleScope', (tester) async { + testWidgets('dialogConstraints scales with QueryaUiScaleScope', + (tester) async { await tester.pumpWidget( QueryaUiScaleScope( scale: 1.25, @@ -28,7 +29,8 @@ void main() { ); }); - testWidgets('scaledDialogExtent applies scale before viewport clamp', (tester) async { + testWidgets('scaledDialogExtent applies scale before viewport clamp', + (tester) async { await tester.pumpWidget( MaterialApp( builder: (context, child) => QueryaUiScaleScope( diff --git a/test/core/market/extension_manifest_test.dart b/test/core/market/extension_manifest_test.dart index 52655997..e4f8840d 100644 --- a/test/core/market/extension_manifest_test.dart +++ b/test/core/market/extension_manifest_test.dart @@ -44,7 +44,8 @@ void main() { expect(manifest.tags, ['neon']); }); - test('fromThemeDefinition falls back to content hash and 0.0.0 version', () { + test('fromThemeDefinition falls back to content hash and 0.0.0 version', + () { const definition = ThemeDefinition( id: 'minimal', name: 'Minimal', diff --git a/test/core/motion/querya_animated_expand_test.dart b/test/core/motion/querya_animated_expand_test.dart index c92a4045..362a3941 100644 --- a/test/core/motion/querya_animated_expand_test.dart +++ b/test/core/motion/querya_animated_expand_test.dart @@ -3,7 +3,8 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:querya_desktop/core/motion/querya_animated_expand.dart'; void main() { - testWidgets('QueryaAnimatedExpand hides child when collapsed', (tester) async { + testWidgets('QueryaAnimatedExpand hides child when collapsed', + (tester) async { await tester.pumpWidget( const MaterialApp( home: _ExpandHost(expanded: false), diff --git a/test/core/motion/querya_cross_fade_stack_test.dart b/test/core/motion/querya_cross_fade_stack_test.dart index 0a829ad6..61b0ff6e 100644 --- a/test/core/motion/querya_cross_fade_stack_test.dart +++ b/test/core/motion/querya_cross_fade_stack_test.dart @@ -3,7 +3,8 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:querya_desktop/core/motion/querya_cross_fade_stack.dart'; void main() { - testWidgets('QueryaCrossFadeStack cross-fades children and excludes focus', (WidgetTester tester) async { + testWidgets('QueryaCrossFadeStack cross-fades children and excludes focus', + (WidgetTester tester) async { final focusNode1 = FocusNode(); final focusNode2 = FocusNode(); @@ -31,8 +32,10 @@ void main() { final animatedOpacityFinder = find.byType(AnimatedOpacity); expect(animatedOpacityFinder, findsNWidgets(2)); - final opacity1 = tester.widget(animatedOpacityFinder.at(0)).opacity; - final opacity2 = tester.widget(animatedOpacityFinder.at(1)).opacity; + final opacity1 = + tester.widget(animatedOpacityFinder.at(0)).opacity; + final opacity2 = + tester.widget(animatedOpacityFinder.at(1)).opacity; expect(opacity1, 1.0); expect(opacity2, 0.0); diff --git a/test/core/motion/querya_motion_controller_test.dart b/test/core/motion/querya_motion_controller_test.dart index c42ddbae..d11487bd 100644 --- a/test/core/motion/querya_motion_controller_test.dart +++ b/test/core/motion/querya_motion_controller_test.dart @@ -26,7 +26,8 @@ void main() { late Directory tempDir; setUpAll(() async { - tempDir = await Directory.systemTemp.createTemp('querya_motion_controller_test_'); + tempDir = + await Directory.systemTemp.createTemp('querya_motion_controller_test_'); PathProviderPlatform.instance = _FakePathProvider(tempDir.path); await LocalDb.initFfi(); }); diff --git a/test/core/platform/open_directory_test.dart b/test/core/platform/open_directory_test.dart index 975add89..b458ebeb 100644 --- a/test/core/platform/open_directory_test.dart +++ b/test/core/platform/open_directory_test.dart @@ -7,8 +7,8 @@ import 'package:querya_desktop/core/platform/open_directory.dart'; void main() { group('openDirectoryInFileManager', () { test('creates missing directory before delegating to opener', () async { - final root = await Directory.systemTemp - .createTemp('querya_open_directory_test_'); + final root = + await Directory.systemTemp.createTemp('querya_open_directory_test_'); addTearDown(() async { if (await root.exists()) { await root.delete(recursive: true); diff --git a/test/core/storage/app_settings_test.dart b/test/core/storage/app_settings_test.dart index 9f4fa6e1..44e41357 100644 --- a/test/core/storage/app_settings_test.dart +++ b/test/core/storage/app_settings_test.dart @@ -34,7 +34,8 @@ class _FakePathProvider extends PathProviderPlatform { Future?> getExternalCachePaths() async => [_root]; @override - Future?> getExternalStoragePaths({StorageDirectory? type}) async => + Future?> getExternalStoragePaths( + {StorageDirectory? type}) async => [_root]; @override @@ -47,7 +48,8 @@ void main() { late Directory tempDir; setUpAll(() async { - tempDir = await Directory.systemTemp.createTemp('querya_app_settings_test_'); + tempDir = + await Directory.systemTemp.createTemp('querya_app_settings_test_'); PathProviderPlatform.instance = _FakePathProvider(tempDir.path); await LocalDb.initFfi(); }); @@ -63,72 +65,91 @@ void main() { await AppSettings.instance.setPostgresSqlStmtTimeoutSeconds(null); await AppSettings.instance.setMysqlSqlStmtTimeoutSeconds(null); await LocalDb.instance.deleteAppSetting(AppSettingsKeys.sqlResultMaxRows); - await LocalDb.instance.deleteAppSetting(AppSettingsKeys.sqlEditorFontSizePoints); - await LocalDb.instance.deleteAppSetting(AppSettingsKeys.sqlHistoryMaxEntries); + await LocalDb.instance + .deleteAppSetting(AppSettingsKeys.sqlEditorFontSizePoints); + await LocalDb.instance + .deleteAppSetting(AppSettingsKeys.sqlHistoryMaxEntries); await AppSettings.instance.clearThemeSettings(); }); group('AppSettings', () { test('getPostgresSqlStmtTimeoutSeconds roundtrip', () async { - expect(await AppSettings.instance.getPostgresSqlStmtTimeoutSeconds(), isNull); + expect(await AppSettings.instance.getPostgresSqlStmtTimeoutSeconds(), + isNull); await AppSettings.instance.setPostgresSqlStmtTimeoutSeconds(90); expect(await AppSettings.instance.getPostgresSqlStmtTimeoutSeconds(), 90); await AppSettings.instance.setPostgresSqlStmtTimeoutSeconds(null); - expect(await AppSettings.instance.getPostgresSqlStmtTimeoutSeconds(), isNull); + expect(await AppSettings.instance.getPostgresSqlStmtTimeoutSeconds(), + isNull); }); - test('getPostgresSqlStmtTimeoutSeconds returns null for invalid stored value', () async { + test( + 'getPostgresSqlStmtTimeoutSeconds returns null for invalid stored value', + () async { await LocalDb.instance.setAppSetting( AppSettingsKeys.postgresSqlStmtTimeoutSeconds, 'not-a-number', ); - expect(await AppSettings.instance.getPostgresSqlStmtTimeoutSeconds(), isNull); + expect(await AppSettings.instance.getPostgresSqlStmtTimeoutSeconds(), + isNull); }); test('getMysqlSqlStmtTimeoutSeconds roundtrip', () async { - expect(await AppSettings.instance.getMysqlSqlStmtTimeoutSeconds(), isNull); + expect( + await AppSettings.instance.getMysqlSqlStmtTimeoutSeconds(), isNull); await AppSettings.instance.setMysqlSqlStmtTimeoutSeconds(60); expect(await AppSettings.instance.getMysqlSqlStmtTimeoutSeconds(), 60); await AppSettings.instance.setMysqlSqlStmtTimeoutSeconds(null); - expect(await AppSettings.instance.getMysqlSqlStmtTimeoutSeconds(), isNull); + expect( + await AppSettings.instance.getMysqlSqlStmtTimeoutSeconds(), isNull); }); - test('getMysqlSqlStmtTimeoutSeconds returns null for invalid stored value', () async { + test('getMysqlSqlStmtTimeoutSeconds returns null for invalid stored value', + () async { await LocalDb.instance.setAppSetting( AppSettingsKeys.mysqlSqlStmtTimeoutSeconds, 'not-a-number', ); - expect(await AppSettings.instance.getMysqlSqlStmtTimeoutSeconds(), isNull); + expect( + await AppSettings.instance.getMysqlSqlStmtTimeoutSeconds(), isNull); }); test('getSqlResultMaxRows defaults and normalizes to preset', () async { - expect(await AppSettings.instance.getSqlResultMaxRows(), kDefaultSqlResultMaxRows); + expect(await AppSettings.instance.getSqlResultMaxRows(), + kDefaultSqlResultMaxRows); await AppSettings.instance.setSqlResultMaxRows(10000); expect(await AppSettings.instance.getSqlResultMaxRows(), 10000); - await LocalDb.instance.setAppSetting(AppSettingsKeys.sqlResultMaxRows, '7777'); + await LocalDb.instance + .setAppSetting(AppSettingsKeys.sqlResultMaxRows, '7777'); expect(await AppSettings.instance.getSqlResultMaxRows(), 10000); - await LocalDb.instance.setAppSetting(AppSettingsKeys.sqlResultMaxRows, 'not-int'); - expect(await AppSettings.instance.getSqlResultMaxRows(), kDefaultSqlResultMaxRows); + await LocalDb.instance + .setAppSetting(AppSettingsKeys.sqlResultMaxRows, 'not-int'); + expect(await AppSettings.instance.getSqlResultMaxRows(), + kDefaultSqlResultMaxRows); }); test('getSqlEditorFontSize roundtrip and invalid stored', () async { - expect(await AppSettings.instance.getSqlEditorFontSize(), kDefaultSqlEditorFontSize); + expect(await AppSettings.instance.getSqlEditorFontSize(), + kDefaultSqlEditorFontSize); await AppSettings.instance.setSqlEditorFontSize(16); expect(await AppSettings.instance.getSqlEditorFontSize(), 16); - await LocalDb.instance.setAppSetting(AppSettingsKeys.sqlEditorFontSizePoints, '99'); + await LocalDb.instance + .setAppSetting(AppSettingsKeys.sqlEditorFontSizePoints, '99'); expect(await AppSettings.instance.getSqlEditorFontSize(), 24); - await LocalDb.instance.setAppSetting(AppSettingsKeys.sqlEditorFontSizePoints, 'x'); - expect(await AppSettings.instance.getSqlEditorFontSize(), kDefaultSqlEditorFontSize); + await LocalDb.instance + .setAppSetting(AppSettingsKeys.sqlEditorFontSizePoints, 'x'); + expect(await AppSettings.instance.getSqlEditorFontSize(), + kDefaultSqlEditorFontSize); }); test('setSqlResultMaxRows snaps non-preset to nearest', () async { @@ -305,7 +326,8 @@ void main() { expect(AppSettingsRevision.listenable.value, start + 1); }); - test('mutating SQL workspace settings notifies SqlWorkspaceSettingsRevision', + test( + 'mutating SQL workspace settings notifies SqlWorkspaceSettingsRevision', () async { var calls = 0; void listener() => calls++; @@ -313,7 +335,8 @@ void main() { SqlWorkspaceSettingsRevision.listenable.addListener(listener); final before = SqlWorkspaceSettingsRevision.listenable.value; await AppSettings.instance.setPostgresSqlStmtTimeoutSeconds(45); - expect(SqlWorkspaceSettingsRevision.listenable.value, greaterThan(before)); + expect( + SqlWorkspaceSettingsRevision.listenable.value, greaterThan(before)); expect(calls, greaterThan(0)); SqlWorkspaceSettingsRevision.listenable.removeListener(listener); }); diff --git a/test/core/storage/local_db_secrets_test.dart b/test/core/storage/local_db_secrets_test.dart index 1afa3908..bee13adf 100644 --- a/test/core/storage/local_db_secrets_test.dart +++ b/test/core/storage/local_db_secrets_test.dart @@ -35,7 +35,8 @@ class _FakePathProvider extends PathProviderPlatform { Future?> getExternalCachePaths() async => [_root]; @override - Future?> getExternalStoragePaths({StorageDirectory? type}) async => + Future?> getExternalStoragePaths( + {StorageDirectory? type}) async => [_root]; @override @@ -87,7 +88,8 @@ void main() { options: OpenDatabaseOptions(readOnly: true), ); try { - final maps = await raw.query('connections', where: 'id = ?', whereArgs: [id]); + final maps = + await raw.query('connections', where: 'id = ?', whereArgs: [id]); expect(maps.single['password'], isNull); } finally { await raw.close(); diff --git a/test/core/storage/sql_query_history_test.dart b/test/core/storage/sql_query_history_test.dart index 38a9c201..77fe48d7 100644 --- a/test/core/storage/sql_query_history_test.dart +++ b/test/core/storage/sql_query_history_test.dart @@ -34,7 +34,8 @@ class _FakePathProvider extends PathProviderPlatform { Future?> getExternalCachePaths() async => [_root]; @override - Future?> getExternalStoragePaths({StorageDirectory? type}) async => + Future?> getExternalStoragePaths( + {StorageDirectory? type}) async => [_root]; @override diff --git a/test/core/theme/parser/querya_editor_theme_from_manifest_test.dart b/test/core/theme/parser/querya_editor_theme_from_manifest_test.dart index acee07e5..ff11ffb3 100644 --- a/test/core/theme/parser/querya_editor_theme_from_manifest_test.dart +++ b/test/core/theme/parser/querya_editor_theme_from_manifest_test.dart @@ -9,8 +9,8 @@ import 'package:querya_desktop/core/theme/querya_theme.dart'; void main() { group('editorThemeFromQueryaColors', () { test('full fixture maps custom editor values', () { - final raw = - File('test/fixtures/themes/querya_custom_dark.json').readAsStringSync(); + final raw = File('test/fixtures/themes/querya_custom_dark.json') + .readAsStringSync(); final manifest = QueryaThemeManifest.fromJsonString(raw); final editor = editorThemeFromQueryaColors( colors: manifest.editorColors, diff --git a/test/core/theme/parser/querya_theme_color_scheme_test.dart b/test/core/theme/parser/querya_theme_color_scheme_test.dart index 49b0af75..b4ad8bdd 100644 --- a/test/core/theme/parser/querya_theme_color_scheme_test.dart +++ b/test/core/theme/parser/querya_theme_color_scheme_test.dart @@ -10,8 +10,8 @@ import 'package:shadcn_flutter/shadcn_flutter.dart'; void main() { group('colorSchemeFromQueryaThemeColors', () { test('full fixture maps custom shadcn values', () { - final raw = - File('test/fixtures/themes/querya_custom_dark.json').readAsStringSync(); + final raw = File('test/fixtures/themes/querya_custom_dark.json') + .readAsStringSync(); final manifest = QueryaThemeManifest.fromJsonString(raw); final scheme = colorSchemeFromQueryaThemeColors( colors: manifest.shadcnColors, diff --git a/test/core/theme/parser/querya_theme_from_manifest_test.dart b/test/core/theme/parser/querya_theme_from_manifest_test.dart index e6e1c814..d81ad8f2 100644 --- a/test/core/theme/parser/querya_theme_from_manifest_test.dart +++ b/test/core/theme/parser/querya_theme_from_manifest_test.dart @@ -10,8 +10,8 @@ import 'package:querya_desktop/core/theme/querya_theme.dart'; void main() { group('queryaThemeFromManifest', () { test('full dark fixture builds dark QueryaTheme', () { - final raw = - File('test/fixtures/themes/querya_custom_dark.json').readAsStringSync(); + final raw = File('test/fixtures/themes/querya_custom_dark.json') + .readAsStringSync(); final manifest = QueryaThemeManifest.fromJsonString(raw); final theme = queryaThemeFromManifest(manifest); @@ -23,8 +23,8 @@ void main() { }); test('full light fixture builds light QueryaTheme', () { - final raw = - File('test/fixtures/themes/querya_custom_light.json').readAsStringSync(); + final raw = File('test/fixtures/themes/querya_custom_light.json') + .readAsStringSync(); final manifest = QueryaThemeManifest.fromJsonString(raw); final theme = queryaThemeFromManifest(manifest); @@ -35,8 +35,8 @@ void main() { }); test('preserves tokenColors from manifest', () { - final raw = - File('test/fixtures/themes/querya_custom_dark.json').readAsStringSync(); + final raw = File('test/fixtures/themes/querya_custom_dark.json') + .readAsStringSync(); final manifest = QueryaThemeManifest.fromJsonString(raw); final theme = queryaThemeFromManifest(manifest); @@ -60,8 +60,8 @@ void main() { }); test('does not create ThemeData', () { - final raw = - File('test/fixtures/themes/querya_custom_dark.json').readAsStringSync(); + final raw = File('test/fixtures/themes/querya_custom_dark.json') + .readAsStringSync(); final manifest = QueryaThemeManifest.fromJsonString(raw); expect(queryaThemeFromManifest(manifest), isA()); diff --git a/test/core/theme/parser/querya_theme_manifest_test.dart b/test/core/theme/parser/querya_theme_manifest_test.dart index 65d5cc56..09faa2a6 100644 --- a/test/core/theme/parser/querya_theme_manifest_test.dart +++ b/test/core/theme/parser/querya_theme_manifest_test.dart @@ -19,8 +19,8 @@ void main() { }); test('parses full dark fixture', () { - final raw = - File('test/fixtures/themes/querya_custom_dark.json').readAsStringSync(); + final raw = File('test/fixtures/themes/querya_custom_dark.json') + .readAsStringSync(); final manifest = QueryaThemeManifest.fromJsonString(raw); expect(manifest.schema, queryaThemeSchemaV1); @@ -37,8 +37,8 @@ void main() { }); test('parses full light fixture', () { - final raw = - File('test/fixtures/themes/querya_custom_light.json').readAsStringSync(); + final raw = File('test/fixtures/themes/querya_custom_light.json') + .readAsStringSync(); final manifest = QueryaThemeManifest.fromJsonString(raw); expect(manifest.type, QueryaThemeType.light); @@ -53,8 +53,8 @@ void main() { }); test('parses minimal fixture with sparse colors', () { - final raw = - File('test/fixtures/themes/querya_custom_minimal.json').readAsStringSync(); + final raw = File('test/fixtures/themes/querya_custom_minimal.json') + .readAsStringSync(); final manifest = QueryaThemeManifest.fromJsonString(raw); expect(manifest.id, 'fixture-custom-minimal'); @@ -65,8 +65,8 @@ void main() { }); test('parses JSONC fixture with comments and trailing commas', () { - final raw = - File('test/fixtures/themes/querya_custom_jsonc.jsonc').readAsStringSync(); + final raw = File('test/fixtures/themes/querya_custom_jsonc.jsonc') + .readAsStringSync(); final manifest = QueryaThemeManifest.fromJsonString(raw); expect(manifest.id, 'fixture-custom-jsonc'); @@ -94,8 +94,8 @@ void main() { }); test('returns unmodifiable color maps', () { - final raw = - File('test/fixtures/themes/querya_custom_minimal.json').readAsStringSync(); + final raw = File('test/fixtures/themes/querya_custom_minimal.json') + .readAsStringSync(); final manifest = QueryaThemeManifest.fromJsonString(raw); expect( @@ -125,8 +125,9 @@ void main() { }); test('throws when id is missing', () { - final raw = File('test/fixtures/themes/querya_custom_invalid_missing_id.json') - .readAsStringSync(); + final raw = + File('test/fixtures/themes/querya_custom_invalid_missing_id.json') + .readAsStringSync(); expect( () => QueryaThemeManifest.fromJsonString(raw), @@ -188,15 +189,16 @@ void main() { }); test('serializes to valid JSON for export', () { - final raw = - File('test/fixtures/themes/querya_custom_dark.json').readAsStringSync(); + final raw = File('test/fixtures/themes/querya_custom_dark.json') + .readAsStringSync(); final manifest = QueryaThemeManifest.fromJsonString(raw); final exported = manifest.toJsonString(); final reparsed = QueryaThemeManifest.fromJsonString(exported); expect(reparsed.id, manifest.id); - expect(reparsed.shadcnColors['primary'], manifest.shadcnColors['primary']); + expect( + reparsed.shadcnColors['primary'], manifest.shadcnColors['primary']); expect(reparsed.tokenColors.length, manifest.tokenColors.length); }); diff --git a/test/core/theme/parser/querya_workbench_theme_from_manifest_test.dart b/test/core/theme/parser/querya_workbench_theme_from_manifest_test.dart index 8b740293..e6c20ce5 100644 --- a/test/core/theme/parser/querya_workbench_theme_from_manifest_test.dart +++ b/test/core/theme/parser/querya_workbench_theme_from_manifest_test.dart @@ -9,8 +9,8 @@ import 'package:querya_desktop/core/theme/querya_theme.dart'; void main() { group('workbenchThemeFromQueryaColors', () { test('full fixture maps custom workbench values', () { - final raw = - File('test/fixtures/themes/querya_custom_dark.json').readAsStringSync(); + final raw = File('test/fixtures/themes/querya_custom_dark.json') + .readAsStringSync(); final manifest = QueryaThemeManifest.fromJsonString(raw); final workbench = workbenchThemeFromQueryaColors( colors: manifest.editorColors, diff --git a/test/core/theme/parser/token_colors_dracula_sql_test.dart b/test/core/theme/parser/token_colors_dracula_sql_test.dart index f2d8a340..fceadf82 100644 --- a/test/core/theme/parser/token_colors_dracula_sql_test.dart +++ b/test/core/theme/parser/token_colors_dracula_sql_test.dart @@ -13,8 +13,10 @@ void main() { await Highlighter.initialize(['sql']); }); - test('Dracula-like tokenColors distinguish comment, keyword, string in SQL', () { - final raw = File('test/fixtures/themes/dracula_tokens.json').readAsStringSync(); + test('Dracula-like tokenColors distinguish comment, keyword, string in SQL', + () { + final raw = + File('test/fixtures/themes/dracula_tokens.json').readAsStringSync(); final manifest = VsCodeThemeManifest.fromJsonString(raw); final theme = highlighterThemeFromQueryaEditor( QueryaTheme.darkDefault.editor, diff --git a/test/core/theme/parser/vscode_color_map_test.dart b/test/core/theme/parser/vscode_color_map_test.dart index d11181d6..3ddc5cee 100644 --- a/test/core/theme/parser/vscode_color_map_test.dart +++ b/test/core/theme/parser/vscode_color_map_test.dart @@ -67,7 +67,8 @@ void main() { expect(theme.workbench.surface, const Color(0xFFFFFFFF)); }); - test('unknown keys are reported and defaults kept for unmapped tokens', () async { + test('unknown keys are reported and defaults kept for unmapped tokens', + () async { final src = await fixture('with_unknown_keys.json'); final manifest = VsCodeThemeManifest.fromJsonString(src); final unknown = []; diff --git a/test/core/theme/querya_app_theme_test.dart b/test/core/theme/querya_app_theme_test.dart index 161ce2b0..25b27219 100644 --- a/test/core/theme/querya_app_theme_test.dart +++ b/test/core/theme/querya_app_theme_test.dart @@ -45,7 +45,8 @@ void main() { await ThemeController.instance.load(); }); - test('ThemeController shadcn themes differ for light vs dark background', () async { + test('ThemeController shadcn themes differ for light vs dark background', + () async { final controller = ThemeController.instance; await controller.load(); diff --git a/test/core/theme/querya_light_theme_test.dart b/test/core/theme/querya_light_theme_test.dart index 0a6d2a4b..88fb8180 100644 --- a/test/core/theme/querya_light_theme_test.dart +++ b/test/core/theme/querya_light_theme_test.dart @@ -53,7 +53,9 @@ double contrastRatio(Color a, Color b) { double _relativeLuminance(Color c) { double channel(double v) { - final normalized = v <= 0.03928 ? v / 12.92 : math.pow((v + 0.055) / 1.055, 2.4).toDouble(); + final normalized = v <= 0.03928 + ? v / 12.92 + : math.pow((v + 0.055) / 1.055, 2.4).toDouble(); return normalized; } diff --git a/test/core/theme/querya_theme_scope_test.dart b/test/core/theme/querya_theme_scope_test.dart index b759edf4..36fc90fc 100644 --- a/test/core/theme/querya_theme_scope_test.dart +++ b/test/core/theme/querya_theme_scope_test.dart @@ -78,4 +78,4 @@ class _ScopeHost extends StatelessWidget { ), ); } -} \ No newline at end of file +} diff --git a/test/core/theme/theme_controller_test.dart b/test/core/theme/theme_controller_test.dart index 6992e273..dfa4f22e 100644 --- a/test/core/theme/theme_controller_test.dart +++ b/test/core/theme/theme_controller_test.dart @@ -108,10 +108,12 @@ void main() { expect(c.preset, QueryaThemePreset.queryaDark); expect(c.activeTheme, QueryaTheme.darkDefault); expect(c.isLoaded, isTrue); - expect(c.availableThemes.map((theme) => theme.id), containsAll([ - ThemeController.builtinQueryaDarkId, - ThemeController.builtinQueryaLightId, - ])); + expect( + c.availableThemes.map((theme) => theme.id), + containsAll([ + ThemeController.builtinQueryaDarkId, + ThemeController.builtinQueryaLightId, + ])); expect(c.effectiveSelectedThemeId, ThemeController.builtinQueryaDarkId); }); @@ -147,7 +149,8 @@ void main() { const Color(0xFFFF0000), ); expect( - (await AppSettings.instance.getThemeColorOverrides())['sideBar.background'], + (await AppSettings.instance + .getThemeColorOverrides())['sideBar.background'], '#ff0000', ); @@ -199,7 +202,8 @@ void main() { }); group('registry integration', () { - test('setThemeById applies registry theme and persists selection', () async { + test('setThemeById applies registry theme and persists selection', + () async { final c = ThemeController.instance; await _copyFixture( 'querya_custom_dark.json', @@ -211,8 +215,10 @@ void main() { expect(c.selectedThemeId, 'fixture-custom-dark'); expect(c.selectedThemeLoadError, isNull); - expect(c.activeTheme.colorScheme.primary, parseQueryaThemeColor('#38BDF8')); - expect(await AppSettings.instance.getSelectedThemeId(), 'fixture-custom-dark'); + expect( + c.activeTheme.colorScheme.primary, parseQueryaThemeColor('#38BDF8')); + expect(await AppSettings.instance.getSelectedThemeId(), + 'fixture-custom-dark'); expect( await AppSettings.instance.getSelectedThemeSource(), 'filesystem', @@ -235,12 +241,14 @@ void main() { expect(c.selectedThemeId, isNull); }); - test('broken selected id falls back to Querya Dark without clearing settings', + test( + 'broken selected id falls back to Querya Dark without clearing settings', () async { final c = ThemeController.instance; await AppSettings.instance.setSelectedThemeId('missing-theme'); await AppSettings.instance.setSelectedThemeSource('filesystem'); - await AppSettings.instance.setSelectedThemePath('/tmp/missing-theme.json'); + await AppSettings.instance + .setSelectedThemePath('/tmp/missing-theme.json'); await AppSettings.instance.setThemePreset(QueryaThemePreset.queryaLight); await c.load(); @@ -273,7 +281,8 @@ void main() { ThemeController.selectedThemeStartupFallbackMessage, ); expect(c.selectedThemeId, 'fixture-custom-dark'); - expect(await AppSettings.instance.getSelectedThemeId(), 'fixture-custom-dark'); + expect(await AppSettings.instance.getSelectedThemeId(), + 'fixture-custom-dark'); }); test('invalid theme file skipped on startup falls back to Querya Dark', @@ -310,7 +319,8 @@ void main() { await c.setThemeById('fixture-custom-dark'); expect(c.selectedThemeLoadError, isNull); - expect(c.activeTheme.colorScheme.primary, parseQueryaThemeColor('#38BDF8')); + expect( + c.activeTheme.colorScheme.primary, parseQueryaThemeColor('#38BDF8')); }); test('setPreset clears registry selection', () async { @@ -362,7 +372,8 @@ void main() { final c = ThemeController.instance; await c.load(); - final result = await c.previewThemeById(ThemeController.builtinQueryaDarkId); + final result = + await c.previewThemeById(ThemeController.builtinQueryaDarkId); expect(result, isA()); expect((result as ThemeLoadSuccess).theme, QueryaTheme.darkDefault); @@ -374,11 +385,13 @@ void main() { await c.setThemeMode(ThemeMode.light); await c.resetToDefaults(); - expect(c.availableThemes.map((theme) => theme.id), containsAll([ - ThemeController.builtinQueryaDarkId, - ThemeController.builtinQueryaLightId, - 'cyberpunk-neon', - ])); + expect( + c.availableThemes.map((theme) => theme.id), + containsAll([ + ThemeController.builtinQueryaDarkId, + ThemeController.builtinQueryaLightId, + 'cyberpunk-neon', + ])); expect(c.effectiveSelectedThemeId, ThemeController.builtinQueryaDarkId); }); }); @@ -423,7 +436,8 @@ void main() { expect(c.selectedThemeId, 'fixture-custom-dark'); }); - test('sets isLoadingAvailableThemes while refresh is in progress', () async { + test('sets isLoadingAvailableThemes while refresh is in progress', + () async { final c = ThemeController.instance; await c.load(); @@ -442,10 +456,12 @@ void main() { expect(c.isLoadingAvailableThemes, isFalse); }); - test('importRegistryThemeFile adds theme to registry and selects it', () async { + test('importRegistryThemeFile adds theme to registry and selects it', + () async { final c = ThemeController.instance; await c.load(); - final source = File(p.join('test/fixtures/themes', 'querya_custom_dark.json')); + final source = + File(p.join('test/fixtures/themes', 'querya_custom_dark.json')); final result = await c.importRegistryThemeFile(source.path); @@ -455,7 +471,8 @@ void main() { c.availableThemes.map((theme) => theme.id), contains('fixture-custom-dark'), ); - expect(c.activeTheme.colorScheme.primary, parseQueryaThemeColor('#38BDF8')); + expect( + c.activeTheme.colorScheme.primary, parseQueryaThemeColor('#38BDF8')); }); }); } diff --git a/test/core/theme/theme_editor_draft_test.dart b/test/core/theme/theme_editor_draft_test.dart index 38539a11..717a7b4f 100644 --- a/test/core/theme/theme_editor_draft_test.dart +++ b/test/core/theme/theme_editor_draft_test.dart @@ -19,14 +19,17 @@ void main() { theme: theme, ); - expect(draft.shadcnColors['primary'], formatVsCodeColor(theme.colorScheme.primary)); - expect(draft.editorColors['background'], formatVsCodeColor(theme.editor.background)); - expect(draft.editorColors['canvas'], formatVsCodeColor(theme.workbench.canvas)); + expect(draft.shadcnColors['primary'], + formatVsCodeColor(theme.colorScheme.primary)); + expect(draft.editorColors['background'], + formatVsCodeColor(theme.editor.background)); + expect(draft.editorColors['canvas'], + formatVsCodeColor(theme.workbench.canvas)); }); test('setColor updates manifest and round-trips export', () { - final raw = - File('test/fixtures/themes/querya_custom_dark.json').readAsStringSync(); + final raw = File('test/fixtures/themes/querya_custom_dark.json') + .readAsStringSync(); final source = QueryaThemeManifest.fromJsonString(raw); final draft = ThemeEditorDraft.fromManifest(source); @@ -56,7 +59,8 @@ void main() { expect(exported.id, 'querya-dark-edited'); expect(exported.name, 'Querya Dark (edited)'); - final json = jsonDecode(exported.toExportJsonString()) as Map; + final json = + jsonDecode(exported.toExportJsonString()) as Map; expect(json['schema'], queryaThemeSchemaV1); expect(json['id'], 'querya-dark-edited'); }); diff --git a/test/core/theme/theme_editor_loader_test.dart b/test/core/theme/theme_editor_loader_test.dart index 8d8834f5..c18f7911 100644 --- a/test/core/theme/theme_editor_loader_test.dart +++ b/test/core/theme/theme_editor_loader_test.dart @@ -71,7 +71,8 @@ void main() { group('ThemeEditorLoader', () { test('loads custom theme file manifest when available', () async { - final source = File(p.join('test/fixtures/themes', 'querya_custom_dark.json')); + final source = + File(p.join('test/fixtures/themes', 'querya_custom_dark.json')); await File(p.join(themesDir.path, 'querya_custom_dark.json')) .writeAsString(await source.readAsString()); @@ -105,7 +106,8 @@ void main() { await c.previewEditorManifest(manifest); expect(c.isEditorPreviewActive, isTrue); - expect(c.activeTheme.colorScheme.primary, parseQueryaThemeColor('#FF00AA')); + expect( + c.activeTheme.colorScheme.primary, parseQueryaThemeColor('#FF00AA')); await c.endEditorPreview(); expect(c.isEditorPreviewActive, isFalse); diff --git a/test/core/theme/theme_registry_builtin_assets_test.dart b/test/core/theme/theme_registry_builtin_assets_test.dart index 0844b354..b990cba1 100644 --- a/test/core/theme/theme_registry_builtin_assets_test.dart +++ b/test/core/theme/theme_registry_builtin_assets_test.dart @@ -32,8 +32,8 @@ void main() { late ThemeRegistryService registry; setUpAll(() async { - tempDir = - await Directory.systemTemp.createTemp('querya_builtin_theme_assets_test_'); + tempDir = await Directory.systemTemp + .createTemp('querya_builtin_theme_assets_test_'); PathProviderPlatform.instance = _FakePathProvider(tempDir.path); }); diff --git a/test/core/theme/theme_registry_legacy_import_test.dart b/test/core/theme/theme_registry_legacy_import_test.dart index 8604ffe1..35684793 100644 --- a/test/core/theme/theme_registry_legacy_import_test.dart +++ b/test/core/theme/theme_registry_legacy_import_test.dart @@ -30,8 +30,8 @@ void main() { late ThemeRegistryService registry; setUpAll(() async { - tempDir = - await Directory.systemTemp.createTemp('querya_theme_legacy_import_test_'); + tempDir = await Directory.systemTemp + .createTemp('querya_theme_legacy_import_test_'); PathProviderPlatform.instance = _FakePathProvider(tempDir.path); await LocalDb.initFfi(); }); @@ -65,9 +65,11 @@ void main() { }); group('ThemeRegistryService legacy imported migration', () { - test('exposes legacy imported theme from persisted import settings', () async { + test('exposes legacy imported theme from persisted import settings', + () async { final fixture = File('test/fixtures/themes/dark_subset.json'); - final importResult = await ThemeImportService.importFromPath(fixture.path); + final importResult = + await ThemeImportService.importFromPath(fixture.path); expect(importResult, isA()); final success = importResult as ThemeImportSuccess; @@ -93,7 +95,8 @@ void main() { test('loads legacy imported theme definition', () async { final fixture = File('test/fixtures/themes/dark_subset.json'); - final importResult = await ThemeImportService.importFromPath(fixture.path); + final importResult = + await ThemeImportService.importFromPath(fixture.path); final success = importResult as ThemeImportSuccess; await AppSettings.instance.setThemeImportedColors(success.colors); @@ -132,7 +135,8 @@ void main() { expect((result as ThemeLoadFailure).message, 'Theme file not found.'); }); - test('QueryaThemePreset.imported still applies via ThemeController', () async { + test('QueryaThemePreset.imported still applies via ThemeController', + () async { final controller = ThemeController.instance; final fixture = File('test/fixtures/themes/dark_subset.json'); final result = await controller.importThemeFromFile(fixture.path); diff --git a/test/core/theme/theme_registry_service_test.dart b/test/core/theme/theme_registry_service_test.dart index fd56ff44..775c7eb6 100644 --- a/test/core/theme/theme_registry_service_test.dart +++ b/test/core/theme/theme_registry_service_test.dart @@ -32,7 +32,8 @@ void main() { late ThemeRegistryService registry; setUpAll(() async { - tempDir = await Directory.systemTemp.createTemp('querya_theme_registry_test_'); + tempDir = + await Directory.systemTemp.createTemp('querya_theme_registry_test_'); PathProviderPlatform.instance = _FakePathProvider(tempDir.path); }); @@ -79,7 +80,8 @@ void main() { expect(metadataTheme.metadata!.pickerSubtitle, 'Querya Themes'); }); - test('includes valid custom and VS Code themes, skips broken file', () async { + test('includes valid custom and VS Code themes, skips broken file', + () async { await _copyFixture( 'querya_custom_dark.json', File(p.join(themesDir.path, 'querya_custom_dark.json')), @@ -167,7 +169,8 @@ void main() { }); test('ignores non-json theme extensions', () async { - await File(p.join(themesDir.path, 'notes.txt')).writeAsString('not a theme'); + await File(p.join(themesDir.path, 'notes.txt')) + .writeAsString('not a theme'); await _copyFixture( 'querya_custom_dark.json', File(p.join(themesDir.path, 'querya_custom_dark.json')), @@ -193,7 +196,8 @@ void main() { final success = result as ThemeLoadSuccess; expect(success.definition, definition); expect(success.theme.brightness, Brightness.dark); - expect(success.theme.colorScheme.primary, parseQueryaThemeColor('#38BDF8')); + expect( + success.theme.colorScheme.primary, parseQueryaThemeColor('#38BDF8')); }); test('loads VS Code theme successfully', () async { @@ -247,7 +251,8 @@ void main() { group('ThemeRegistryService.importThemeFile', () { test('imports custom theme into user themes directory', () async { - final source = File(p.join('test/fixtures/themes', 'querya_custom_dark.json')); + final source = + File(p.join('test/fixtures/themes', 'querya_custom_dark.json')); final result = await registry.importThemeFile(source.path); expect(result, isA()); @@ -281,7 +286,8 @@ void main() { }); test('reuses existing file when content hash matches', () async { - final source = File(p.join('test/fixtures/themes', 'querya_custom_minimal.json')); + final source = + File(p.join('test/fixtures/themes', 'querya_custom_minimal.json')); final first = await registry.importThemeFile(source.path); expect(first, isA()); final firstSuccess = first as ThemeDefinitionImportSuccess; @@ -300,8 +306,10 @@ void main() { expect(themeFiles, 1); }); - test('suffixes custom theme id when same id has different content', () async { - final source = File(p.join('test/fixtures/themes', 'querya_custom_minimal.json')); + test('suffixes custom theme id when same id has different content', + () async { + final source = + File(p.join('test/fixtures/themes', 'querya_custom_minimal.json')); final first = await registry.importThemeFile(source.path); expect(first, isA()); diff --git a/test/core/theme/theme_remote_install_policy_test.dart b/test/core/theme/theme_remote_install_policy_test.dart index b908cc00..5d1e1d0e 100644 --- a/test/core/theme/theme_remote_install_policy_test.dart +++ b/test/core/theme/theme_remote_install_policy_test.dart @@ -58,7 +58,9 @@ void main() { ); }); - test('rejects link-local, unique-local, unspecified, and multicast IPv6 addresses', () { + test( + 'rejects link-local, unique-local, unspecified, and multicast IPv6 addresses', + () { expect( ThemeRemoteInstallPolicy.isAllowedUrl( Uri.parse('https://[fe80::1]/theme.json'), diff --git a/test/core/theme/theme_remote_install_service_test.dart b/test/core/theme/theme_remote_install_service_test.dart index 8836ed7e..b5972417 100644 --- a/test/core/theme/theme_remote_install_service_test.dart +++ b/test/core/theme/theme_remote_install_service_test.dart @@ -71,8 +71,8 @@ void main() { group('ThemeRemoteInstallService', () { test('installs valid HTTPS theme content', () async { - final raw = - await File('test/fixtures/themes/querya_custom_dark.json').readAsString(); + final raw = await File('test/fixtures/themes/querya_custom_dark.json') + .readAsString(); final service = ThemeRemoteInstallService( registry, allowLocalhostInDebug: false, @@ -96,8 +96,8 @@ void main() { }); test('rejects checksum mismatch and does not write theme file', () async { - final raw = - await File('test/fixtures/themes/querya_custom_dark.json').readAsString(); + final raw = await File('test/fixtures/themes/querya_custom_dark.json') + .readAsString(); final service = ThemeRemoteInstallService( registry, allowLocalhostInDebug: false, @@ -149,8 +149,8 @@ void main() { }); test('reuses existing file when remote content hash matches', () async { - final raw = - await File('test/fixtures/themes/querya_custom_dark.json').readAsString(); + final raw = await File('test/fixtures/themes/querya_custom_dark.json') + .readAsString(); await File(p.join(themesDir.path, 'fixture-custom-dark.json')) .writeAsString(raw); @@ -174,8 +174,8 @@ void main() { group('ThemeController remote install', () { test('importRegistryThemeFromUrl activates imported theme', () async { - final raw = - await File('test/fixtures/themes/querya_custom_dark.json').readAsString(); + final raw = await File('test/fixtures/themes/querya_custom_dark.json') + .readAsString(); final controller = ThemeController.instance; await controller.load(); diff --git a/test/core/util/deep_collection_equals_test.dart b/test/core/util/deep_collection_equals_test.dart index 69938137..1e316354 100644 --- a/test/core/util/deep_collection_equals_test.dart +++ b/test/core/util/deep_collection_equals_test.dart @@ -7,15 +7,27 @@ void main() { test('compares nested maps and lists', () { const a = { 'x': 1, - 'y': [1, 2, {'z': 'ok'}], + 'y': [ + 1, + 2, + {'z': 'ok'} + ], }; const b = { 'x': 1, - 'y': [1, 2, {'z': 'ok'}], + 'y': [ + 1, + 2, + {'z': 'ok'} + ], }; const c = { 'x': 1, - 'y': [1, 2, {'z': 'nope'}], + 'y': [ + 1, + 2, + {'z': 'nope'} + ], }; expect(deepCollectionEquals(a, b), isTrue); expect(deepCollectionEquals(a, c), isFalse); diff --git a/test/features/connections/connections_panel_layout_test.dart b/test/features/connections/connections_panel_layout_test.dart index 244cbfe3..3f68e14b 100644 --- a/test/features/connections/connections_panel_layout_test.dart +++ b/test/features/connections/connections_panel_layout_test.dart @@ -51,7 +51,8 @@ class _FakePathProvider extends PathProviderPlatform { Future?> getExternalCachePaths() async => [_root]; @override - Future?> getExternalStoragePaths({StorageDirectory? type}) async => + Future?> getExternalStoragePaths( + {StorageDirectory? type}) async => [_root]; @override @@ -103,7 +104,8 @@ void main() { themeMode: ThemeMode.dark, home: material.SizedBox.expand( child: ConnectionsPanel( - onPostgresOpenSqlWorkspace: (_, {database, schema, name, kind}) {}, + onPostgresOpenSqlWorkspace: (_, + {database, schema, name, kind}) {}, ), ), ), @@ -189,7 +191,8 @@ void main() { home: material.SizedBox.expand( child: ConnectionsPanel( skipInitialDbLoadForTest: true, - onPostgresOpenSqlWorkspace: (_, {database, schema, name, kind}) {}, + onPostgresOpenSqlWorkspace: (_, + {database, schema, name, kind}) {}, ), ), ), diff --git a/test/features/connections/lazy_connection_tree_list_test.dart b/test/features/connections/lazy_connection_tree_list_test.dart index 8feed90e..cb9b2564 100644 --- a/test/features/connections/lazy_connection_tree_list_test.dart +++ b/test/features/connections/lazy_connection_tree_list_test.dart @@ -3,7 +3,8 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:querya_desktop/features/connections/connections_panel.dart'; void main() { - testWidgets('lazyConnectionTreeList uses ListView for large lists', (tester) async { + testWidgets('lazyConnectionTreeList uses ListView for large lists', + (tester) async { await tester.pumpWidget( material.MaterialApp( home: material.Scaffold( @@ -26,7 +27,8 @@ void main() { expect(find.byType(material.ListView), findsOneWidget); }); - testWidgets('lazyConnectionTreeList uses Column for small lists', (tester) async { + testWidgets('lazyConnectionTreeList uses Column for small lists', + (tester) async { await tester.pumpWidget( material.MaterialApp( home: material.Scaffold( diff --git a/test/features/main_screen/query_editor_tab_test.dart b/test/features/main_screen/query_editor_tab_test.dart index 47a80e22..acce6fb4 100644 --- a/test/features/main_screen/query_editor_tab_test.dart +++ b/test/features/main_screen/query_editor_tab_test.dart @@ -6,7 +6,8 @@ import 'package:shadcn_flutter/shadcn_flutter.dart'; import '../../support/querya_theme_test_shell.dart'; void main() { - testWidgets('QueryEditorTab applies fontSize to EditableText', (tester) async { + testWidgets('QueryEditorTab applies fontSize to EditableText', + (tester) async { await tester.pumpWidget( queryaThemeTestShell( child: const material.Scaffold( diff --git a/test/features/main_screen/results_tab_test.dart b/test/features/main_screen/results_tab_test.dart index 647f9c16..7ddb99ef 100644 --- a/test/features/main_screen/results_tab_test.dart +++ b/test/features/main_screen/results_tab_test.dart @@ -93,7 +93,8 @@ void main() { await tester.pumpAndSettle(); // Header + only visible rows (not all 500). - final dataRowWidgets = tester.widgetList(find.byType(material.Row)).length; + final dataRowWidgets = + tester.widgetList(find.byType(material.Row)).length; expect(dataRowWidgets, lessThan(80)); }); }); diff --git a/test/features/main_screen/sql_editor_chrome_test.dart b/test/features/main_screen/sql_editor_chrome_test.dart index 42887469..59adee35 100644 --- a/test/features/main_screen/sql_editor_chrome_test.dart +++ b/test/features/main_screen/sql_editor_chrome_test.dart @@ -83,7 +83,8 @@ void main() { }); group('SqlEditorChrome widget', () { - testWidgets('applies imported editor background and border', (tester) async { + testWidgets('applies imported editor background and border', + (tester) async { final queryaTheme = buildQueryaThemeFromVsCodeColors( brightness: Brightness.dark, colors: const { @@ -120,7 +121,8 @@ void main() { return d is material.BoxDecoration && d.color == const Color(0xFFAABBCC); }); - final border = (inner.decoration! as material.BoxDecoration).border as Border; + final border = + (inner.decoration! as material.BoxDecoration).border as Border; expect( border.top.color, const Color(0xFF112233).withValues(alpha: 0.5), @@ -132,7 +134,8 @@ void main() { .expand((d) => d.boxShadow ?? const []) .map((s) => s.color) .whereType() - .firstWhere((c) => c == const Color(0xFF00FFEE).withValues(alpha: 0.1)); + .firstWhere( + (c) => c == const Color(0xFF00FFEE).withValues(alpha: 0.1)); expect(outerGlow, const Color(0xFF00FFEE).withValues(alpha: 0.1)); }); }); diff --git a/test/features/main_screen/workspace_empty_hero_test.dart b/test/features/main_screen/workspace_empty_hero_test.dart index 88eeab5a..131685ca 100644 --- a/test/features/main_screen/workspace_empty_hero_test.dart +++ b/test/features/main_screen/workspace_empty_hero_test.dart @@ -26,7 +26,8 @@ void main() { expect(tapped, isTrue); }); - testWidgets('WorkspaceEmptyHero mock uses workbench surface color', (tester) async { + testWidgets('WorkspaceEmptyHero mock uses workbench surface color', + (tester) async { const surface = material.Color(0xFFABCDEF); final theme = QueryaTheme.darkDefault.copyWith( workbench: QueryaTheme.darkDefault.workbench.copyWith(surface: surface), @@ -44,12 +45,15 @@ void main() { ); await tester.pump(); - final container = tester.widgetList( - find.byType(material.Container), - ).firstWhere( - (c) => c.decoration is material.BoxDecoration && - (c.decoration! as material.BoxDecoration).color == surface, - ); + final container = tester + .widgetList( + find.byType(material.Container), + ) + .firstWhere( + (c) => + c.decoration is material.BoxDecoration && + (c.decoration! as material.BoxDecoration).color == surface, + ); expect(container.decoration, isNotNull); }); } diff --git a/test/features/main_screen/workspace_homes_and_preferences_test.dart b/test/features/main_screen/workspace_homes_and_preferences_test.dart index 5f60927f..86bf7452 100644 --- a/test/features/main_screen/workspace_homes_and_preferences_test.dart +++ b/test/features/main_screen/workspace_homes_and_preferences_test.dart @@ -38,7 +38,8 @@ class _FakePathProvider extends PathProviderPlatform { Future?> getExternalCachePaths() async => [_root]; @override - Future?> getExternalStoragePaths({StorageDirectory? type}) async => + Future?> getExternalStoragePaths( + {StorageDirectory? type}) async => [_root]; @override @@ -58,7 +59,8 @@ void main() { ); group('MysqlWorkspaceHome', () { - testWidgets('shows tabs and SQL tab exposes Execute control', (tester) async { + testWidgets('shows tabs and SQL tab exposes Execute control', + (tester) async { await tester.pumpWidget( queryaThemeTestShell( child: const material.Scaffold( @@ -85,7 +87,8 @@ void main() { }); group('Driver Manager dialog', () { - testWidgets('showDriverManagerDialog shows built-in drivers copy', (tester) async { + testWidgets('showDriverManagerDialog shows built-in drivers copy', + (tester) async { await tester.binding.setSurfaceSize(const material.Size(900, 1200)); addTearDown(() => tester.binding.setSurfaceSize(null)); @@ -120,8 +123,8 @@ void main() { late Directory tempDir; setUpAll(() async { - tempDir = await Directory.systemTemp - .createTemp('querya_prefs_dialog_test_'); + tempDir = + await Directory.systemTemp.createTemp('querya_prefs_dialog_test_'); PathProviderPlatform.instance = _FakePathProvider(tempDir.path); await LocalDb.initFfi(); await LocalDb.instance.close(); @@ -134,7 +137,8 @@ void main() { } }); - testWidgets('showPreferencesDialog shows preferences title', (tester) async { + testWidgets('showPreferencesDialog shows preferences title', + (tester) async { late material.BuildContext ctx; await tester.pumpWidget( ShadcnApp( diff --git a/test/features/main_screen/workspace_panel_layout_test.dart b/test/features/main_screen/workspace_panel_layout_test.dart index 92943780..430f6778 100644 --- a/test/features/main_screen/workspace_panel_layout_test.dart +++ b/test/features/main_screen/workspace_panel_layout_test.dart @@ -52,7 +52,8 @@ void main() { const material.Size(800, 600), queryaThemeTestShell( child: const material.SizedBox.expand( - child: WorkspacePanel(activeConnection: stubSplitWorkspaceConnection), + child: WorkspacePanel( + activeConnection: stubSplitWorkspaceConnection), ), ), ); diff --git a/test/features/mongodb/mongo_document_editor_test.dart b/test/features/mongodb/mongo_document_editor_test.dart index 6915c118..85f9ea7b 100644 --- a/test/features/mongodb/mongo_document_editor_test.dart +++ b/test/features/mongodb/mongo_document_editor_test.dart @@ -60,7 +60,8 @@ void main() { expect(find.textContaining('Invalid JSON'), findsNothing); }); - testWidgets('invalid JSON shows error banner without breaking editor', (tester) async { + testWidgets('invalid JSON shows error banner without breaking editor', + (tester) async { await pumpEditor(tester); await tester.enterText(find.byType(material.EditableText), '{not json'); await tester.pump(); @@ -81,10 +82,12 @@ void main() { final editorFinder = find.byType(QueryaCodeEditor); final container = tester.widget( - find.ancestor( - of: editorFinder, - matching: find.byType(material.Container), - ).first, + find + .ancestor( + of: editorFinder, + matching: find.byType(material.Container), + ) + .first, ); expect(container.color, bg); }); diff --git a/test/features/mysql/mysql_table_utils_test.dart b/test/features/mysql/mysql_table_utils_test.dart index ef992048..6c916591 100644 --- a/test/features/mysql/mysql_table_utils_test.dart +++ b/test/features/mysql/mysql_table_utils_test.dart @@ -8,7 +8,8 @@ void main() { }); test('allows WITH', () { - expect(isAllowedMysqlSelectQuery('WITH x AS (SELECT 1) SELECT * FROM x'), isTrue); + expect(isAllowedMysqlSelectQuery('WITH x AS (SELECT 1) SELECT * FROM x'), + isTrue); }); test('rejects multi-statement', () { diff --git a/test/features/postgresql/postgres_object_kind_test.dart b/test/features/postgresql/postgres_object_kind_test.dart index 28c9ee0f..212c6c1e 100644 --- a/test/features/postgresql/postgres_object_kind_test.dart +++ b/test/features/postgresql/postgres_object_kind_test.dart @@ -22,9 +22,12 @@ void main() { }); test('materializedView and schema metadata kinds are distinct', () { - expect(PostgresObjectKind.materializedView, isNot(PostgresObjectKind.view)); - expect(PostgresObjectKind.schemaIndexes, isNot(PostgresObjectKind.schemaTriggers)); - expect(PostgresObjectKind.databaseExtensions, isNot(PostgresObjectKind.databaseForeignData)); + expect( + PostgresObjectKind.materializedView, isNot(PostgresObjectKind.view)); + expect(PostgresObjectKind.schemaIndexes, + isNot(PostgresObjectKind.schemaTriggers)); + expect(PostgresObjectKind.databaseExtensions, + isNot(PostgresObjectKind.databaseForeignData)); }); }); } diff --git a/test/features/postgresql/postgres_table_toolbar_layout_test.dart b/test/features/postgresql/postgres_table_toolbar_layout_test.dart index f3530d51..26031109 100644 --- a/test/features/postgresql/postgres_table_toolbar_layout_test.dart +++ b/test/features/postgresql/postgres_table_toolbar_layout_test.dart @@ -40,7 +40,8 @@ void main() { }; for (final entry in sizes.entries) { - testWidgets('no overflow at ${entry.key} with long title', (tester) async { + testWidgets('no overflow at ${entry.key} with long title', + (tester) async { await expectNoLayoutOverflow(() async { await pumpWidgetWithSurfaceSize( tester, diff --git a/test/features/postgresql/postgres_table_utils_test.dart b/test/features/postgresql/postgres_table_utils_test.dart index 221ccb5b..8b59e7e8 100644 --- a/test/features/postgresql/postgres_table_utils_test.dart +++ b/test/features/postgresql/postgres_table_utils_test.dart @@ -94,7 +94,8 @@ void main() { [1, null, 'x', 2.5, true, DateTime.utc(2026, 1, 1)], ]); expect(result.length, 1); - expect(result.first, ['1', 'NULL', 'x', '2.5', 'true', '2026-01-01T00:00:00.000Z']); + expect(result.first, + ['1', 'NULL', 'x', '2.5', 'true', '2026-01-01T00:00:00.000Z']); }); }); } diff --git a/test/features/redis/redis_keys_view_test.dart b/test/features/redis/redis_keys_view_test.dart index dc8cd064..be207d40 100644 --- a/test/features/redis/redis_keys_view_test.dart +++ b/test/features/redis/redis_keys_view_test.dart @@ -8,7 +8,8 @@ import 'package:shadcn_flutter/shadcn_flutter.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); - testWidgets('RedisKeysView lists keys from ListView after load', (tester) async { + testWidgets('RedisKeysView lists keys from ListView after load', + (tester) async { final fake = RedisConnectionTestFake( firstScanKeys: const ['key_a', 'key_b'], dbSizeResult: 2, diff --git a/test/features/settings/interface_scale_slider_test.dart b/test/features/settings/interface_scale_slider_test.dart index 300c9c6d..32318211 100644 --- a/test/features/settings/interface_scale_slider_test.dart +++ b/test/features/settings/interface_scale_slider_test.dart @@ -25,7 +25,8 @@ void main() { test('UiScaleController.normalize snaps to presets unless fine mode', () { expect(UiScaleController.normalize(1.15), 1.1); - expect(UiScaleController.normalize(1.15, fine: true), closeTo(1.15, 0.001)); + expect( + UiScaleController.normalize(1.15, fine: true), closeTo(1.15, 0.001)); expect(UiScaleController.normalize(0.88), 0.9); }); }); diff --git a/test/features/settings/preferences_appearance_section_test.dart b/test/features/settings/preferences_appearance_section_test.dart index 11661ee9..d003de4f 100644 --- a/test/features/settings/preferences_appearance_section_test.dart +++ b/test/features/settings/preferences_appearance_section_test.dart @@ -125,11 +125,13 @@ void main() { expect(find.text('Reset appearance'), findsOneWidget); }); - testWidgets('shows themes folder hint and theme editor entry', (tester) async { + testWidgets('shows themes folder hint and theme editor entry', + (tester) async { await pumpSection(tester); expect( - find.textContaining('Themes are loaded from the app support themes folder'), + find.textContaining( + 'Themes are loaded from the app support themes folder'), findsOneWidget, ); expect(find.textContaining('watched automatically'), findsOneWidget); diff --git a/test/features/settings/theme_import_flow_test.dart b/test/features/settings/theme_import_flow_test.dart index 8e7638fc..df375c7f 100644 --- a/test/features/settings/theme_import_flow_test.dart +++ b/test/features/settings/theme_import_flow_test.dart @@ -123,7 +123,8 @@ void main() { await File(p.join(themesDir.path, 'fixture-custom-dark.json')).exists(), isTrue, ); - expect(await AppSettings.instance.getSelectedThemeId(), 'fixture-custom-dark'); + expect(await AppSettings.instance.getSelectedThemeId(), + 'fixture-custom-dark'); expect( await AppSettings.instance.getSelectedThemeSource(), 'filesystem', diff --git a/test/features/settings/theme_picker_button_test.dart b/test/features/settings/theme_picker_button_test.dart index c6780531..6defda18 100644 --- a/test/features/settings/theme_picker_button_test.dart +++ b/test/features/settings/theme_picker_button_test.dart @@ -71,7 +71,8 @@ void main() { expect(find.byType(material.ListView), findsNothing); }); - testWidgets('opens scrollable menu and renders visible rows', (tester) async { + testWidgets('opens scrollable menu and renders visible rows', + (tester) async { final themes = _fakeThemes(60); await tester.pumpWidget( @@ -224,7 +225,8 @@ void main() { ); }); - testWidgets('builds only a visible subset of 60 theme rows', (tester) async { + testWidgets('builds only a visible subset of 60 theme rows', + (tester) async { final themes = _fakeThemes(60); await tester.pumpWidget( @@ -343,7 +345,8 @@ void main() { ); }); - testWidgets('shows empty message when filter has no results', (tester) async { + testWidgets('shows empty message when filter has no results', + (tester) async { await tester.pumpWidget( queryaThemeTestShell( child: material.Scaffold( diff --git a/test/memory_secrets_backend.dart b/test/memory_secrets_backend.dart index 75a6250a..1eb251a1 100644 --- a/test/memory_secrets_backend.dart +++ b/test/memory_secrets_backend.dart @@ -1,7 +1,8 @@ import 'package:querya_desktop/core/storage/connection_secrets_store.dart'; /// Shared in-memory backend wired from `test/flutter_test_config.dart`. -final MemorySecretsStorageBackend testMemorySecrets = MemorySecretsStorageBackend(); +final MemorySecretsStorageBackend testMemorySecrets = + MemorySecretsStorageBackend(); /// In-memory secrets backend for `flutter test` (no OS keychain / libsecret). class MemorySecretsStorageBackend implements SecretsStorageBackend { diff --git a/test/shared/querya_dropdown_test.dart b/test/shared/querya_dropdown_test.dart index ec2fa07f..7ab3c013 100644 --- a/test/shared/querya_dropdown_test.dart +++ b/test/shared/querya_dropdown_test.dart @@ -8,7 +8,8 @@ void main() { group('QueryaDropdownTokens', () { test('uses design-system defaults from issue #89', () { expect(QueryaDropdownTokens.triggerHeight, 36.0); - expect(QueryaDropdownTokens.menuAlignmentOffset, const material.Offset(0, 4)); + expect(QueryaDropdownTokens.menuAlignmentOffset, + const material.Offset(0, 4)); expect(QueryaDropdownTokens.menuMaxHeight, 300.0); expect(QueryaDropdownTokens.menuBorderRadius, 6.0); expect(QueryaDropdownTokens.fontSize, 14.0); @@ -16,7 +17,8 @@ void main() { }); group('QueryaDropdown', () { - testWidgets('builds MenuAnchor with current label and chevron', (tester) async { + testWidgets('builds MenuAnchor with current label and chevron', + (tester) async { await tester.pumpWidget( queryaThemeTestShell( child: material.Scaffold( @@ -35,7 +37,8 @@ void main() { expect(find.byType(material.MenuAnchor), findsOneWidget); expect(find.text('Beta'), findsOneWidget); - expect(find.byIcon(material.Icons.keyboard_arrow_down_rounded), findsOneWidget); + expect(find.byIcon(material.Icons.keyboard_arrow_down_rounded), + findsOneWidget); final box = tester.getSize(find.byType(material.AnimatedContainer).first); expect(box.height, QueryaDropdownTokens.triggerHeight); diff --git a/third_party/refresh_rate/lib/src/generated/refresh_rate_api.g.dart b/third_party/refresh_rate/lib/src/generated/refresh_rate_api.g.dart index 10763859..530dd070 100644 --- a/third_party/refresh_rate/lib/src/generated/refresh_rate_api.g.dart +++ b/third_party/refresh_rate/lib/src/generated/refresh_rate_api.g.dart @@ -17,7 +17,8 @@ PlatformException _createConnectionError(String channelName) { ); } -List wrapResponse({Object? result, PlatformException? error, bool empty = false}) { +List wrapResponse( + {Object? result, PlatformException? error, bool empty = false}) { if (empty) { return []; } @@ -108,7 +109,6 @@ class DisplayInfoMessage { } } - class _PigeonCodec extends StandardMessageCodec { const _PigeonCodec(); @override @@ -116,7 +116,7 @@ class _PigeonCodec extends StandardMessageCodec { if (value is int) { buffer.putUint8(4); buffer.putInt64(value); - } else if (value is DisplayInfoMessage) { + } else if (value is DisplayInfoMessage) { buffer.putUint8(129); writeValue(buffer, value.encode()); } else { @@ -127,7 +127,7 @@ class _PigeonCodec extends StandardMessageCodec { @override Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { - case 129: + case 129: return DisplayInfoMessage.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); @@ -139,9 +139,11 @@ class RefreshRateHostApi { /// Constructor for [RefreshRateHostApi]. The [binaryMessenger] named argument is /// available for dependency injection. If it is left null, the default /// BinaryMessenger will be used which routes to the host platform. - RefreshRateHostApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) + RefreshRateHostApi( + {BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) : pigeonVar_binaryMessenger = binaryMessenger, - pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + pigeonVar_messageChannelSuffix = + messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; final BinaryMessenger? pigeonVar_binaryMessenger; static const MessageCodec pigeonChannelCodec = _PigeonCodec(); @@ -149,8 +151,10 @@ class RefreshRateHostApi { final String pigeonVar_messageChannelSuffix; Future getDisplayInfo() async { - final String pigeonVar_channelName = 'dev.flutter.pigeon.refresh_rate.RefreshRateHostApi.getDisplayInfo$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + final String pigeonVar_channelName = + 'dev.flutter.pigeon.refresh_rate.RefreshRateHostApi.getDisplayInfo$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, @@ -176,8 +180,10 @@ class RefreshRateHostApi { } Future enable() async { - final String pigeonVar_channelName = 'dev.flutter.pigeon.refresh_rate.RefreshRateHostApi.enable$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + final String pigeonVar_channelName = + 'dev.flutter.pigeon.refresh_rate.RefreshRateHostApi.enable$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, @@ -198,8 +204,10 @@ class RefreshRateHostApi { } Future disable() async { - final String pigeonVar_channelName = 'dev.flutter.pigeon.refresh_rate.RefreshRateHostApi.disable$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + final String pigeonVar_channelName = + 'dev.flutter.pigeon.refresh_rate.RefreshRateHostApi.disable$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, @@ -220,8 +228,10 @@ class RefreshRateHostApi { } Future preferMax() async { - final String pigeonVar_channelName = 'dev.flutter.pigeon.refresh_rate.RefreshRateHostApi.preferMax$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + final String pigeonVar_channelName = + 'dev.flutter.pigeon.refresh_rate.RefreshRateHostApi.preferMax$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, @@ -242,8 +252,10 @@ class RefreshRateHostApi { } Future preferDefault() async { - final String pigeonVar_channelName = 'dev.flutter.pigeon.refresh_rate.RefreshRateHostApi.preferDefault$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + final String pigeonVar_channelName = + 'dev.flutter.pigeon.refresh_rate.RefreshRateHostApi.preferDefault$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, @@ -264,8 +276,10 @@ class RefreshRateHostApi { } Future matchContent(double fps) async { - final String pigeonVar_channelName = 'dev.flutter.pigeon.refresh_rate.RefreshRateHostApi.matchContent$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + final String pigeonVar_channelName = + 'dev.flutter.pigeon.refresh_rate.RefreshRateHostApi.matchContent$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, @@ -286,8 +300,10 @@ class RefreshRateHostApi { } Future boost(int durationMs) async { - final String pigeonVar_channelName = 'dev.flutter.pigeon.refresh_rate.RefreshRateHostApi.boost$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + final String pigeonVar_channelName = + 'dev.flutter.pigeon.refresh_rate.RefreshRateHostApi.boost$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, @@ -308,14 +324,16 @@ class RefreshRateHostApi { } Future setCategory(int categoryIndex) async { - final String pigeonVar_channelName = 'dev.flutter.pigeon.refresh_rate.RefreshRateHostApi.setCategory$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + final String pigeonVar_channelName = + 'dev.flutter.pigeon.refresh_rate.RefreshRateHostApi.setCategory$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final List? pigeonVar_replyList = - await pigeonVar_channel.send([categoryIndex]) as List?; + final List? pigeonVar_replyList = await pigeonVar_channel + .send([categoryIndex]) as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -330,8 +348,10 @@ class RefreshRateHostApi { } Future setTouchBoost(bool enabled) async { - final String pigeonVar_channelName = 'dev.flutter.pigeon.refresh_rate.RefreshRateHostApi.setTouchBoost$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + final String pigeonVar_channelName = + 'dev.flutter.pigeon.refresh_rate.RefreshRateHostApi.setTouchBoost$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, @@ -352,8 +372,10 @@ class RefreshRateHostApi { } Future isSupported() async { - final String pigeonVar_channelName = 'dev.flutter.pigeon.refresh_rate.RefreshRateHostApi.isSupported$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + final String pigeonVar_channelName = + 'dev.flutter.pigeon.refresh_rate.RefreshRateHostApi.isSupported$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, @@ -384,18 +406,26 @@ abstract class RefreshRateFlutterApi { void onDisplayInfoChanged(DisplayInfoMessage info); - static void setUp(RefreshRateFlutterApi? api, {BinaryMessenger? binaryMessenger, String messageChannelSuffix = '',}) { - messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + static void setUp( + RefreshRateFlutterApi? api, { + BinaryMessenger? binaryMessenger, + String messageChannelSuffix = '', + }) { + messageChannelSuffix = + messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; { - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( - 'dev.flutter.pigeon.refresh_rate.RefreshRateFlutterApi.onDisplayInfoChanged$messageChannelSuffix', pigeonChannelCodec, + final BasicMessageChannel< + Object?> pigeonVar_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.refresh_rate.RefreshRateFlutterApi.onDisplayInfoChanged$messageChannelSuffix', + pigeonChannelCodec, binaryMessenger: binaryMessenger); if (api == null) { pigeonVar_channel.setMessageHandler(null); } else { pigeonVar_channel.setMessageHandler((Object? message) async { assert(message != null, - 'Argument for dev.flutter.pigeon.refresh_rate.RefreshRateFlutterApi.onDisplayInfoChanged was null.'); + 'Argument for dev.flutter.pigeon.refresh_rate.RefreshRateFlutterApi.onDisplayInfoChanged was null.'); final List args = (message as List?)!; final DisplayInfoMessage? arg_info = (args[0] as DisplayInfoMessage?); assert(arg_info != null, @@ -405,8 +435,9 @@ abstract class RefreshRateFlutterApi { return wrapResponse(empty: true); } on PlatformException catch (e) { return wrapResponse(error: e); - } catch (e) { - return wrapResponse(error: PlatformException(code: 'error', message: e.toString())); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); } }); } diff --git a/third_party/refresh_rate/lib/src/models/display_info.dart b/third_party/refresh_rate/lib/src/models/display_info.dart index 53b630bb..d7b8429f 100644 --- a/third_party/refresh_rate/lib/src/models/display_info.dart +++ b/third_party/refresh_rate/lib/src/models/display_info.dart @@ -80,7 +80,8 @@ class DisplayInfo { currentRate: msg.currentRate ?? 60.0, maxRate: msg.maxRate ?? 60.0, minRate: msg.minRate ?? 60.0, - supportedRates: msg.supportedRates?.whereType().toList() ?? const [60.0], + supportedRates: + msg.supportedRates?.whereType().toList() ?? const [60.0], isVariableRefreshRate: msg.isVariableRefreshRate ?? false, engineTargetRate: msg.engineTargetRate ?? 60.0, iosProMotionEnabled: msg.iosProMotionEnabled, @@ -96,14 +97,14 @@ class DisplayInfo { /// A safe fallback [DisplayInfo] used before the first [RefreshRate.refresh] /// call completes. Assumes a standard 60 Hz non-VRR display. static const DisplayInfo fallback = DisplayInfo( - currentRate: 60.0, - maxRate: 60.0, - minRate: 60.0, - supportedRates: [60.0], - isVariableRefreshRate: false, - engineTargetRate: 60.0, - thermalState: ThermalState.unknown, - ); + currentRate: 60.0, + maxRate: 60.0, + minRate: 60.0, + supportedRates: [60.0], + isVariableRefreshRate: false, + engineTargetRate: 60.0, + thermalState: ThermalState.unknown, + ); @override bool operator ==(Object other) => diff --git a/third_party/refresh_rate/lib/src/models/enums.dart b/third_party/refresh_rate/lib/src/models/enums.dart index 57900f09..e9339415 100644 --- a/third_party/refresh_rate/lib/src/models/enums.dart +++ b/third_party/refresh_rate/lib/src/models/enums.dart @@ -18,11 +18,16 @@ enum RateCategory { /// Returns the [RateCategory] matching [index], or [none] if unknown. static RateCategory fromIndex(int? index) { switch (index) { - case 0: return none; - case 1: return low; - case 2: return normal; - case 3: return high; - default: return none; + case 0: + return none; + case 1: + return low; + case 2: + return normal; + case 3: + return high; + default: + return none; } } } @@ -50,11 +55,16 @@ enum ThermalState { /// Returns the [ThermalState] matching [index], or [unknown] if not found. static ThermalState fromIndex(int? index) { switch (index) { - case 0: return nominal; - case 1: return fair; - case 2: return serious; - case 3: return critical; - default: return unknown; + case 0: + return nominal; + case 1: + return fair; + case 2: + return serious; + case 3: + return critical; + default: + return unknown; } } } diff --git a/third_party/refresh_rate/lib/src/models/session_report.dart b/third_party/refresh_rate/lib/src/models/session_report.dart index 378a6ca3..8ceee04c 100644 --- a/third_party/refresh_rate/lib/src/models/session_report.dart +++ b/third_party/refresh_rate/lib/src/models/session_report.dart @@ -179,6 +179,7 @@ class SessionReport { } return s; } + final headers = m.keys.join(','); final values = m.values.map(esc).join(','); return '$headers\n$values'; diff --git a/third_party/refresh_rate/lib/src/refresh_rate.dart b/third_party/refresh_rate/lib/src/refresh_rate.dart index a0a2fe8d..ef7e1fb4 100644 --- a/third_party/refresh_rate/lib/src/refresh_rate.dart +++ b/third_party/refresh_rate/lib/src/refresh_rate.dart @@ -99,8 +99,7 @@ class RefreshRate { /// /// Commonly used when a gesture or animation starts — the display snaps to /// high-Hz immediately and returns to the preferred rate after the duration. - static void boost(Duration duration) => - _api.boost(duration.inMilliseconds); + static void boost(Duration duration) => _api.boost(duration.inMilliseconds); /// Boosts the refresh rate for the lifetime of an [AnimationController]. /// @@ -113,7 +112,7 @@ class RefreshRate { status == AnimationStatus.reverse) { _api.boost(controller.duration?.inMilliseconds ?? 500); } else if (status == AnimationStatus.completed || - status == AnimationStatus.dismissed) { + status == AnimationStatus.dismissed) { controller.removeStatusListener(statusListener); } }; @@ -185,14 +184,12 @@ class RefreshRate { /// /// Returns `false` until a successful [refresh] is completed and the device /// is an iPhone/iPad with a ProMotion display. - static bool get isProMotionReady => - _cachedInfo.iosProMotionEnabled == true; + static bool get isProMotionReady => _cachedInfo.iosProMotionEnabled == true; /// Whether the device is currently in Low Power Mode. /// /// Defaults to `false` when the value cannot be determined. - static bool get isLowPowerMode => - _cachedInfo.isLowPowerMode ?? false; + static bool get isLowPowerMode => _cachedInfo.isLowPowerMode ?? false; /// The current thermal state of the device. /// @@ -216,6 +213,7 @@ class RefreshRate { void handle(DisplayInfoMessage msg) { _cachedInfo = DisplayInfo.fromMessage(msg); } + void handleError(Object e) { assert(() { debugPrint('RefreshRate: _refreshInfo error: $e'); diff --git a/third_party/refresh_rate/lib/src/refresh_rate_api_adapter.dart b/third_party/refresh_rate/lib/src/refresh_rate_api_adapter.dart index 3152293e..7fce754b 100644 --- a/third_party/refresh_rate/lib/src/refresh_rate_api_adapter.dart +++ b/third_party/refresh_rate/lib/src/refresh_rate_api_adapter.dart @@ -10,22 +10,31 @@ import 'generated/refresh_rate_api.g.dart'; abstract class RefreshRateApiAdapter { /// Fetches the latest display configuration. FutureOr getDisplayInfo(); + /// Enables High Refresh Rate overrides. FutureOr enable(); + /// Disables High Refresh Rate overrides. FutureOr disable(); + /// Requests the highest possible display refresh rate. FutureOr preferMax(); + /// Resets to the system default refresh rate. FutureOr preferDefault(); + /// Attempts to set the display refresh rate to match [fps]. FutureOr matchContent(double fps); + /// Temporarily boosts the refresh rate for [durationMs]. FutureOr boost(int durationMs); + /// Sets the refresh rate based on a given category. FutureOr setCategory(int categoryIndex); + /// Enables or disables automatic refresh rate boost on touch interactions. FutureOr setTouchBoost(bool enabled); + /// Checks whether the refresh rate overrides are supported by the platform. FutureOr isSupported(); } @@ -59,7 +68,8 @@ class PigeonRefreshRateApiAdapter implements RefreshRateApiAdapter { Future boost(int durationMs) => _pigeon.boost(durationMs); @override - Future setCategory(int categoryIndex) => _pigeon.setCategory(categoryIndex); + Future setCategory(int categoryIndex) => + _pigeon.setCategory(categoryIndex); @override Future setTouchBoost(bool enabled) => _pigeon.setTouchBoost(enabled); diff --git a/third_party/refresh_rate/lib/src/verification/fps_tracker.dart b/third_party/refresh_rate/lib/src/verification/fps_tracker.dart index 6bf58d03..ff44590d 100644 --- a/third_party/refresh_rate/lib/src/verification/fps_tracker.dart +++ b/third_party/refresh_rate/lib/src/verification/fps_tracker.dart @@ -5,12 +5,16 @@ import 'package:flutter/scheduler.dart'; class FrameSample { /// Time spent in the UI build thread, in microseconds. final int buildUs; + /// Time spent in the raster thread, in microseconds. final int rasterUs; + /// Total time to render the frame, in microseconds. final int totalUs; + /// Vsync timestamp, in microseconds. final int vsyncUs; + /// System time when the sample was recorded. final DateTime timestamp; @@ -61,7 +65,8 @@ class FpsTracker { /// FPS over the last [n] frames — used by the live overlay. double recentFps([int n = 60]) { if (_samples.length < 2) return 0.0; - final window = _samples.length <= n ? _samples : _samples.sublist(_samples.length - n); + final window = + _samples.length <= n ? _samples : _samples.sublist(_samples.length - n); return _fpsFromWindow(window); } @@ -75,19 +80,25 @@ class FpsTracker { /// Average duration of the UI build phase across all samples, in ms. double get avgBuildMs { if (_samples.isEmpty) return 0.0; - return _samples.fold(0, (s, f) => s + f.buildUs) / _samples.length / 1000.0; + return _samples.fold(0, (s, f) => s + f.buildUs) / + _samples.length / + 1000.0; } /// Average duration of the raster phase across all samples, in ms. double get avgRasterMs { if (_samples.isEmpty) return 0.0; - return _samples.fold(0, (s, f) => s + f.rasterUs) / _samples.length / 1000.0; + return _samples.fold(0, (s, f) => s + f.rasterUs) / + _samples.length / + 1000.0; } /// Average total duration (build + raster) across all samples, in ms. double get avgTotalMs { if (_samples.isEmpty) return 0.0; - return _samples.fold(0, (s, f) => s + f.totalUs) / _samples.length / 1000.0; + return _samples.fold(0, (s, f) => s + f.totalUs) / + _samples.length / + 1000.0; } /// Worst 1-percentile frame rate. @@ -97,7 +108,8 @@ class FpsTracker { final cutoff = (sorted.length * 0.99).floor(); final slowSamples = sorted.skip(cutoff).toList(); if (slowSamples.isEmpty) return avgFps; - final avgSlowUs = slowSamples.fold(0, (s, v) => s + v) / slowSamples.length; + final avgSlowUs = + slowSamples.fold(0, (s, v) => s + v) / slowSamples.length; return avgSlowUs > 0 ? 1000000 / avgSlowUs : 0.0; } @@ -108,7 +120,8 @@ class FpsTracker { final cutoff = (sorted.length * 0.95).floor(); final slowSamples = sorted.skip(cutoff).toList(); if (slowSamples.isEmpty) return avgFps; - final avgSlowUs = slowSamples.fold(0, (s, v) => s + v) / slowSamples.length; + final avgSlowUs = + slowSamples.fold(0, (s, v) => s + v) / slowSamples.length; return avgSlowUs > 0 ? 1000000 / avgSlowUs : 0.0; } diff --git a/third_party/refresh_rate/lib/src/verification/overlay_controller.dart b/third_party/refresh_rate/lib/src/verification/overlay_controller.dart index 048da7e7..b79893d3 100644 --- a/third_party/refresh_rate/lib/src/verification/overlay_controller.dart +++ b/third_party/refresh_rate/lib/src/verification/overlay_controller.dart @@ -8,6 +8,7 @@ enum _OverlayMode { none, fps, hz, full } /// Manages the visibility and rendering of debug overlays. class OverlayController { OverlayController._(); + /// The singleton instance of [OverlayController]. static final instance = OverlayController._(); @@ -21,8 +22,10 @@ class OverlayController { /// Shows a compact FPS overlay. void showFPS() => _show(_OverlayMode.fps); + /// Shows a compact Hz (refresh rate) overlay. void showHz() => _show(_OverlayMode.hz); + /// Shows the full debug overlay with detailed metrics. void showFull() => _show(_OverlayMode.full); @@ -61,6 +64,7 @@ class OverlayController { } element.visitChildren(visit); } + root.visitChildren(visit); return found; } diff --git a/third_party/refresh_rate/lib/src/verification/overlay_widgets.dart b/third_party/refresh_rate/lib/src/verification/overlay_widgets.dart index 823b772d..273807e4 100644 --- a/third_party/refresh_rate/lib/src/verification/overlay_widgets.dart +++ b/third_party/refresh_rate/lib/src/verification/overlay_widgets.dart @@ -53,6 +53,7 @@ mixin _OverlayStateMixin on State { class FpsOverlayWidget extends StatefulWidget { /// The [FpsTracker] providing frame timings. final FpsTracker tracker; + /// Creates a [FpsOverlayWidget]. const FpsOverlayWidget({super.key, required this.tracker}); @override @@ -84,6 +85,7 @@ class _FpsOverlayWidgetState extends State class HzOverlayWidget extends StatefulWidget { /// The [FpsTracker] (unused but required for generic widget creation). final FpsTracker tracker; + /// Creates a [HzOverlayWidget]. const HzOverlayWidget({super.key, required this.tracker}); @override @@ -115,6 +117,7 @@ class _HzOverlayWidgetState extends State class FullOverlayWidget extends StatefulWidget { /// The [FpsTracker] providing frame timings. final FpsTracker tracker; + /// Creates a [FullOverlayWidget]. const FullOverlayWidget({super.key, required this.tracker}); @override @@ -151,8 +154,10 @@ class _FullOverlayWidgetState extends State fontFeatures: const [FontFeature.tabularFigures()], ), ), - Text('build ${buildMs.toStringAsFixed(1)}ms raster ${rasterMs.toStringAsFixed(1)}ms'), - Text('budget ${budgetMs.toStringAsFixed(1)}ms @ ${targetHz.toStringAsFixed(0)}Hz'), + Text( + 'build ${buildMs.toStringAsFixed(1)}ms raster ${rasterMs.toStringAsFixed(1)}ms'), + Text( + 'budget ${budgetMs.toStringAsFixed(1)}ms @ ${targetHz.toStringAsFixed(0)}Hz'), if (RefreshRate.isLowPowerMode) const Text( '\u26a1 Low Power', diff --git a/third_party/refresh_rate/lib/src/verification/session_scorer.dart b/third_party/refresh_rate/lib/src/verification/session_scorer.dart index 12494cd8..3a56b00c 100644 --- a/third_party/refresh_rate/lib/src/verification/session_scorer.dart +++ b/third_party/refresh_rate/lib/src/verification/session_scorer.dart @@ -58,7 +58,8 @@ abstract class SessionScorer { ); } - static Verdict _computeVerdict(double avgFps, double targetHz, double missedPct) { + static Verdict _computeVerdict( + double avgFps, double targetHz, double missedPct) { if (avgFps == 0) return Verdict.inconclusive; final hitRate = avgFps / targetHz; if (hitRate >= 0.95 && missedPct < 2) return Verdict.excellent; @@ -76,7 +77,8 @@ abstract class SessionScorer { required ThermalState thermalState, }) { if (isLowPowerMode == true) return Bottleneck.powerLimited; - if (thermalState == ThermalState.serious || thermalState == ThermalState.critical) { + if (thermalState == ThermalState.serious || + thermalState == ThermalState.critical) { return Bottleneck.thermalLimited; } final budgetMs = 1000.0 / targetHz; @@ -86,7 +88,8 @@ abstract class SessionScorer { } // Below 85%: check for build vs raster bottleneck if (avgFps < targetHz * 0.85) { - if (avgBuild > avgRaster && avgBuild > budgetMs * 0.5) return Bottleneck.buildBound; + if (avgBuild > avgRaster && avgBuild > budgetMs * 0.5) + return Bottleneck.buildBound; if (avgRaster > budgetMs * 0.5) return Bottleneck.rasterBound; } return Bottleneck.none; diff --git a/third_party/refresh_rate/lib/src/web/raf_hz_detector.dart b/third_party/refresh_rate/lib/src/web/raf_hz_detector.dart index 70f2fec1..ba6bfefe 100644 --- a/third_party/refresh_rate/lib/src/web/raf_hz_detector.dart +++ b/third_party/refresh_rate/lib/src/web/raf_hz_detector.dart @@ -54,7 +54,18 @@ class RafHzDetector { /// Snaps a raw Hz measurement to the nearest common display refresh rate /// when the raw value is within 3% tolerance. static double _snapToCommonHz(double raw) { - const commonRates = [30.0, 48.0, 60.0, 72.0, 90.0, 120.0, 144.0, 165.0, 240.0, 360.0]; + const commonRates = [ + 30.0, + 48.0, + 60.0, + 72.0, + 90.0, + 120.0, + 144.0, + 165.0, + 240.0, + 360.0 + ]; for (final rate in commonRates) { if ((raw - rate).abs() / rate < 0.03) return rate; } diff --git a/third_party/refresh_rate/pigeons/refresh_rate_api.dart b/third_party/refresh_rate/pigeons/refresh_rate_api.dart index 725ec7b3..9b34f155 100644 --- a/third_party/refresh_rate/pigeons/refresh_rate_api.dart +++ b/third_party/refresh_rate/pigeons/refresh_rate_api.dart @@ -6,7 +6,8 @@ import 'package:pigeon/pigeon.dart'; kotlinOut: 'android/src/main/kotlin/in/qoder/refresh_rate/generated/RefreshRateApi.kt', kotlinOptions: KotlinOptions(package: 'in.qoder.refresh_rate.generated'), - swiftOut: 'ios/refresh_rate/Sources/refresh_rate/generated/RefreshRateApi.swift', + swiftOut: + 'ios/refresh_rate/Sources/refresh_rate/generated/RefreshRateApi.swift', swiftOptions: SwiftOptions(), cppHeaderOut: 'windows/refresh_rate_api.g.h', cppSourceOut: 'windows/refresh_rate_api.g.cpp', diff --git a/third_party/refresh_rate/test/fps_tracker_test.dart b/third_party/refresh_rate/test/fps_tracker_test.dart index fe4fa4dc..17c420c6 100644 --- a/third_party/refresh_rate/test/fps_tracker_test.dart +++ b/third_party/refresh_rate/test/fps_tracker_test.dart @@ -144,9 +144,13 @@ void main() { test('starts in running state', () { WidgetsFlutterBinding.ensureInitialized(); final info = DisplayInfo( - currentRate: 120.0, maxRate: 120.0, minRate: 60.0, - supportedRates: [60.0, 120.0], isVariableRefreshRate: true, - engineTargetRate: 120.0, thermalState: ThermalState.nominal, + currentRate: 120.0, + maxRate: 120.0, + minRate: 60.0, + supportedRates: [60.0, 120.0], + isVariableRefreshRate: true, + engineTargetRate: 120.0, + thermalState: ThermalState.nominal, ); final session = RefreshRateSession.create('test', info); expect(session.state, SessionState.running); @@ -156,9 +160,13 @@ void main() { test('end() returns a SessionReport', () async { WidgetsFlutterBinding.ensureInitialized(); final info = DisplayInfo( - currentRate: 120.0, maxRate: 120.0, minRate: 60.0, - supportedRates: [60.0, 120.0], isVariableRefreshRate: true, - engineTargetRate: 120.0, thermalState: ThermalState.nominal, + currentRate: 120.0, + maxRate: 120.0, + minRate: 60.0, + supportedRates: [60.0, 120.0], + isVariableRefreshRate: true, + engineTargetRate: 120.0, + thermalState: ThermalState.nominal, ); final session = RefreshRateSession.create('scroll_test', info); final report = await session.end(); @@ -183,17 +191,17 @@ List _fakeTimingSequence(List durationsUs) { List _fakeTimings(int count, int intervalUs) => _fakeTimingSequence(List.filled(count, intervalUs)); - - class _MockFrameTiming implements FrameTiming { final int _totalUs; final int _vsyncUs; _MockFrameTiming(this._totalUs, this._vsyncUs); @override - Duration get buildDuration => Duration(microseconds: (_totalUs * 0.6).toInt()); + Duration get buildDuration => + Duration(microseconds: (_totalUs * 0.6).toInt()); @override - Duration get rasterDuration => Duration(microseconds: (_totalUs * 0.4).toInt()); + Duration get rasterDuration => + Duration(microseconds: (_totalUs * 0.4).toInt()); @override Duration get totalSpan => Duration(microseconds: _totalUs); @override diff --git a/third_party/refresh_rate/test/models_test.dart b/third_party/refresh_rate/test/models_test.dart index 3901a575..a9894511 100644 --- a/third_party/refresh_rate/test/models_test.dart +++ b/third_party/refresh_rate/test/models_test.dart @@ -30,8 +30,11 @@ void main() { test('handles null thermalStateIndex as unknown', () { final msg = DisplayInfoMessage( - currentRate: 60.0, maxRate: 60.0, minRate: 60.0, - supportedRates: [60.0], isVariableRefreshRate: false, + currentRate: 60.0, + maxRate: 60.0, + minRate: 60.0, + supportedRates: [60.0], + isVariableRefreshRate: false, engineTargetRate: 60.0, thermalStateIndex: null, ); diff --git a/third_party/refresh_rate/test/refresh_rate_api_test.dart b/third_party/refresh_rate/test/refresh_rate_api_test.dart index a6155aa0..9408ae93 100644 --- a/third_party/refresh_rate/test/refresh_rate_api_test.dart +++ b/third_party/refresh_rate/test/refresh_rate_api_test.dart @@ -7,20 +7,57 @@ class _FakeHostApi implements RefreshRateApiAdapter { final calls = []; @override DisplayInfoMessage getDisplayInfo() => DisplayInfoMessage( - currentRate: 120.0, maxRate: 120.0, minRate: 60.0, - supportedRates: [60.0, 120.0], isVariableRefreshRate: true, - engineTargetRate: 120.0, thermalStateIndex: 0, + currentRate: 120.0, + maxRate: 120.0, + minRate: 60.0, + supportedRates: [60.0, 120.0], + isVariableRefreshRate: true, + engineTargetRate: 120.0, + thermalStateIndex: 0, ); - @override void enable() { calls.add('enable'); } - @override void disable() { calls.add('disable'); } - @override void preferMax() { calls.add('preferMax'); } - @override void preferDefault() { calls.add('preferDefault'); } - @override void matchContent(double fps) { calls.add('matchContent:$fps'); } - @override void boost(int durationMs) { calls.add('boost:$durationMs'); } - @override void setCategory(int c) { calls.add('setCategory:$c'); } - @override void setTouchBoost(bool e) { calls.add('setTouchBoost:$e'); } - @override bool isSupported() => true; + @override + void enable() { + calls.add('enable'); + } + + @override + void disable() { + calls.add('disable'); + } + + @override + void preferMax() { + calls.add('preferMax'); + } + + @override + void preferDefault() { + calls.add('preferDefault'); + } + + @override + void matchContent(double fps) { + calls.add('matchContent:$fps'); + } + + @override + void boost(int durationMs) { + calls.add('boost:$durationMs'); + } + + @override + void setCategory(int c) { + calls.add('setCategory:$c'); + } + + @override + void setTouchBoost(bool e) { + calls.add('setTouchBoost:$e'); + } + + @override + bool isSupported() => true; } void main() { diff --git a/third_party/shadcn_flutter/lib/src/components/display/divider.dart b/third_party/shadcn_flutter/lib/src/components/display/divider.dart index 22535959..c9bafdfd 100644 --- a/third_party/shadcn_flutter/lib/src/components/display/divider.dart +++ b/third_party/shadcn_flutter/lib/src/components/display/divider.dart @@ -251,8 +251,7 @@ class Divider extends StatelessWidget implements PreferredSizeWidget { defaultValue: AxisAlignment.center, ).resolve(textDirection); if (child != null) { - final clampedAlignmentValue = - childAlignment.value.clamp(-1.0, 1.0); + final clampedAlignmentValue = childAlignment.value.clamp(-1.0, 1.0); final leftRatio = (clampedAlignmentValue + 1) / 2; final rightRatio = 1 - leftRatio; final leftFlex = (leftRatio * 1000).round(); diff --git a/third_party/shadcn_flutter/lib/src/components/layout/icon.dart b/third_party/shadcn_flutter/lib/src/components/layout/icon.dart index 3e6fdcb6..ccd06fd6 100644 --- a/third_party/shadcn_flutter/lib/src/components/layout/icon.dart +++ b/third_party/shadcn_flutter/lib/src/components/layout/icon.dart @@ -1,4 +1,5 @@ import 'package:shadcn_flutter/shadcn_flutter.dart'; + /// Provides themed icon container widgets for shadcn_flutter components. /// /// Includes [IconContainerTheme] and [IconContainer] for styling icons with background, padding, and border radius. @@ -6,10 +7,13 @@ import 'package:shadcn_flutter/shadcn_flutter.dart'; class IconContainerTheme extends ComponentThemeData { /// Background color for the icon container. final Color? backgroundColor; + /// Color for the icon inside the container. final Color? iconColor; + /// Padding inside the icon container. final EdgeInsetsGeometry? padding; + /// Border radius for the icon container. final BorderRadius? borderRadius; @@ -87,14 +91,19 @@ class IconContainerTheme extends ComponentThemeData { class IconContainer extends StatelessWidget { /// The icon widget to display. final Widget icon; + /// Padding inside the container. final EdgeInsetsGeometry? padding; + /// Border radius for the container. final BorderRadius? borderRadius; + /// Background color for the container. final Color? backgroundColor; + /// Color for the icon. final Color? iconColor; + /// Creates an [IconContainer]. /// /// Parameters: diff --git a/third_party/shadcn_flutter/lib/src/util.dart b/third_party/shadcn_flutter/lib/src/util.dart index 18094189..a9d731b2 100644 --- a/third_party/shadcn_flutter/lib/src/util.dart +++ b/third_party/shadcn_flutter/lib/src/util.dart @@ -2198,6 +2198,7 @@ class ContextCallbackAction extends ContextAction { class ErrorFilter extends StatefulWidget { /// Child widget to wrap with error filtering. final Widget child; + /// Creates an [ErrorFilter]. /// /// Parameters: