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 214ab03..83e0201 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 916e8de..153f368 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 dc2566f..631af8d 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 1b968db..752cc0f 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 03d1949..73c7143 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 5cc2dac..8f9de12 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 94760aa..9a65026 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 377de8c..7e9cb9f 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 eb46c8e..12e04c7 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 a0fece9..ca69d17 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 7491d6d..c464d52 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 247ce9d..1c450cb 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 5f837e0..96dd97c 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 05e783a..0919cf0 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 09f7531..a37758e 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 332f57c..f89696f 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 bfac6e3..07ff844 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 38aa0c9..8627bc3 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 026f191..543c2cf 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 7142b5a..dce9dce 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 6e02914..914a1b8 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 074272d..9d9390a 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 66e665f..acb4832 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 b9f5f6d..966ff29 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 b35c72b..8f04850 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 6a4f498..b735eec 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 bfbf843..de3097b 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 b8217bf..df8ac50 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 431fd58..ab18e57 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 0939fb2..061c27c 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 9a43338..36d609a 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 fcaf578..d61d325 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 ca0a9b4..29ac034 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 84b191d..8e6b6c1 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 60859e2..aee015f 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 4509361..925849a 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 4245f5b..efee18a 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 2e98ec3..d054ee5 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 b3e18e1..0401e6b 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 1397c66..76c8931 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 db316e7..f4c6a85 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 30bec54..f4ad632 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 dd8bb41..4be397d 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 10f8f7b..1a0f78a 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 e471749..1d957b8 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 eedeba8..962027a 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 2a8aa14..98952a6 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 dc89ffc..63e6504 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 1ec7e51..e0cd83b 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 0bb4cb3..f41188b 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 620de32..af10e3d 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 087ac0d..3c4ece4 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 3c98336..bba3ba4 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 a5b9826..67c4444 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 b7baea7..b14a542 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 d8418cb..923638b 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 ab5e036..23566d1 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 a143e51..7eef3b2 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 3f653e3..e1c09ef 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 2a47435..886bcd7 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 5617644..6fc7422 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 d02c88a..06d3041 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 1e63af4..4d67d57 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 63eeefd..2b3745c 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 61a1916..5ddab61 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 34c1431..a52b59d 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 79aa40c..407c855 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 f0e5a84..1f28fef 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 c9158d6..532e6ec 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 31c253b..9af3317 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 24020e7..20c1b50 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 e80ee43..a3fdeb6 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 e89f990..4f72544 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 2e4ccec..eca2fd6 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 3c4b46d..eb15fd9 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 08c960f..fcd3af4 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 5987b07..5429285 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 48e7d25..9316b80 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 1e100db..10cbbb1 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 d0a62a6..b240900 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 ad30a93..9b03c93 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 78fc076..1db44f3 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 fb6187f..4426f3e 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 c87f8ca..71ca171 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 5dfac98..a8b9319 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 c8d8afa..b1668c7 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 36ff20e..01ffa2f 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 1e6eb8c..486715f 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 6a4a372..e29cc70 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 4c4619c..515b6ed 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 d459d07..e1be60c 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 eec675a..0542de5 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 136d8bf..bcb68fc 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 162b484..07563a4 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 0d49cac..0ad57b8 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 f559be3..069f0e0 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 c12ddcf..9958745 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 4c376da..2eac736 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 4a480fd..6d8eb98 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 f064d83..65edb88 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 5265599..e4f8840 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 c92a404..362a394 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 0a829ad..61b0ff6 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 c42ddba..d11487b 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 975add8..b458ebe 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 9f4fa6e..44e4135 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 1afa390..bee13ad 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 38a9c20..77fe48d 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 acee07e..ff11ffb 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 49b0af7..b4ad8bd 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 e6e1c81..d81ad8f 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 65d5cc5..09faa2a 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 8b74029..e6c20ce 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 f2d8a34..fceadf8 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 d11181d..3ddc5ce 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 161ce2b..25b2721 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 0a6d2a4..88fb818 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 b759edf..36fc90f 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 6992e27..dfa4f22 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 38539a1..717a7b4 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 8d8834f..c18f791 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 0844b35..b990cba 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 8604ffe..3568479 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 fd56ff4..775c7eb 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 b908cc0..5d1e1d0 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 8836ed7..b597241 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 6993813..1e31635 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 244cbfe..3f68e14 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 8feed90..cb9b256 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 47a80e2..acce6fb 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 647f9c1..7ddb99e 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 4288746..59adee3 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 88eeab5..131685c 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 5f60927..86bf745 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 9294378..430f677 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 6915c11..85f9ea7 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 ef99204..6c91659 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 28c9ee0..212c6c1 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 f3530d5..2603110 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 221ccb5..8b59e7e 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 dc8cd06..be207d4 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 300c9c6..3231821 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 11661ee..d003de4 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 8e7638f..df375c7 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 c678053..6defda1 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 75a6250..1eb251a 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 ec2fa07..7ab3c01 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 1076385..530dd07 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 53b630b..d7b8429 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 57900f0..e933941 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 378a6ca..8ceee04 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 a0a2fe8..ef7e1fb 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 3152293..7fce754 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 6bf58d0..ff44590 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 048da7e..b79893d 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 823b772..273807e 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 12494cd..3a56b00 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 70f2fec..ba6bfef 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 725ec7b..9b34f15 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 fe4fa4d..17c420c 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 3901a57..a989451 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 a6155aa..9408ae9 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 2253595..c9bafdf 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 3e6fdcb..ccd06fd 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 1809418..a9d731b 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: