From fbf462a30e702d3caa13c5553c83da8d7c2e5fd6 Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Sun, 19 Apr 2026 12:42:50 +0300 Subject: [PATCH 01/27] theme: add Querya design tokens (querya_colors) --- lib/core/theme/querya_colors.dart | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 lib/core/theme/querya_colors.dart diff --git a/lib/core/theme/querya_colors.dart b/lib/core/theme/querya_colors.dart new file mode 100644 index 00000000..af0707d0 --- /dev/null +++ b/lib/core/theme/querya_colors.dart @@ -0,0 +1,27 @@ +import 'dart:ui'; + +/// Design tokens aligned with [design-front] mockups (near-black canvas, cyan accent). +/// +/// Canonical semantic mapping lives in [QueryaColorScheme]; these are named aliases +/// for documentation and for a few widgets that need the raw accent (e.g. glows). +abstract class QueryaColors { + QueryaColors._(); + + /// Main app background — pure black on reference hero. + static const Color canvas = Color(0xFF000000); + + /// Elevated surface (cards, mock window chrome). + static const Color surface = Color(0xFF0C0C0C); + + /// Brand accent (CTAs, tree icons, focus ring). + static const Color accentCyan = Color(0xFF22D3EE); + + /// Text / icons on filled primary buttons. + static const Color onAccent = Color(0xFF0A0A0A); + + /// Hairline borders on dark UI. + static const Color borderSubtle = Color(0xFF27272A); + + /// Secondary label tone (body muted on mockups). + static const Color mutedLabel = Color(0xFF94A3B8); +} From 50378a862e7edfade7808318559ae48360db87d9 Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Sun, 19 Apr 2026 12:42:50 +0300 Subject: [PATCH 02/27] theme: add Querya dark ColorScheme --- lib/core/theme/querya_color_scheme.dart | 34 +++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 lib/core/theme/querya_color_scheme.dart diff --git a/lib/core/theme/querya_color_scheme.dart b/lib/core/theme/querya_color_scheme.dart new file mode 100644 index 00000000..f57e203b --- /dev/null +++ b/lib/core/theme/querya_color_scheme.dart @@ -0,0 +1,34 @@ +import 'package:shadcn_flutter/shadcn_flutter.dart'; + +import 'querya_colors.dart'; + +/// Dark [ColorScheme] for Querya desktop — matches [QueryaColors] tokens. +abstract class QueryaColorScheme { + static const ColorScheme dark = ColorScheme( + brightness: Brightness.dark, + background: QueryaColors.canvas, + foreground: Color(0xFFF8FAFC), + card: QueryaColors.surface, + cardForeground: Color(0xFFF8FAFC), + popover: QueryaColors.surface, + popoverForeground: Color(0xFFF8FAFC), + primary: QueryaColors.accentCyan, + primaryForeground: QueryaColors.onAccent, + secondary: Color(0xFF18181B), + secondaryForeground: Color(0xFFF8FAFC), + muted: Color(0xFF18181B), + mutedForeground: QueryaColors.mutedLabel, + accent: Color(0xFF27272A), + accentForeground: Color(0xFFF8FAFC), + destructive: Color(0xFFEF4444), + destructiveForeground: Color(0xFFF8FAFC), + border: QueryaColors.borderSubtle, + input: QueryaColors.borderSubtle, + ring: QueryaColors.accentCyan, + chart1: Color(0xFF2662D9), + chart2: Color(0xFF2EB88A), + chart3: Color(0xFFE88C30), + chart4: Color(0xFFAF57DB), + chart5: Color(0xFFE23670), + ); +} From 1a545c285d5b2be6380552054148af0341b84596 Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Sun, 19 Apr 2026 12:42:50 +0300 Subject: [PATCH 03/27] theme: shared monospace stack for SQL editors --- lib/core/theme/querya_typography.dart | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 lib/core/theme/querya_typography.dart diff --git a/lib/core/theme/querya_typography.dart b/lib/core/theme/querya_typography.dart new file mode 100644 index 00000000..4b5faf39 --- /dev/null +++ b/lib/core/theme/querya_typography.dart @@ -0,0 +1,6 @@ +/// Monospace stack for SQL editors (bundled font can replace this later). +abstract class QueryaTypography { + QueryaTypography._(); + + static const String mono = 'monospace'; +} From 55dd7d004f097201071a8f95c6be4ae60e631f82 Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Sun, 19 Apr 2026 12:42:50 +0300 Subject: [PATCH 04/27] theme: wire QueryaColorScheme and radius --- lib/core/theme/app_theme.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/core/theme/app_theme.dart b/lib/core/theme/app_theme.dart index e66c1385..4e8fe381 100644 --- a/lib/core/theme/app_theme.dart +++ b/lib/core/theme/app_theme.dart @@ -1,10 +1,12 @@ import 'package:shadcn_flutter/shadcn_flutter.dart'; +import 'querya_color_scheme.dart'; + /// App theme: dark only. Used for both theme and darkTheme so the app is always dark. abstract class AppTheme { static ThemeData get dark => const ThemeData.dark( - colorScheme: ColorSchemes.darkSlate, - radius: 0.5, + colorScheme: QueryaColorScheme.dark, + radius: 0.58, scaling: 1, typography: Typography.geist(), ); From 05033aac051e364ea117e2ca493a700ea25f011f Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Sun, 19 Apr 2026 12:42:50 +0300 Subject: [PATCH 05/27] layout: responsive breakpoints and dialog insets --- lib/core/layout/window_layout.dart | 79 ++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 lib/core/layout/window_layout.dart diff --git a/lib/core/layout/window_layout.dart b/lib/core/layout/window_layout.dart new file mode 100644 index 00000000..fd17520e --- /dev/null +++ b/lib/core/layout/window_layout.dart @@ -0,0 +1,79 @@ +import 'dart:math' as math; + +import 'package:flutter/widgets.dart'; + +/// Breakpoints and sizes derived from window / overlay size (desktop adaptive UI). +abstract class WindowLayout { + WindowLayout._(); + + static const double narrowWindowWidth = 720; + static const double compactWindowWidth = 520; + + /// Horizontal inset for modal dialogs (clamped by screen). + static double dialogHorizontalInset(double screenWidth) { + return (screenWidth * 0.05).clamp(12.0, 48.0); + } + + static double dialogVerticalInset(double screenHeight) { + return (screenHeight * 0.04).clamp(12.0, 40.0); + } + + /// Use for [Dialog.insetPadding] / modal margins on small windows. + static EdgeInsets dialogSymmetricInsets(BuildContext context) { + final mq = MediaQuery.sizeOf(context); + return EdgeInsets.symmetric( + horizontal: dialogHorizontalInset(mq.width), + vertical: dialogVerticalInset(mq.height), + ); + } + + /// "Select database" and similar pickers. + static double newConnectionDialogMaxWidth(double screenWidth) { + final inset = dialogHorizontalInset(screenWidth) * 2; + return math.min(740, math.max(280.0, screenWidth - inset)); + } + + static double newConnectionDialogHeight(double screenHeight) { + final inset = dialogVerticalInset(screenHeight) * 2; + final h = screenHeight - inset; + return math.min(580, math.max(320.0, h * 0.78)); + } + + static double newConnectionSidebarWidth(double dialogWidth) { + if (dialogWidth < 400) return 88; + if (dialogWidth < 520) return 108; + if (dialogWidth < 640) return 124; + return 140; + } + + /// Grid area width (right of sidebar, inner padding applied separately). + static int dbTypeGridCrossAxisCount(double gridInnerWidth) { + if (gridInnerWidth >= 460) return 4; + if (gridInnerWidth >= 240) return 2; + return 1; + } + + static double dbTypeCardHeight(int crossAxisCount) { + return switch (crossAxisCount) { + 4 => 144, + 2 => 138, + _ => 132, + }; + } + + /// Empty workspace hero content max width. + static double heroContentMaxWidth(double viewportWidth) { + return math.min(560, math.max(260.0, viewportWidth * 0.92)); + } + + static double heroHorizontalPadding(double viewportWidth) { + if (viewportWidth < compactWindowWidth) return 16; + if (viewportWidth < narrowWindowWidth) return 22; + return 28; + } + + /// Mock window block height scales with available width. + static double heroMockWindowHeight(double contentWidth) { + return (contentWidth * 0.38).clamp(140.0, 220.0); + } +} From f6d6a0a817b35ee371f7fa4fec02934ab1553857 Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Sun, 19 Apr 2026 12:42:50 +0300 Subject: [PATCH 06/27] editor: placeholder for future SQL syntax highlighting --- lib/core/editor/sql_syntax_highlighting.dart | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 lib/core/editor/sql_syntax_highlighting.dart diff --git a/lib/core/editor/sql_syntax_highlighting.dart b/lib/core/editor/sql_syntax_highlighting.dart new file mode 100644 index 00000000..5dfb2276 --- /dev/null +++ b/lib/core/editor/sql_syntax_highlighting.dart @@ -0,0 +1,7 @@ +/// Future work: editable SQL with syntax highlighting (see plan: syntax-highlight-epic). +/// +/// Candidates: custom [EditableText] + [TextPainter], or a dedicated code-editor package. +/// Plain [TextField] remains the source of truth until an editor is chosen. +abstract class SqlSyntaxHighlighting { + const SqlSyntaxHighlighting._(); +} From 909ad5ab5664f5a425e004834b0b45bc63f642d8 Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Sun, 19 Apr 2026 12:42:50 +0300 Subject: [PATCH 07/27] ui: SQL editor chrome with glow and inline decoration --- .../main_screen/sql_editor_chrome.dart | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 lib/features/main_screen/sql_editor_chrome.dart diff --git a/lib/features/main_screen/sql_editor_chrome.dart b/lib/features/main_screen/sql_editor_chrome.dart new file mode 100644 index 00000000..88014e14 --- /dev/null +++ b/lib/features/main_screen/sql_editor_chrome.dart @@ -0,0 +1,59 @@ +import 'package:flutter/material.dart' as material; +import 'package:querya_desktop/core/theme/querya_colors.dart'; +import 'package:shadcn_flutter/shadcn_flutter.dart'; + +/// Outer chrome for SQL editors: subtle border, surface fill, soft cyan glow. +class SqlEditorChrome extends StatelessWidget { + const SqlEditorChrome({super.key, required this.child}); + + final Widget child; + + static material.BoxDecoration inlineFieldDecoration(ThemeData theme) { + final cs = theme.colorScheme; + return material.BoxDecoration( + color: cs.card, + borderRadius: material.BorderRadius.circular(10), + border: material.Border.all( + color: cs.border.withValues(alpha: 0.45), + ), + boxShadow: [ + material.BoxShadow( + color: QueryaColors.accentCyan.withValues(alpha: 0.07), + blurRadius: 18, + offset: const material.Offset(0, 6), + ), + ], + ); + } + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final cs = theme.colorScheme; + final glow = QueryaColors.accentCyan.withValues(alpha: 0.1); + return material.Container( + decoration: material.BoxDecoration( + borderRadius: material.BorderRadius.circular(14), + boxShadow: [ + material.BoxShadow( + color: glow, + blurRadius: 28, + spreadRadius: 0, + offset: const material.Offset(0, 10), + ), + ], + ), + child: material.Container( + decoration: material.BoxDecoration( + color: cs.card, + borderRadius: material.BorderRadius.circular(14), + border: material.Border.all( + color: cs.border.withValues(alpha: 0.5), + ), + ), + clipBehavior: material.Clip.antiAlias, + child: child, + ), + ); + } +} From 830136775dd9141a5c1f907f9f384f7370de4138 Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Sun, 19 Apr 2026 12:42:50 +0300 Subject: [PATCH 08/27] connections: extract promptCreateConnection flow --- .../connections/connection_creation_flow.dart | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 lib/features/connections/connection_creation_flow.dart diff --git a/lib/features/connections/connection_creation_flow.dart b/lib/features/connections/connection_creation_flow.dart new file mode 100644 index 00000000..184c5909 --- /dev/null +++ b/lib/features/connections/connection_creation_flow.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart' as material; + +import 'package:querya_desktop/core/storage/local_db.dart'; +import 'package:querya_desktop/features/connections/new_connection_dialog.dart'; +import 'package:querya_desktop/features/mongodb/mongodb_connection_form.dart'; +import 'package:querya_desktop/features/mysql/mysql_connection_form.dart'; +import 'package:querya_desktop/features/postgresql/postgresql_connection_form.dart'; +import 'package:querya_desktop/features/redis/redis_connection_form.dart'; + +/// Picks a database type, opens the matching form, returns a saved row or null. +Future promptCreateConnection( + material.BuildContext context, { + int? folderId, +}) async { + final type = await showNewConnectionDialog(context); + if (type == null) return null; + return switch (type) { + ConnectionType.postgresql => + await showPostgresConnectionForm(context, folderId: folderId), + ConnectionType.mysql => + await showMysqlConnectionForm(context, folderId: folderId), + ConnectionType.mongodb => + await showMongoConnectionForm(context, folderId: folderId), + ConnectionType.redis => + await showRedisConnectionForm(context, folderId: folderId), + }; +} From b3abc6066619208f77be8f84453e880c65c79108 Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Sun, 19 Apr 2026 12:42:50 +0300 Subject: [PATCH 09/27] ui: empty workspace hero and mock window --- .../main_screen/workspace_empty_hero.dart | 310 ++++++++++++++++++ 1 file changed, 310 insertions(+) create mode 100644 lib/features/main_screen/workspace_empty_hero.dart diff --git a/lib/features/main_screen/workspace_empty_hero.dart b/lib/features/main_screen/workspace_empty_hero.dart new file mode 100644 index 00000000..2c71b215 --- /dev/null +++ b/lib/features/main_screen/workspace_empty_hero.dart @@ -0,0 +1,310 @@ +import 'package:flutter/material.dart' as material; +import 'package:querya_desktop/core/layout/window_layout.dart'; +import 'package:querya_desktop/core/theme/querya_colors.dart'; +import 'package:querya_desktop/core/theme/querya_typography.dart'; +import 'package:querya_desktop/shared/widgets/widgets.dart'; + +/// Marketing-style empty workspace: badge, copy, mock window, primary CTA. +class WorkspaceEmptyHero extends StatelessWidget { + const WorkspaceEmptyHero({ + super.key, + required this.onNewConnection, + }); + + final VoidCallback onNewConnection; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final cs = theme.colorScheme; + return material.LayoutBuilder( + builder: (context, c) { + final vw = c.maxWidth; + final padH = WindowLayout.heroHorizontalPadding(vw); + final padV = vw < WindowLayout.compactWindowWidth ? 24.0 : 32.0; + final maxContent = WindowLayout.heroContentMaxWidth(vw); + final mockH = WindowLayout.heroMockWindowHeight(maxContent); + final compact = vw < WindowLayout.compactWindowWidth; + return material.SingleChildScrollView( + padding: material.EdgeInsets.symmetric(horizontal: padH, vertical: padV), + child: material.Align( + alignment: material.Alignment.topCenter, + child: material.ConstrainedBox( + constraints: material.BoxConstraints(maxWidth: maxContent), + child: material.Column( + crossAxisAlignment: material.CrossAxisAlignment.center, + children: [ + _HeroBadge(colorScheme: cs, compact: compact), + material.SizedBox(height: compact ? 16 : 20), + material.Padding( + padding: const material.EdgeInsets.symmetric(horizontal: 4), + child: Text( + 'A lightweight desktop client for every database you ship', + textAlign: material.TextAlign.center, + ) + .large() + .semiBold(), + ), + material.SizedBox(height: compact ? 10 : 12), + material.Padding( + padding: const material.EdgeInsets.symmetric(horizontal: 4), + child: Text( + 'Connect to PostgreSQL, MySQL, Redis and MongoDB from a single ' + 'focused app with a calm dark interface.', + textAlign: material.TextAlign.center, + ).muted().small(), + ), + const material.SizedBox(height: 10), + Text( + 'BUILT WITH FLUTTER · CROSS-PLATFORM', + textAlign: material.TextAlign.center, + style: material.TextStyle( + fontFamily: QueryaTypography.mono, + fontSize: compact ? 9 : 10, + letterSpacing: 0.6, + color: cs.mutedForeground.withValues(alpha: 0.85), + ), + ), + material.SizedBox(height: compact ? 20 : 28), + _MockAppWindow( + colorScheme: cs, + height: mockH, + compact: compact, + ), + material.SizedBox(height: compact ? 20 : 28), + material.Wrap( + alignment: material.WrapAlignment.center, + spacing: 12, + runSpacing: 12, + children: [ + PrimaryButton( + onPressed: onNewConnection, + leading: material.Icon( + material.Icons.add_link_rounded, + size: compact ? 16 : 18, + ), + child: const Text('New connection'), + ), + ], + ), + ], + ), + ), + ), + ); + }, + ); + } +} + +class _HeroBadge extends StatelessWidget { + const _HeroBadge({required this.colorScheme, this.compact = false}); + + final ColorScheme colorScheme; + final bool compact; + + @override + Widget build(BuildContext context) { + return material.Container( + padding: material.EdgeInsets.symmetric( + horizontal: compact ? 10 : 12, + vertical: compact ? 5 : 6, + ), + decoration: material.BoxDecoration( + color: colorScheme.background, + borderRadius: material.BorderRadius.circular(999), + border: material.Border.all( + color: QueryaColors.accentCyan.withValues(alpha: 0.45), + ), + ), + child: material.Row( + mainAxisSize: material.MainAxisSize.min, + children: [ + material.Icon( + material.Icons.auto_awesome_rounded, + size: compact ? 12 : 14, + color: colorScheme.primary, + ), + material.SizedBox(width: compact ? 6 : 8), + Text( + 'One app for SQL and NoSQL', + style: material.TextStyle( + fontSize: compact ? 11 : 12, + color: colorScheme.foreground.withValues(alpha: 0.92), + ), + ), + ], + ), + ); + } +} + +class _MockAppWindow extends StatelessWidget { + const _MockAppWindow({ + required this.colorScheme, + required this.height, + this.compact = false, + }); + + final ColorScheme colorScheme; + final double height; + final bool compact; + + @override + Widget build(BuildContext context) { + final glow = QueryaColors.accentCyan.withValues(alpha: 0.14); + final sidebarW = compact ? 58.0 : 72.0; + final blur = compact ? 28.0 : 40.0; + final radius = compact ? 12.0 : 16.0; + return material.Container( + decoration: material.BoxDecoration( + borderRadius: material.BorderRadius.circular(radius), + boxShadow: [ + material.BoxShadow( + color: glow, + blurRadius: blur, + spreadRadius: -4, + offset: material.Offset(0, compact ? 12 : 18), + ), + ], + ), + child: material.Container( + height: height, + decoration: material.BoxDecoration( + color: const Color(0xFF141414), + borderRadius: material.BorderRadius.circular(radius), + border: material.Border.all( + color: colorScheme.border.withValues(alpha: 0.5), + ), + ), + clipBehavior: material.Clip.antiAlias, + child: material.Column( + crossAxisAlignment: material.CrossAxisAlignment.stretch, + children: [ + material.Container( + padding: material.EdgeInsets.symmetric( + horizontal: compact ? 10 : 12, + vertical: compact ? 8 : 10, + ), + decoration: material.BoxDecoration( + border: material.Border( + bottom: material.BorderSide( + color: colorScheme.border.withValues(alpha: 0.35), + ), + ), + ), + child: material.Row( + children: [ + _trafficDot(const Color(0xFFFF5F57)), + const material.SizedBox(width: 6), + _trafficDot(const Color(0xFFFEBC2E)), + const material.SizedBox(width: 6), + _trafficDot(const Color(0xFF28C840)), + ], + ), + ), + material.Expanded( + child: material.Row( + crossAxisAlignment: material.CrossAxisAlignment.stretch, + children: [ + material.Container( + width: sidebarW, + padding: material.EdgeInsets.fromLTRB( + compact ? 8 : 10, + compact ? 8 : 10, + compact ? 6 : 8, + compact ? 8 : 10, + ), + color: const Color(0xFF0F0F0F), + child: material.Column( + crossAxisAlignment: material.CrossAxisAlignment.start, + children: [ + Text( + 'SERVERS', + style: material.TextStyle( + fontFamily: QueryaTypography.mono, + fontSize: compact ? 8 : 9, + letterSpacing: 0.5, + color: colorScheme.mutedForeground, + ), + ), + material.SizedBox(height: compact ? 8 : 10), + material.Container( + padding: material.EdgeInsets.symmetric( + horizontal: compact ? 6 : 8, + vertical: compact ? 4 : 6, + ), + decoration: material.BoxDecoration( + color: colorScheme.primary.withValues(alpha: 0.2), + borderRadius: + material.BorderRadius.circular(compact ? 6 : 8), + ), + child: material.Text( + 'analytics-prod', + maxLines: 1, + overflow: material.TextOverflow.ellipsis, + style: material.TextStyle( + fontSize: compact ? 9 : 10, + color: colorScheme.foreground, + ), + ), + ), + ], + ), + ), + material.Expanded( + child: material.Container( + margin: material.EdgeInsets.all(compact ? 8 : 10), + padding: material.EdgeInsets.all(compact ? 8 : 10), + decoration: material.BoxDecoration( + color: const Color(0xFF0A0A0A), + borderRadius: material.BorderRadius.circular(8), + border: material.Border.all( + color: colorScheme.border.withValues(alpha: 0.25), + ), + ), + child: material.Column( + crossAxisAlignment: material.CrossAxisAlignment.start, + children: [ + material.Text( + 'SELECT …', + style: material.TextStyle( + fontFamily: QueryaTypography.mono, + fontSize: compact ? 9 : 11, + height: 1.45, + color: colorScheme.primary, + ), + ), + material.Text( + " interval '1 day'", + style: material.TextStyle( + fontFamily: QueryaTypography.mono, + fontSize: compact ? 9 : 11, + height: 1.45, + color: const Color(0xFFFBBF24), + ), + ), + ], + ), + ), + ), + ], + ), + ), + ], + ), + ), + ); + } +} + +material.Widget _trafficDot(Color c) { + return material.Container( + width: 10, + height: 10, + decoration: material.BoxDecoration( + color: c, + shape: material.BoxShape.circle, + ), + ); +} From 1e3ddc571839104c19d75e5cc6cdb3d4794ff241 Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Sun, 19 Apr 2026 12:42:50 +0300 Subject: [PATCH 10/27] ui: apply SqlEditorChrome to query editor tab --- lib/features/main_screen/query_editor_tab.dart | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/features/main_screen/query_editor_tab.dart b/lib/features/main_screen/query_editor_tab.dart index 240e47b6..7272997e 100644 --- a/lib/features/main_screen/query_editor_tab.dart +++ b/lib/features/main_screen/query_editor_tab.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart' as material - show Padding, EdgeInsets, TextStyle, TextEditingController; + show EdgeInsets, Padding, TextEditingController, TextStyle; +import 'package:querya_desktop/core/theme/querya_typography.dart'; +import 'package:querya_desktop/features/main_screen/sql_editor_chrome.dart'; import 'package:querya_desktop/shared/widgets/widgets.dart'; class QueryEditorTab extends StatelessWidget { @@ -68,17 +70,18 @@ class _QueryEditorBodyState extends State<_QueryEditorBody> { @override Widget build(BuildContext context) { + final theme = Theme.of(context); return material.Padding( padding: const material.EdgeInsets.all(12), - child: Card( - padding: material.EdgeInsets.zero, + child: SqlEditorChrome( child: TextField( controller: _owned, maxLines: null, expands: true, - style: const material.TextStyle( - fontFamily: 'monospace', + style: material.TextStyle( + fontFamily: QueryaTypography.mono, fontSize: 13, + color: theme.colorScheme.foreground, ), placeholder: const Text('-- Enter SQL here…\nSELECT 1;'), ), From 1f84db59aca048bb5beea62cd86c560bf7119d2d Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Sun, 19 Apr 2026 12:42:50 +0300 Subject: [PATCH 11/27] ui: responsive SQL editor dialog chrome (PostgreSQL) --- .../postgresql/postgres_sql_editor_dialog.dart | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/lib/features/postgresql/postgres_sql_editor_dialog.dart b/lib/features/postgresql/postgres_sql_editor_dialog.dart index e1bf4a9d..86dd0328 100644 --- a/lib/features/postgresql/postgres_sql_editor_dialog.dart +++ b/lib/features/postgresql/postgres_sql_editor_dialog.dart @@ -1,4 +1,7 @@ import 'package:flutter/material.dart' as material; +import 'package:querya_desktop/core/layout/window_layout.dart'; +import 'package:querya_desktop/core/theme/querya_typography.dart'; +import 'package:querya_desktop/features/main_screen/sql_editor_chrome.dart'; import 'package:querya_desktop/shared/widgets/widgets.dart'; /// Returns true if [sql] is allowed to run (read-only: SELECT / WITH). @@ -99,8 +102,7 @@ class _PostgresSqlEditorDialogState extends material.State<_PostgresSqlEditorDia final radius = Theme.of(context).radiusXxl; return material.Dialog( backgroundColor: material.Colors.transparent, - insetPadding: - const material.EdgeInsets.symmetric(horizontal: 32, vertical: 24), + insetPadding: WindowLayout.dialogSymmetricInsets(context), child: material.Container( constraints: const material.BoxConstraints( maxWidth: 720, @@ -137,11 +139,8 @@ class _PostgresSqlEditorDialogState extends material.State<_PostgresSqlEditorDia child: material.SizedBox( height: 280, child: material.Container( - decoration: material.BoxDecoration( - color: theme.muted.withValues(alpha: 0.15), - borderRadius: material.BorderRadius.circular(8), - border: material.Border.all( - color: theme.border.withValues(alpha: 0.4)), + decoration: SqlEditorChrome.inlineFieldDecoration( + Theme.of(context), ), child: material.TextField( controller: _controller, @@ -149,7 +148,7 @@ class _PostgresSqlEditorDialogState extends material.State<_PostgresSqlEditorDia expands: true, textAlignVertical: material.TextAlignVertical.top, style: material.TextStyle( - fontFamily: 'monospace', + fontFamily: QueryaTypography.mono, fontSize: 12, color: theme.foreground, ), From 5229c19cedc87f11068b9d8136f3c12c8b3d540c Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Sun, 19 Apr 2026 12:42:50 +0300 Subject: [PATCH 12/27] ui: responsive SQL editor dialog chrome (MySQL) --- lib/features/mysql/mysql_sql_editor_dialog.dart | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/lib/features/mysql/mysql_sql_editor_dialog.dart b/lib/features/mysql/mysql_sql_editor_dialog.dart index b75d472a..eb69d33d 100644 --- a/lib/features/mysql/mysql_sql_editor_dialog.dart +++ b/lib/features/mysql/mysql_sql_editor_dialog.dart @@ -1,4 +1,7 @@ import 'package:flutter/material.dart' as material; +import 'package:querya_desktop/core/layout/window_layout.dart'; +import 'package:querya_desktop/core/theme/querya_typography.dart'; +import 'package:querya_desktop/features/main_screen/sql_editor_chrome.dart'; import 'package:querya_desktop/features/mysql/mysql_table_utils.dart'; import 'package:querya_desktop/shared/widgets/widgets.dart'; @@ -72,8 +75,7 @@ class _MysqlSqlEditorDialogState extends material.State<_MysqlSqlEditorDialog> { final radius = Theme.of(context).radiusXxl; return material.Dialog( backgroundColor: material.Colors.transparent, - insetPadding: - const material.EdgeInsets.symmetric(horizontal: 32, vertical: 24), + insetPadding: WindowLayout.dialogSymmetricInsets(context), child: material.Container( constraints: const material.BoxConstraints( maxWidth: 720, @@ -110,11 +112,8 @@ class _MysqlSqlEditorDialogState extends material.State<_MysqlSqlEditorDialog> { child: material.SizedBox( height: 280, child: material.Container( - decoration: material.BoxDecoration( - color: theme.muted.withValues(alpha: 0.15), - borderRadius: material.BorderRadius.circular(8), - border: material.Border.all( - color: theme.border.withValues(alpha: 0.4)), + decoration: SqlEditorChrome.inlineFieldDecoration( + Theme.of(context), ), child: material.TextField( controller: _controller, @@ -122,7 +121,7 @@ class _MysqlSqlEditorDialogState extends material.State<_MysqlSqlEditorDialog> { expands: true, textAlignVertical: material.TextAlignVertical.top, style: material.TextStyle( - fontFamily: 'monospace', + fontFamily: QueryaTypography.mono, fontSize: 12, color: theme.foreground, ), From 4d7ff974f1d51208a63beaf564ee6957f309f367 Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Sun, 19 Apr 2026 12:42:50 +0300 Subject: [PATCH 13/27] ui: SERVERS header, selection capsule, creation flow --- .../connections/connections_panel.dart | 448 ++++++++++-------- 1 file changed, 241 insertions(+), 207 deletions(-) diff --git a/lib/features/connections/connections_panel.dart b/lib/features/connections/connections_panel.dart index 5ae139f8..4a32e54e 100644 --- a/lib/features/connections/connections_panel.dart +++ b/lib/features/connections/connections_panel.dart @@ -1,4 +1,4 @@ -import 'package:flutter/material.dart' as material show AlertDialog, BoxConstraints, BuildContext, Column, ConstrainedBox, Container, BoxDecoration, Border, BorderSide, InkWell, Icon, Icons, IconData, Image, EdgeInsets, BorderRadius, CrossAxisAlignment, MainAxisSize, MouseRegion, SystemMouseCursors, DefaultTextStyle, TextStyle, CustomScrollView, SliverToBoxAdapter, SliverFillRemaining, SliverPadding, GestureDetector, HitTestBehavior, SizedBox, AnimatedRotation, Row, BoxFit, Text, TextOverflow, Expanded, CircularProgressIndicator, Material, StatelessWidget, Colors, Tooltip, Color, LayoutBuilder, TextPainter, TextSpan, TextDirection, SelectableText, Padding, Widget, Navigator, ValueKey; +import 'package:flutter/material.dart' as material show AlertDialog, BoxConstraints, BuildContext, Column, ConstrainedBox, Container, BoxDecoration, Border, BorderSide, InkWell, Icon, Icons, IconData, Image, EdgeInsets, BorderRadius, CrossAxisAlignment, MainAxisSize, MouseRegion, SystemMouseCursors, DefaultTextStyle, TextStyle, CustomScrollView, SliverToBoxAdapter, SliverFillRemaining, SliverPadding, GestureDetector, HitTestBehavior, SizedBox, AnimatedRotation, Row, BoxFit, Text, TextOverflow, Expanded, CircularProgressIndicator, Material, StatelessWidget, Colors, Tooltip, Color, LayoutBuilder, TextPainter, TextSpan, TextDirection, SelectableText, Padding, Widget, Navigator, ValueKey, FontWeight, VoidCallback; 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'; @@ -7,22 +7,49 @@ import 'package:querya_desktop/core/database/redis_connection.dart'; import 'package:querya_desktop/core/database/redis_info.dart'; import 'package:querya_desktop/core/storage/folders_storage.dart'; import 'package:querya_desktop/core/storage/local_db.dart'; +import 'package:querya_desktop/core/theme/querya_typography.dart'; +import 'package:querya_desktop/features/connections/connection_creation_flow.dart'; import 'package:querya_desktop/shared/widgets/widgets.dart'; import 'package:querya_desktop/features/mongodb/mongo_database_dialog.dart'; -import 'package:querya_desktop/features/mongodb/mongodb_connection_form.dart'; import 'package:querya_desktop/features/postgresql/postgres_object_kind.dart'; -import 'package:querya_desktop/features/mysql/mysql_connection_form.dart'; import 'package:querya_desktop/features/mysql/mysql_object_kind.dart'; -import 'package:querya_desktop/features/postgresql/postgresql_connection_form.dart'; -import 'package:querya_desktop/features/redis/redis_connection_form.dart'; -import 'new_connection_dialog.dart'; import 'new_folder_dialog.dart'; +material.Widget _sidebarConnectionShell({ + required material.BuildContext context, + required bool isSelected, + required material.VoidCallback? onTap, + required material.Widget child, +}) { + final p = Theme.of(context).colorScheme.primary; + return material.Material( + color: material.Colors.transparent, + child: material.InkWell( + onTap: onTap, + borderRadius: material.BorderRadius.circular(20), + mouseCursor: onTap != null + ? material.SystemMouseCursors.click + : material.SystemMouseCursors.basic, + child: material.Container( + decoration: material.BoxDecoration( + color: isSelected ? p.withValues(alpha: 0.16) : null, + borderRadius: material.BorderRadius.circular(20), + border: isSelected + ? material.Border.all(color: p.withValues(alpha: 0.26)) + : null, + ), + child: child, + ), + ), + ); +} + /// Left panel: Browser tree (pgAdmin-style). Uses shadcn layout widgets. class ConnectionsPanel extends StatefulWidget { const ConnectionsPanel({ super.key, + this.selectedConnectionId, this.onConnectionSelected, this.onRedisDatabaseSelected, this.onMongoDBDatabaseSelected, @@ -37,6 +64,9 @@ class ConnectionsPanel extends StatefulWidget { this.skipInitialDbLoadForTest = false, }); + /// Highlights the active connection row in the sidebar (workspace selection). + final int? selectedConnectionId; + /// Called when the user taps a connection tile. final void Function(ConnectionRow connection)? onConnectionSelected; @@ -147,21 +177,14 @@ class ConnectionsPanelState extends State { if (mounted) setState(() => _folders = FoldersStorage.instance.folders); } - Future _createConnection(ConnectionType type, {int? folderId}) async { - ConnectionRow? row; - - if (type == ConnectionType.postgresql) { - row = await showPostgresConnectionForm(context, folderId: folderId); - } else if (type == ConnectionType.mysql) { - row = await showMysqlConnectionForm(context, folderId: folderId); - } else if (type == ConnectionType.mongodb) { - row = await showMongoConnectionForm(context, folderId: folderId); - } else if (type == ConnectionType.redis) { - row = await showRedisConnectionForm(context, folderId: folderId); - } else { - row = null; - } - + Future _createConnection({ + int? folderId, + material.BuildContext? dialogContext, + }) async { + final row = await promptCreateConnection( + dialogContext ?? context, + folderId: folderId, + ); if (row != null && mounted) { await LocalDb.instance.addConnection(row); await _loadData(); @@ -197,9 +220,12 @@ class ConnectionsPanelState extends State { } Widget _buildConnectionTile(ConnectionRow conn) { + final isSelected = + widget.selectedConnectionId != null && widget.selectedConnectionId == conn.id; if (conn.type == 'postgresql') { return _PostgresConnectionTile( connection: conn, + isSelected: isSelected, icon: _iconForType(conn.type), iconAsset: _iconAssetForType(conn.type), onRemove: () => _removeConnection(conn.id!), @@ -210,6 +236,7 @@ class ConnectionsPanelState extends State { } else if (conn.type == 'mysql') { return _MysqlConnectionTile( connection: conn, + isSelected: isSelected, icon: _iconForType(conn.type), iconAsset: _iconAssetForType(conn.type), onRemove: () => _removeConnection(conn.id!), @@ -220,6 +247,7 @@ class ConnectionsPanelState extends State { } else if (conn.type == 'redis') { return _RedisConnectionTile( connection: conn, + isSelected: isSelected, icon: _iconForType(conn.type), iconAsset: _iconAssetForType(conn.type), onRemove: () => _removeConnection(conn.id!), @@ -229,6 +257,7 @@ class ConnectionsPanelState extends State { } else if (conn.type == 'mongodb') { return _MongoConnectionTile( connection: conn, + isSelected: isSelected, icon: _iconForType(conn.type), iconAsset: _iconAssetForType(conn.type), onRemove: () => _removeConnection(conn.id!), @@ -238,6 +267,7 @@ class ConnectionsPanelState extends State { } return _ConnectionTile( connection: conn, + isSelected: isSelected, icon: _iconForType(conn.type), iconAsset: _iconAssetForType(conn.type), onRemove: () => _removeConnection(conn.id!), @@ -257,7 +287,7 @@ class ConnectionsPanelState extends State { color: theme.colorScheme.background, border: material.Border( right: material.BorderSide( - color: theme.colorScheme.border.withValues(alpha: 0.4), + color: theme.colorScheme.border.withValues(alpha: 0.28), width: 1, ), ), @@ -267,12 +297,18 @@ class ConnectionsPanelState extends State { children: [ material.Padding( padding: const material.EdgeInsets.fromLTRB(20, 24, 16, 16), - child: material.DefaultTextStyle( - style: material.TextStyle(color: theme.colorScheme.mutedForeground), - child: const Text('Browser').semiBold().small(), + child: material.Text( + 'SERVERS', + style: material.TextStyle( + fontFamily: QueryaTypography.mono, + fontSize: 11, + letterSpacing: 0.85, + fontWeight: material.FontWeight.w600, + color: theme.colorScheme.mutedForeground, + ), ), ), - Divider(height: 1, color: theme.colorScheme.border.withValues(alpha: 0.3)), + Divider(height: 1, color: theme.colorScheme.border.withValues(alpha: 0.22)), Expanded( child: material.CustomScrollView( slivers: [ @@ -305,10 +341,9 @@ class ConnectionsPanelState extends State { await _loadData(); }, onNewConnection: (folderName) async { - final type = await showNewConnectionDialog(context); - if (type == null || !mounted) return; - final folderId = await LocalDb.instance.getFolderIdByName(folderName); - await _createConnection(type, folderId: folderId); + final folderId = + await LocalDb.instance.getFolderIdByName(folderName); + await _createConnection(folderId: folderId); }, iconForType: _iconForType, onRemoveConnection: _removeConnection, @@ -340,11 +375,9 @@ class ConnectionsPanelState extends State { MenuButton( leading: material.Icon(material.Icons.settings_ethernet_rounded, size: 18, color: theme.colorScheme.mutedForeground), onPressed: (menuContext) async { - final type = await showNewConnectionDialog(menuContext); - if (type == null || !mounted) return; await Future.delayed(const Duration(milliseconds: 100)); if (!mounted) return; - await _createConnection(type); + await _createConnection(dialogContext: menuContext); }, child: const Text('New Connection'), ), @@ -412,6 +445,7 @@ class _EmptyState extends StatelessWidget { class _ConnectionTile extends StatelessWidget { const _ConnectionTile({ required this.connection, + this.isSelected = false, required this.icon, this.iconAsset, required this.onRemove, @@ -419,6 +453,7 @@ class _ConnectionTile extends StatelessWidget { }); final ConnectionRow connection; + final bool isSelected; final material.IconData icon; final String? iconAsset; final VoidCallback onRemove; @@ -450,46 +485,45 @@ class _ConnectionTile extends StatelessWidget { ], child: material.Padding( padding: const material.EdgeInsets.only(bottom: 2), - child: material.MouseRegion( - cursor: material.SystemMouseCursors.click, - child: material.InkWell( - onTap: onTap, - borderRadius: material.BorderRadius.circular(6), - child: material.Padding( - padding: const material.EdgeInsets.symmetric(horizontal: 8, vertical: 6), - child: material.Row( - children: [ - iconWidget, - const Gap(8), - material.Expanded( - child: material.Column( - crossAxisAlignment: material.CrossAxisAlignment.start, - mainAxisSize: material.MainAxisSize.min, - children: [ + child: _sidebarConnectionShell( + context: context, + isSelected: isSelected, + onTap: onTap, + child: material.Padding( + padding: + const material.EdgeInsets.symmetric(horizontal: 8, vertical: 6), + child: material.Row( + children: [ + iconWidget, + const Gap(8), + material.Expanded( + child: material.Column( + crossAxisAlignment: material.CrossAxisAlignment.start, + mainAxisSize: material.MainAxisSize.min, + children: [ + material.Text( + connection.name, + overflow: material.TextOverflow.ellipsis, + maxLines: 1, + style: material.TextStyle( + fontSize: 13, + color: theme.colorScheme.foreground, + ), + ), + if (connection.host != null) material.Text( - connection.name, + '${connection.host}:${connection.port ?? ''}', overflow: material.TextOverflow.ellipsis, maxLines: 1, style: material.TextStyle( - fontSize: 13, - color: theme.colorScheme.foreground, + fontSize: 11, + color: theme.colorScheme.mutedForeground, ), ), - if (connection.host != null) - material.Text( - '${connection.host}:${connection.port ?? ''}', - overflow: material.TextOverflow.ellipsis, - maxLines: 1, - style: material.TextStyle( - fontSize: 11, - color: theme.colorScheme.mutedForeground, - ), - ), - ], - ), + ], ), - ], - ), + ), + ], ), ), ), @@ -613,6 +647,7 @@ class _FolderTile extends StatelessWidget { class _RedisConnectionTile extends StatefulWidget { const _RedisConnectionTile({ required this.connection, + this.isSelected = false, required this.icon, this.iconAsset, required this.onRemove, @@ -621,6 +656,7 @@ class _RedisConnectionTile extends StatefulWidget { }); final ConnectionRow connection; + final bool isSelected; final material.IconData icon; final String? iconAsset; final VoidCallback onRemove; @@ -766,48 +802,46 @@ class _RedisConnectionTileState extends State<_RedisConnectionTile> { ), // Connection name — clickable for stats material.Expanded( - child: material.MouseRegion( - cursor: material.SystemMouseCursors.click, - child: material.InkWell( - onTap: widget.onTap, - borderRadius: material.BorderRadius.circular(6), - child: material.Padding( - padding: const material.EdgeInsets.symmetric( - horizontal: 4, vertical: 6), - child: material.Row( - children: [ - iconWidget, - const Gap(8), - material.Expanded( - child: material.Column( - crossAxisAlignment: - material.CrossAxisAlignment.start, - mainAxisSize: material.MainAxisSize.min, - children: [ + child: _sidebarConnectionShell( + context: context, + isSelected: widget.isSelected, + onTap: widget.onTap, + child: material.Padding( + padding: const material.EdgeInsets.symmetric( + horizontal: 4, vertical: 6), + child: material.Row( + children: [ + iconWidget, + const Gap(8), + material.Expanded( + child: material.Column( + crossAxisAlignment: + material.CrossAxisAlignment.start, + mainAxisSize: material.MainAxisSize.min, + children: [ + material.Text( + widget.connection.name, + overflow: material.TextOverflow.ellipsis, + maxLines: 1, + style: material.TextStyle( + fontSize: 13, + color: theme.colorScheme.foreground, + ), + ), + if (widget.connection.host != null) material.Text( - widget.connection.name, + '${widget.connection.host}:${widget.connection.port ?? ''}', overflow: material.TextOverflow.ellipsis, maxLines: 1, style: material.TextStyle( - fontSize: 13, - color: theme.colorScheme.foreground, + fontSize: 11, + color: theme.colorScheme.mutedForeground, ), ), - if (widget.connection.host != null) - material.Text( - '${widget.connection.host}:${widget.connection.port ?? ''}', - overflow: material.TextOverflow.ellipsis, - maxLines: 1, - style: material.TextStyle( - fontSize: 11, - color: theme.colorScheme.mutedForeground, - ), - ), - ], - ), + ], ), - ], - ), + ), + ], ), ), ), @@ -924,6 +958,7 @@ class _RedisDatabaseNode extends StatelessWidget { class _MongoConnectionTile extends StatefulWidget { const _MongoConnectionTile({ required this.connection, + this.isSelected = false, required this.icon, this.iconAsset, required this.onRemove, @@ -932,6 +967,7 @@ class _MongoConnectionTile extends StatefulWidget { }); final ConnectionRow connection; + final bool isSelected; final material.IconData icon; final String? iconAsset; final VoidCallback onRemove; @@ -1098,48 +1134,46 @@ class _MongoConnectionTileState extends State<_MongoConnectionTile> { ), // Connection name — clickable for stats material.Expanded( - child: material.MouseRegion( - cursor: material.SystemMouseCursors.click, - child: material.InkWell( - onTap: widget.onTap, - borderRadius: material.BorderRadius.circular(6), - child: material.Padding( - padding: const material.EdgeInsets.symmetric( - horizontal: 4, vertical: 6), - child: material.Row( - children: [ - iconWidget, - const Gap(8), - material.Expanded( - child: material.Column( - crossAxisAlignment: - material.CrossAxisAlignment.start, - mainAxisSize: material.MainAxisSize.min, - children: [ + child: _sidebarConnectionShell( + context: context, + isSelected: widget.isSelected, + onTap: widget.onTap, + child: material.Padding( + padding: const material.EdgeInsets.symmetric( + horizontal: 4, vertical: 6), + child: material.Row( + children: [ + iconWidget, + const Gap(8), + material.Expanded( + child: material.Column( + crossAxisAlignment: + material.CrossAxisAlignment.start, + mainAxisSize: material.MainAxisSize.min, + children: [ + material.Text( + widget.connection.name, + overflow: material.TextOverflow.ellipsis, + maxLines: 1, + style: material.TextStyle( + fontSize: 13, + color: theme.colorScheme.foreground, + ), + ), + if (widget.connection.host != null) material.Text( - widget.connection.name, + '${widget.connection.host}:${widget.connection.port ?? ''}', overflow: material.TextOverflow.ellipsis, maxLines: 1, style: material.TextStyle( - fontSize: 13, - color: theme.colorScheme.foreground, + fontSize: 11, + color: theme.colorScheme.mutedForeground, ), ), - if (widget.connection.host != null) - material.Text( - '${widget.connection.host}:${widget.connection.port ?? ''}', - overflow: material.TextOverflow.ellipsis, - maxLines: 1, - style: material.TextStyle( - fontSize: 11, - color: theme.colorScheme.mutedForeground, - ), - ), - ], - ), + ], ), - ], - ), + ), + ], ), ), ), @@ -1272,6 +1306,7 @@ class _MongoDatabaseNode extends StatelessWidget { class _PostgresConnectionTile extends StatefulWidget { const _PostgresConnectionTile({ required this.connection, + this.isSelected = false, required this.icon, this.iconAsset, required this.onRemove, @@ -1281,6 +1316,7 @@ class _PostgresConnectionTile extends StatefulWidget { }); final ConnectionRow connection; + final bool isSelected; final material.IconData icon; final String? iconAsset; final VoidCallback onRemove; @@ -1407,48 +1443,46 @@ class _PostgresConnectionTileState extends State<_PostgresConnectionTile> { ), ), material.Expanded( - child: material.MouseRegion( - cursor: material.SystemMouseCursors.click, - child: material.InkWell( - onTap: widget.onTap, - borderRadius: material.BorderRadius.circular(6), - child: material.Padding( - padding: const material.EdgeInsets.symmetric( - horizontal: 4, vertical: 6), - child: material.Row( - children: [ - iconWidget, - const Gap(8), - material.Expanded( - child: material.Column( - crossAxisAlignment: - material.CrossAxisAlignment.start, - mainAxisSize: material.MainAxisSize.min, - children: [ + child: _sidebarConnectionShell( + context: context, + isSelected: widget.isSelected, + onTap: widget.onTap, + child: material.Padding( + padding: const material.EdgeInsets.symmetric( + horizontal: 4, vertical: 6), + child: material.Row( + children: [ + iconWidget, + const Gap(8), + material.Expanded( + child: material.Column( + crossAxisAlignment: + material.CrossAxisAlignment.start, + mainAxisSize: material.MainAxisSize.min, + children: [ + material.Text( + widget.connection.name, + overflow: material.TextOverflow.ellipsis, + maxLines: 1, + style: material.TextStyle( + fontSize: 13, + color: theme.colorScheme.foreground, + ), + ), + if (widget.connection.host != null) material.Text( - widget.connection.name, + '${widget.connection.host}:${widget.connection.port ?? ''}', overflow: material.TextOverflow.ellipsis, maxLines: 1, style: material.TextStyle( - fontSize: 13, - color: theme.colorScheme.foreground, + fontSize: 11, + color: theme.colorScheme.mutedForeground, ), ), - if (widget.connection.host != null) - material.Text( - '${widget.connection.host}:${widget.connection.port ?? ''}', - overflow: material.TextOverflow.ellipsis, - maxLines: 1, - style: material.TextStyle( - fontSize: 11, - color: theme.colorScheme.mutedForeground, - ), - ), - ], - ), + ], ), - ], - ), + ), + ], ), ), ), @@ -1509,6 +1543,7 @@ class _PostgresConnectionTileState extends State<_PostgresConnectionTile> { class _MysqlConnectionTile extends StatefulWidget { const _MysqlConnectionTile({ required this.connection, + this.isSelected = false, required this.icon, this.iconAsset, required this.onRemove, @@ -1518,6 +1553,7 @@ class _MysqlConnectionTile extends StatefulWidget { }); final ConnectionRow connection; + final bool isSelected; final material.IconData icon; final String? iconAsset; final VoidCallback onRemove; @@ -1648,49 +1684,47 @@ class _MysqlConnectionTileState extends State<_MysqlConnectionTile> { ), ), material.Expanded( - child: material.MouseRegion( - cursor: material.SystemMouseCursors.click, - child: material.InkWell( - onTap: widget.onTap, - borderRadius: material.BorderRadius.circular(6), - child: material.Padding( - padding: const material.EdgeInsets.symmetric( - horizontal: 4, vertical: 6), - child: material.Row( - children: [ - iconWidget, - const Gap(8), - material.Expanded( - child: material.Column( - crossAxisAlignment: - material.CrossAxisAlignment.start, - mainAxisSize: material.MainAxisSize.min, - children: [ + child: _sidebarConnectionShell( + context: context, + isSelected: widget.isSelected, + onTap: widget.onTap, + child: material.Padding( + padding: const material.EdgeInsets.symmetric( + horizontal: 4, vertical: 6), + child: material.Row( + children: [ + iconWidget, + const Gap(8), + material.Expanded( + child: material.Column( + crossAxisAlignment: + material.CrossAxisAlignment.start, + mainAxisSize: material.MainAxisSize.min, + children: [ + material.Text( + widget.connection.name, + overflow: material.TextOverflow.ellipsis, + maxLines: 1, + style: material.TextStyle( + fontSize: 13, + color: theme.colorScheme.foreground, + ), + ), + if (widget.connection.host != null) material.Text( - widget.connection.name, + '${widget.connection.host}:${widget.connection.port ?? ''}', overflow: material.TextOverflow.ellipsis, maxLines: 1, style: material.TextStyle( - fontSize: 13, - color: theme.colorScheme.foreground, + fontSize: 11, + color: + theme.colorScheme.mutedForeground, ), ), - if (widget.connection.host != null) - material.Text( - '${widget.connection.host}:${widget.connection.port ?? ''}', - overflow: material.TextOverflow.ellipsis, - maxLines: 1, - style: material.TextStyle( - fontSize: 11, - color: - theme.colorScheme.mutedForeground, - ), - ), - ], - ), + ], ), - ], - ), + ), + ], ), ), ), From ee6a28e89e85f462748aa6f36a6d818fa33711b2 Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Sun, 19 Apr 2026 12:42:50 +0300 Subject: [PATCH 14/27] ui: title bar accent, borders, hero new connection flow --- lib/features/main_screen/main_screen.dart | 30 ++++++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/lib/features/main_screen/main_screen.dart b/lib/features/main_screen/main_screen.dart index e9882cf0..58c3fae1 100644 --- a/lib/features/main_screen/main_screen.dart +++ b/lib/features/main_screen/main_screen.dart @@ -4,9 +4,10 @@ import 'package:bitsdojo_window/bitsdojo_window.dart'; import 'package:flutter/material.dart' as material show Scaffold, Container, MainAxisSize, GestureDetector, MouseRegion, SystemMouseCursors, HitTestBehavior, Icons, Icon; import 'package:querya_desktop/core/storage/local_db.dart'; import 'package:querya_desktop/core/theme/app_theme.dart'; -import 'package:querya_desktop/shared/widgets/widgets.dart'; - +import 'package:querya_desktop/core/theme/querya_colors.dart'; +import 'package:querya_desktop/features/connections/connection_creation_flow.dart'; import 'package:querya_desktop/features/connections/connections_panel.dart'; +import 'package:querya_desktop/shared/widgets/widgets.dart'; import 'package:querya_desktop/features/mysql/mysql_object_kind.dart'; import 'package:querya_desktop/features/postgresql/postgres_object_kind.dart'; import 'package:querya_desktop/features/connections/driver_manager_dialog.dart'; @@ -21,6 +22,9 @@ class MainScreen extends StatefulWidget { } class _MainScreenState extends State { + final GlobalKey _connectionsPanelKey = + GlobalKey(); + static const double _minLeftWidth = 180; static const double _maxLeftWidth = 500; /// Minimum width reserved for workspace (avoid Row overflow when window is narrow). @@ -142,6 +146,13 @@ class _MainScreenState extends State { }); } + Future _openNewConnectionFromHero() async { + final row = await promptCreateConnection(context); + if (!mounted || row == null) return; + await LocalDb.instance.addConnection(row); + await _connectionsPanelKey.currentState?.reloadConnectionsFromDb(); + } + @override Widget build(BuildContext context) { final theme = AppTheme.dark.colorScheme; @@ -150,12 +161,12 @@ class _MainScreenState extends State { body: Theme( data: AppTheme.dark, child: WindowBorder( - color: theme.border.withValues(alpha: 0.6), + color: theme.border.withValues(alpha: 0.35), width: 1, child: Column( children: [ _CustomTitleBar(theme: theme), - const Divider(height: 1), + Divider(height: 1, color: theme.border.withValues(alpha: 0.22)), Expanded( child: LayoutBuilder( builder: (context, constraints) { @@ -178,6 +189,8 @@ class _MainScreenState extends State { SizedBox( width: leftW, child: ConnectionsPanel( + key: _connectionsPanelKey, + selectedConnectionId: _activeConnection?.id, onConnectionSelected: _onConnectionSelected, onRedisDatabaseSelected: _onRedisDatabaseSelected, onMongoDBDatabaseSelected: _onMongoDBDatabaseSelected, @@ -216,6 +229,9 @@ class _MainScreenState extends State { postgresSqlTabRequestToken: _postgresSqlTabRequestToken, selectedMysqlObject: _selectedMysqlObject, mysqlSqlTabRequestToken: _mysqlSqlTabRequestToken, + onRequestNewConnection: () { + _openNewConnectionFromHero(); + }, ), ), ], @@ -292,6 +308,12 @@ class _CustomTitleBarState extends State<_CustomTitleBar> { child: Row( children: [ const SizedBox(width: 16), + material.Icon( + material.Icons.search_rounded, + size: 18, + color: QueryaColors.accentCyan, + ), + const Gap(8), const Text('Querya').semiBold().small(), const Gap(24), Menubar( From 40f6bce48d9e72efb30173485b197c2ae7e8967c Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Sun, 19 Apr 2026 12:42:50 +0300 Subject: [PATCH 15/27] ui: show empty hero when no connection selected --- lib/features/main_screen/workspace_panel.dart | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/lib/features/main_screen/workspace_panel.dart b/lib/features/main_screen/workspace_panel.dart index 1cbad5c3..beee2950 100644 --- a/lib/features/main_screen/workspace_panel.dart +++ b/lib/features/main_screen/workspace_panel.dart @@ -1,4 +1,4 @@ -import 'package:flutter/material.dart' as material show Axis, Container, EdgeInsets, BoxDecoration, GestureDetector, Padding, BorderRadius, Center, CrossAxisAlignment, Icon, Icons, MouseRegion, AnimatedContainer, AnimatedScale, Curves, SystemMouseCursors, LayoutBuilder, HitTestBehavior, SizedBox, SingleChildScrollView, Row, MainAxisSize; +import 'package:flutter/material.dart' as material show Alignment, Axis, Container, EdgeInsets, BoxDecoration, GestureDetector, Padding, BorderRadius, Center, CrossAxisAlignment, Icon, Icons, MouseRegion, AnimatedContainer, AnimatedScale, Curves, SystemMouseCursors, LayoutBuilder, HitTestBehavior, SizedBox, SingleChildScrollView, Row, MainAxisSize; import 'package:querya_desktop/core/storage/local_db.dart'; import 'package:querya_desktop/shared/widgets/widgets.dart'; @@ -17,6 +17,7 @@ import 'package:querya_desktop/features/redis/redis_explorer_view.dart'; import 'package:querya_desktop/features/redis/redis_view.dart'; import 'query_editor_tab.dart'; import 'results_tab.dart'; +import 'workspace_empty_hero.dart'; Widget _pgObjectWorkspace({ required ConnectionRow connection, @@ -122,6 +123,7 @@ class WorkspacePanel extends StatefulWidget { this.postgresSqlTabRequestToken = 0, this.selectedMysqlObject, this.mysqlSqlTabRequestToken = 0, + this.onRequestNewConnection, }); /// Currently selected connection from the sidebar. @@ -150,6 +152,9 @@ class WorkspacePanel extends StatefulWidget { /// Incremented by [MainScreen] to switch the MySQL home view to the SQL tab. final int mysqlSqlTabRequestToken; + /// Empty-state hero: primary CTA to add a connection. + final void Function()? onRequestNewConnection; + @override State createState() => _WorkspacePanelState(); } @@ -163,6 +168,17 @@ class _WorkspacePanelState extends State { Widget build(BuildContext context) { final theme = Theme.of(context); + if (widget.activeConnection == null) { + return material.Container( + color: theme.colorScheme.background, + alignment: material.Alignment.topCenter, + child: WorkspaceEmptyHero( + onNewConnection: + widget.onRequestNewConnection ?? () {}, + ), + ); + } + // If a PostgreSQL connection is selected → stats, table/view, function, or sequence if (widget.activeConnection != null && widget.activeConnection!.type == 'postgresql') { From 91c0d375555be87fdaa2716fc5715e16a4961dd2 Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Sun, 19 Apr 2026 12:42:50 +0300 Subject: [PATCH 16/27] test: workspace panel layout uses stub connection for splitter --- .../main_screen/workspace_panel_layout_test.dart | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/test/features/main_screen/workspace_panel_layout_test.dart b/test/features/main_screen/workspace_panel_layout_test.dart index b92fac09..850aa696 100644 --- a/test/features/main_screen/workspace_panel_layout_test.dart +++ b/test/features/main_screen/workspace_panel_layout_test.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart' as material; import 'package:flutter_test/flutter_test.dart'; +import 'package:querya_desktop/core/storage/local_db.dart'; import 'package:querya_desktop/core/theme/app_theme.dart'; import 'package:querya_desktop/features/main_screen/workspace_panel.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; @@ -7,6 +8,15 @@ import 'package:shadcn_flutter/shadcn_flutter.dart'; import '../../support/layout_overflow.dart'; void main() { + const stubRedisConnection = ConnectionRow( + id: 1, + type: 'redis', + name: 'layout-stub', + host: '127.0.0.1', + port: 6379, + createdAt: '0', + ); + group('WorkspacePanel layout (no connection)', () { final sizes = { 'narrow_tall': const material.Size(320, 720), @@ -46,7 +56,7 @@ void main() { darkTheme: AppTheme.dark, themeMode: ThemeMode.dark, home: const material.SizedBox.expand( - child: WorkspacePanel(), + child: WorkspacePanel(activeConnection: stubRedisConnection), ), ), ); From e83e6e23a27cec9627640ad17f1a3afb3f7bff3d Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Sun, 19 Apr 2026 12:42:50 +0300 Subject: [PATCH 17/27] ui: adaptive database picker dialog and grid --- .../connections/new_connection_dialog.dart | 123 +++++++++++++----- 1 file changed, 88 insertions(+), 35 deletions(-) diff --git a/lib/features/connections/new_connection_dialog.dart b/lib/features/connections/new_connection_dialog.dart index cf940bc0..62b9bb61 100644 --- a/lib/features/connections/new_connection_dialog.dart +++ b/lib/features/connections/new_connection_dialog.dart @@ -1,4 +1,7 @@ +import 'dart:math' as math; + import 'package:flutter/material.dart' as material; +import 'package:querya_desktop/core/layout/window_layout.dart'; import 'package:querya_desktop/shared/widgets/widgets.dart'; /// Database type for new connection. @@ -43,10 +46,10 @@ enum _Category { all, sql, nosql } Future showNewConnectionDialog(BuildContext context) { return showAppDialog( context: context, - builder: (context) => const material.Dialog( + builder: (context) => material.Dialog( backgroundColor: material.Colors.transparent, - insetPadding: material.EdgeInsets.symmetric(horizontal: 40, vertical: 24), - child: _NewConnectionDialogContent(), + insetPadding: WindowLayout.dialogSymmetricInsets(context), + child: const _NewConnectionDialogContent(), ), ); } @@ -86,25 +89,34 @@ class _NewConnectionDialogContentState extends material.State<_NewConnectionDial material.Widget build(material.BuildContext context) { final theme = Theme.of(context).colorScheme; final radius = Theme.of(context).radiusXxl; + final mq = MediaQuery.sizeOf(context); + final dialogMaxW = WindowLayout.newConnectionDialogMaxWidth(mq.width); + final dialogH = WindowLayout.newConnectionDialogHeight(mq.height); + final sidebarW = WindowLayout.newConnectionSidebarWidth(dialogMaxW); + final headerPadH = dialogMaxW < 420 ? 16.0 : 24.0; + return material.Container( - constraints: const material.BoxConstraints(maxWidth: 740, minHeight: 380), + width: dialogMaxW, + constraints: material.BoxConstraints( + maxWidth: dialogMaxW, + maxHeight: dialogH, + minHeight: math.min(320.0, dialogH), + ), decoration: material.BoxDecoration( color: theme.popover, borderRadius: material.BorderRadius.circular(radius), border: material.Border.all(color: theme.muted), ), - child: material.ConstrainedBox( - constraints: const material.BoxConstraints(maxWidth: 740, minHeight: 380), - child: material.ClipRRect( - borderRadius: material.BorderRadius.circular(radius), - child: material.SizedBox( - height: 520, - child: material.Column( + child: material.ClipRRect( + borderRadius: material.BorderRadius.circular(radius), + child: material.SizedBox( + height: dialogH, + child: material.Column( mainAxisSize: material.MainAxisSize.min, crossAxisAlignment: material.CrossAxisAlignment.stretch, children: [ material.Padding( - padding: const material.EdgeInsets.fromLTRB(24, 24, 24, 8), + padding: material.EdgeInsets.fromLTRB(headerPadH, 20, headerPadH, 8), child: Column( crossAxisAlignment: material.CrossAxisAlignment.stretch, children: [ @@ -147,7 +159,7 @@ class _NewConnectionDialogContentState extends material.State<_NewConnectionDial crossAxisAlignment: material.CrossAxisAlignment.stretch, children: [ material.Container( - width: 140, + width: sidebarW, decoration: material.BoxDecoration( border: material.Border( right: material.BorderSide( @@ -186,16 +198,25 @@ class _NewConnectionDialogContentState extends material.State<_NewConnectionDial child: material.LayoutBuilder( builder: (context, constraints) { const spacing = 12.0; - const crossAxisCount = 4; - final cardWidth = (constraints.maxWidth - spacing * (crossAxisCount - 1)) / crossAxisCount; - const cardHeight = 132.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(crossAxisCount); + final cardWidth = crossAxisCount > 0 + ? (innerW - spacing * (crossAxisCount - 1)) / + crossAxisCount + : innerW; + final aspect = + cardHeight > 0 ? cardWidth / cardHeight : 1.0; return material.Padding( - padding: const material.EdgeInsets.all(16), + padding: material.EdgeInsets.all(gridPad), child: material.GridView.count( crossAxisCount: crossAxisCount, mainAxisSpacing: spacing, crossAxisSpacing: spacing, - childAspectRatio: cardWidth / cardHeight, + childAspectRatio: aspect.clamp(0.4, 4.0), shrinkWrap: true, physics: const material.ClampingScrollPhysics(), children: [ @@ -216,7 +237,10 @@ class _NewConnectionDialogContentState extends material.State<_NewConnectionDial ), ), material.Container( - padding: const material.EdgeInsets.symmetric(horizontal: 24, vertical: 16), + padding: material.EdgeInsets.symmetric( + horizontal: headerPadH, + vertical: 14, + ), decoration: material.BoxDecoration( border: material.Border( top: material.BorderSide(color: theme.border.withValues(alpha: 0.3)), @@ -241,7 +265,6 @@ class _NewConnectionDialogContentState extends material.State<_NewConnectionDial ), ], ), - ), ), ), ); @@ -317,7 +340,7 @@ class _DbTypeCardState extends material.State<_DbTypeCard> { child: material.AnimatedContainer( duration: const Duration(milliseconds: 120), curve: material.Curves.easeOut, - padding: const material.EdgeInsets.symmetric(vertical: 12, horizontal: 12), + 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), borderRadius: material.BorderRadius.circular(10), @@ -327,22 +350,52 @@ class _DbTypeCardState extends material.State<_DbTypeCard> { ), ), child: material.Column( - mainAxisSize: material.MainAxisSize.min, - mainAxisAlignment: material.MainAxisAlignment.center, + crossAxisAlignment: material.CrossAxisAlignment.stretch, children: [ - material.SizedBox( - width: 56, - height: 56, - child: widget.type.iconAsset != null - ? material.Image.asset( - widget.type.iconAsset!, - fit: material.BoxFit.contain, - filterQuality: material.FilterQuality.medium, - ) - : material.Icon(widget.type.icon, size: 56, color: t.primary), + material.Expanded( + child: material.Center( + child: material.SizedBox( + width: 52, + height: 52, + child: widget.type.iconAsset != null + ? material.Image.asset( + widget.type.iconAsset!, + fit: material.BoxFit.contain, + filterQuality: material.FilterQuality.medium, + ) + : material.Icon(widget.type.icon, size: 52, color: t.primary), + ), + ), + ), + const material.SizedBox(height: 6), + material.LayoutBuilder( + builder: (context, lc) { + return material.SizedBox( + height: 38, + child: material.FittedBox( + fit: material.BoxFit.scaleDown, + alignment: material.Alignment.center, + child: material.ConstrainedBox( + constraints: material.BoxConstraints( + maxWidth: math.max(48.0, lc.maxWidth), + ), + child: material.Text( + widget.type.label, + textAlign: material.TextAlign.center, + maxLines: 2, + overflow: material.TextOverflow.ellipsis, + style: material.TextStyle( + fontSize: 13, + fontWeight: material.FontWeight.w600, + height: 1.2, + color: t.foreground, + ), + ), + ), + ), + ); + }, ), - const material.SizedBox(height: 10), - Text(widget.type.label).semiBold().small(), ], ), ), From 3bf80b6260cc399d820fa3156e0c3504c05fc112 Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Sun, 19 Apr 2026 12:42:50 +0300 Subject: [PATCH 18/27] ui: adaptive dialog insets for driver manager --- lib/features/connections/driver_manager_dialog.dart | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/features/connections/driver_manager_dialog.dart b/lib/features/connections/driver_manager_dialog.dart index dc3ece3c..fa386c67 100644 --- a/lib/features/connections/driver_manager_dialog.dart +++ b/lib/features/connections/driver_manager_dialog.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart' as material; import 'package:querya_desktop/core/drivers/driver_storage.dart'; +import 'package:querya_desktop/core/layout/window_layout.dart'; import 'package:querya_desktop/shared/widgets/widgets.dart'; import 'new_connection_dialog.dart'; @@ -30,10 +31,10 @@ final _driverInfoList = <_DriverInfo>[ void showDriverManagerDialog(BuildContext context) { showAppDialog( context: context, - builder: (context) => const material.Dialog( + builder: (context) => material.Dialog( backgroundColor: material.Colors.transparent, - insetPadding: material.EdgeInsets.symmetric(horizontal: 40, vertical: 24), - child: _DriverManagerDialogContent(), + insetPadding: WindowLayout.dialogSymmetricInsets(context), + child: const _DriverManagerDialogContent(), ), ); } From b3b1f6f6fb668f6ffaf041ccee93f8136b095057 Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Sun, 19 Apr 2026 12:42:50 +0300 Subject: [PATCH 19/27] ui: adaptive dialog insets for new folder --- lib/features/connections/new_folder_dialog.dart | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/features/connections/new_folder_dialog.dart b/lib/features/connections/new_folder_dialog.dart index 8dae9849..15c95a5d 100644 --- a/lib/features/connections/new_folder_dialog.dart +++ b/lib/features/connections/new_folder_dialog.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart' as material; +import 'package:querya_desktop/core/layout/window_layout.dart'; import 'package:querya_desktop/shared/widgets/widgets.dart'; /// Shows a dialog to create a new folder (same style as new connection). @@ -6,10 +7,10 @@ import 'package:querya_desktop/shared/widgets/widgets.dart'; Future showNewFolderDialog(BuildContext context) { return showAppDialog( context: context, - builder: (context) => const material.Dialog( + builder: (context) => material.Dialog( backgroundColor: material.Colors.transparent, - insetPadding: material.EdgeInsets.symmetric(horizontal: 40, vertical: 24), - child: _NewFolderDialogContent(), + insetPadding: WindowLayout.dialogSymmetricInsets(context), + child: const _NewFolderDialogContent(), ), ); } From 487afb85fa7fa683b15ab69b20dae43ffc5cfd58 Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Sun, 19 Apr 2026 12:42:51 +0300 Subject: [PATCH 20/27] ui: adaptive dialog insets for PostgreSQL form --- lib/features/postgresql/postgresql_connection_form.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/features/postgresql/postgresql_connection_form.dart b/lib/features/postgresql/postgresql_connection_form.dart index 79a7b779..30ab6670 100644 --- a/lib/features/postgresql/postgresql_connection_form.dart +++ b/lib/features/postgresql/postgresql_connection_form.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:flutter/material.dart' as material; import 'package:querya_desktop/core/database/postgres_connection.dart'; +import 'package:querya_desktop/core/layout/window_layout.dart'; import 'package:querya_desktop/core/storage/local_db.dart'; import 'package:querya_desktop/shared/widgets/widgets.dart'; @@ -15,8 +16,7 @@ Future showPostgresConnectionForm( context: context, builder: (context) => material.Dialog( backgroundColor: material.Colors.transparent, - insetPadding: - const material.EdgeInsets.symmetric(horizontal: 40, vertical: 24), + insetPadding: WindowLayout.dialogSymmetricInsets(context), child: _PostgresConnectionFormContent(folderId: folderId), ), ); From 9015d22106df73c7597bc3824d2b6fb7c986c03f Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Sun, 19 Apr 2026 12:42:51 +0300 Subject: [PATCH 21/27] ui: adaptive dialog insets for MySQL form --- lib/features/mysql/mysql_connection_form.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/features/mysql/mysql_connection_form.dart b/lib/features/mysql/mysql_connection_form.dart index 92ba4cee..8227115f 100644 --- a/lib/features/mysql/mysql_connection_form.dart +++ b/lib/features/mysql/mysql_connection_form.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:flutter/material.dart' as material; import 'package:querya_desktop/core/database/mysql_connection.dart'; +import 'package:querya_desktop/core/layout/window_layout.dart'; import 'package:querya_desktop/core/storage/local_db.dart'; import 'package:querya_desktop/shared/widgets/widgets.dart'; @@ -15,8 +16,7 @@ Future showMysqlConnectionForm( context: context, builder: (context) => material.Dialog( backgroundColor: material.Colors.transparent, - insetPadding: - const material.EdgeInsets.symmetric(horizontal: 40, vertical: 24), + insetPadding: WindowLayout.dialogSymmetricInsets(context), child: _MysqlConnectionFormContent(folderId: folderId), ), ); From b26e9fbd5510bb2bcab9d03a1d5ac1a8ee53b0f0 Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Sun, 19 Apr 2026 12:42:51 +0300 Subject: [PATCH 22/27] ui: adaptive dialog insets for MongoDB form --- lib/features/mongodb/mongodb_connection_form.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/features/mongodb/mongodb_connection_form.dart b/lib/features/mongodb/mongodb_connection_form.dart index e998ca3f..f0526ece 100644 --- a/lib/features/mongodb/mongodb_connection_form.dart +++ b/lib/features/mongodb/mongodb_connection_form.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:flutter/material.dart' as material; import 'package:querya_desktop/core/database/mongodb_connection.dart'; +import 'package:querya_desktop/core/layout/window_layout.dart'; import 'package:querya_desktop/core/storage/local_db.dart'; import 'package:querya_desktop/shared/widgets/widgets.dart'; @@ -47,7 +48,7 @@ Future showMongoConnectionForm( context: context, builder: (context) => material.Dialog( backgroundColor: material.Colors.transparent, - insetPadding: const material.EdgeInsets.symmetric(horizontal: 40, vertical: 24), + insetPadding: WindowLayout.dialogSymmetricInsets(context), child: _MongoConnectionFormContent(folderId: folderId), ), ); From f4c30110e8612c07edf87e2da31f9b0926d6acb8 Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Sun, 19 Apr 2026 12:42:51 +0300 Subject: [PATCH 23/27] ui: adaptive dialog insets for Redis form --- lib/features/redis/redis_connection_form.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/features/redis/redis_connection_form.dart b/lib/features/redis/redis_connection_form.dart index d79d3c74..798b3a2c 100644 --- a/lib/features/redis/redis_connection_form.dart +++ b/lib/features/redis/redis_connection_form.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:flutter/material.dart' as material; import 'package:querya_desktop/core/database/redis_connection.dart'; +import 'package:querya_desktop/core/layout/window_layout.dart'; import 'package:querya_desktop/core/storage/local_db.dart'; import 'package:querya_desktop/shared/widgets/widgets.dart'; @@ -14,7 +15,7 @@ Future showRedisConnectionForm( context: context, builder: (context) => material.Dialog( backgroundColor: material.Colors.transparent, - insetPadding: const material.EdgeInsets.symmetric(horizontal: 40, vertical: 24), + insetPadding: WindowLayout.dialogSymmetricInsets(context), child: _RedisConnectionFormContent(folderId: folderId), ), ); From 61a0d341257f234f7cec40667e0bb775fc2ea4ef Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Sun, 19 Apr 2026 12:42:51 +0300 Subject: [PATCH 24/27] ui: adaptive dialog insets for create MongoDB database --- lib/features/mongodb/mongo_database_dialog.dart | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/features/mongodb/mongo_database_dialog.dart b/lib/features/mongodb/mongo_database_dialog.dart index a833dad4..dcf15582 100644 --- a/lib/features/mongodb/mongo_database_dialog.dart +++ b/lib/features/mongodb/mongo_database_dialog.dart @@ -1,14 +1,15 @@ import 'package:flutter/material.dart' as material; +import 'package:querya_desktop/core/layout/window_layout.dart'; import 'package:querya_desktop/shared/widgets/widgets.dart'; /// Shows dialog to create a new MongoDB database. Future showCreateMongoDBDialog(material.BuildContext context) async { return showAppDialog( context: context, - builder: (context) => const material.Dialog( + builder: (context) => material.Dialog( backgroundColor: material.Colors.transparent, - insetPadding: material.EdgeInsets.symmetric(horizontal: 40, vertical: 24), - child: _CreateMongoDBDialogContent(), + insetPadding: WindowLayout.dialogSymmetricInsets(context), + child: const _CreateMongoDBDialogContent(), ), ); } From 0a25ebc6d5bbd02e41662c5ac4cfb22414190d3c Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Sun, 19 Apr 2026 12:42:51 +0300 Subject: [PATCH 25/27] ui: adaptive dialog insets for table privileges --- lib/features/postgresql/postgres_table_privileges_dialog.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/features/postgresql/postgres_table_privileges_dialog.dart b/lib/features/postgresql/postgres_table_privileges_dialog.dart index 3130f008..512055d1 100644 --- a/lib/features/postgresql/postgres_table_privileges_dialog.dart +++ b/lib/features/postgresql/postgres_table_privileges_dialog.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart' as material; import 'package:querya_desktop/core/database/postgres_connection.dart'; +import 'package:querya_desktop/core/layout/window_layout.dart'; import 'package:querya_desktop/core/database/postgres_metadata.dart'; import 'package:querya_desktop/shared/widgets/widgets.dart'; @@ -79,8 +80,7 @@ class _PrivilegesDialogBodyState extends material.State<_PrivilegesDialogBody> { final radius = Theme.of(context).radiusXxl; return material.Dialog( backgroundColor: material.Colors.transparent, - insetPadding: - const material.EdgeInsets.symmetric(horizontal: 32, vertical: 24), + insetPadding: WindowLayout.dialogSymmetricInsets(context), child: material.Container( constraints: const material.BoxConstraints( maxWidth: 560, From 27035d5405465ec920239b4228fba66005e4b25e Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Sun, 19 Apr 2026 12:42:51 +0300 Subject: [PATCH 26/27] chore: update .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 5c073f8c..229ea50b 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ Thumbs.db *.dll *.exe flutter_*.log +design-front From 04b9d12c2a2c717aab8f8f7f7197e5ee0085e5dd Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Sun, 19 Apr 2026 13:07:40 +0300 Subject: [PATCH 27/27] fix --- .flutter-plugins-dependencies | 2 +- lib/core/layout/window_layout.dart | 6 ++-- .../connections/connection_creation_flow.dart | 21 +++++------ .../connections/connections_panel.dart | 4 +-- lib/features/main_screen/main_screen.dart | 2 +- .../main_screen/workspace_empty_hero.dart | 36 ++++++++++++------- .../workspace_panel_layout_test.dart | 10 +++--- 7 files changed, 49 insertions(+), 32 deletions(-) diff --git a/.flutter-plugins-dependencies b/.flutter-plugins-dependencies index c00bcc79..7d469f53 100644 --- a/.flutter-plugins-dependencies +++ b/.flutter-plugins-dependencies @@ -1 +1 @@ -{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"path_provider_foundation","path":"/home/zhuchka/.pub-cache/hosted/pub.dev/path_provider_foundation-2.6.0/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"sqflite_darwin","path":"/home/zhuchka/.pub-cache/hosted/pub.dev/sqflite_darwin-2.4.2/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false}],"android":[{"name":"jni","path":"/home/zhuchka/.pub-cache/hosted/pub.dev/jni-1.0.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"jni_flutter","path":"/home/zhuchka/.pub-cache/hosted/pub.dev/jni_flutter-1.0.1/","native_build":true,"dependencies":["jni"],"dev_dependency":false},{"name":"path_provider_android","path":"/home/zhuchka/.pub-cache/hosted/pub.dev/path_provider_android-2.3.1/","native_build":false,"dependencies":["jni","jni_flutter"],"dev_dependency":false},{"name":"sqflite_android","path":"/home/zhuchka/.pub-cache/hosted/pub.dev/sqflite_android-2.4.2+3/","native_build":true,"dependencies":[],"dev_dependency":false}],"macos":[{"name":"bitsdojo_window_macos","path":"/home/zhuchka/.pub-cache/hosted/pub.dev/bitsdojo_window_macos-0.1.4/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_foundation","path":"/home/zhuchka/.pub-cache/hosted/pub.dev/path_provider_foundation-2.6.0/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"sqflite_darwin","path":"/home/zhuchka/.pub-cache/hosted/pub.dev/sqflite_darwin-2.4.2/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false}],"linux":[{"name":"bitsdojo_window_linux","path":"/home/zhuchka/.pub-cache/hosted/pub.dev/bitsdojo_window_linux-0.1.4/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"jni","path":"/home/zhuchka/.pub-cache/hosted/pub.dev/jni-1.0.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_linux","path":"/home/zhuchka/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1/","native_build":false,"dependencies":[],"dev_dependency":false}],"windows":[{"name":"bitsdojo_window_windows","path":"/home/zhuchka/.pub-cache/hosted/pub.dev/bitsdojo_window_windows-0.1.6/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"jni","path":"/home/zhuchka/.pub-cache/hosted/pub.dev/jni-1.0.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_windows","path":"/home/zhuchka/.pub-cache/hosted/pub.dev/path_provider_windows-2.3.0/","native_build":false,"dependencies":[],"dev_dependency":false}],"web":[]},"dependencyGraph":[{"name":"bitsdojo_window","dependencies":["bitsdojo_window_windows","bitsdojo_window_macos","bitsdojo_window_linux"]},{"name":"bitsdojo_window_linux","dependencies":[]},{"name":"bitsdojo_window_macos","dependencies":[]},{"name":"bitsdojo_window_windows","dependencies":[]},{"name":"jni","dependencies":[]},{"name":"jni_flutter","dependencies":["jni"]},{"name":"path_provider","dependencies":["path_provider_android","path_provider_foundation","path_provider_linux","path_provider_windows"]},{"name":"path_provider_android","dependencies":["jni","jni_flutter"]},{"name":"path_provider_foundation","dependencies":[]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"sqflite","dependencies":["sqflite_android","sqflite_darwin"]},{"name":"sqflite_android","dependencies":[]},{"name":"sqflite_darwin","dependencies":[]}],"date_created":"2026-04-11 12:30:47.798124","version":"3.41.6","swift_package_manager_enabled":{"ios":false,"macos":false}} \ No newline at end of file +{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"path_provider_foundation","path":"/home/zhuchka/.pub-cache/hosted/pub.dev/path_provider_foundation-2.6.0/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"sqflite_darwin","path":"/home/zhuchka/.pub-cache/hosted/pub.dev/sqflite_darwin-2.4.2/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false}],"android":[{"name":"jni","path":"/home/zhuchka/.pub-cache/hosted/pub.dev/jni-1.0.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"jni_flutter","path":"/home/zhuchka/.pub-cache/hosted/pub.dev/jni_flutter-1.0.1/","native_build":true,"dependencies":["jni"],"dev_dependency":false},{"name":"path_provider_android","path":"/home/zhuchka/.pub-cache/hosted/pub.dev/path_provider_android-2.3.1/","native_build":false,"dependencies":["jni","jni_flutter"],"dev_dependency":false},{"name":"sqflite_android","path":"/home/zhuchka/.pub-cache/hosted/pub.dev/sqflite_android-2.4.2+3/","native_build":true,"dependencies":[],"dev_dependency":false}],"macos":[{"name":"bitsdojo_window_macos","path":"/home/zhuchka/.pub-cache/hosted/pub.dev/bitsdojo_window_macos-0.1.4/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_foundation","path":"/home/zhuchka/.pub-cache/hosted/pub.dev/path_provider_foundation-2.6.0/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"sqflite_darwin","path":"/home/zhuchka/.pub-cache/hosted/pub.dev/sqflite_darwin-2.4.2/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false}],"linux":[{"name":"bitsdojo_window_linux","path":"/home/zhuchka/.pub-cache/hosted/pub.dev/bitsdojo_window_linux-0.1.4/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"jni","path":"/home/zhuchka/.pub-cache/hosted/pub.dev/jni-1.0.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_linux","path":"/home/zhuchka/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1/","native_build":false,"dependencies":[],"dev_dependency":false}],"windows":[{"name":"bitsdojo_window_windows","path":"/home/zhuchka/.pub-cache/hosted/pub.dev/bitsdojo_window_windows-0.1.6/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"jni","path":"/home/zhuchka/.pub-cache/hosted/pub.dev/jni-1.0.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_windows","path":"/home/zhuchka/.pub-cache/hosted/pub.dev/path_provider_windows-2.3.0/","native_build":false,"dependencies":[],"dev_dependency":false}],"web":[]},"dependencyGraph":[{"name":"bitsdojo_window","dependencies":["bitsdojo_window_windows","bitsdojo_window_macos","bitsdojo_window_linux"]},{"name":"bitsdojo_window_linux","dependencies":[]},{"name":"bitsdojo_window_macos","dependencies":[]},{"name":"bitsdojo_window_windows","dependencies":[]},{"name":"jni","dependencies":[]},{"name":"jni_flutter","dependencies":["jni"]},{"name":"path_provider","dependencies":["path_provider_android","path_provider_foundation","path_provider_linux","path_provider_windows"]},{"name":"path_provider_android","dependencies":["jni","jni_flutter"]},{"name":"path_provider_foundation","dependencies":[]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"sqflite","dependencies":["sqflite_android","sqflite_darwin"]},{"name":"sqflite_android","dependencies":[]},{"name":"sqflite_darwin","dependencies":[]}],"date_created":"2026-04-19 12:51:07.216547","version":"3.41.6","swift_package_manager_enabled":{"ios":false,"macos":false}} \ No newline at end of file diff --git a/lib/core/layout/window_layout.dart b/lib/core/layout/window_layout.dart index fd17520e..a926d607 100644 --- a/lib/core/layout/window_layout.dart +++ b/lib/core/layout/window_layout.dart @@ -61,9 +61,11 @@ abstract class WindowLayout { }; } - /// Empty workspace hero content max width. + /// Empty workspace hero content max width (stays within viewport minus padding). static double heroContentMaxWidth(double viewportWidth) { - return math.min(560, math.max(260.0, viewportWidth * 0.92)); + final pad = heroHorizontalPadding(viewportWidth) * 2; + final inner = math.max(0.0, viewportWidth - pad); + return math.min(560, inner); } static double heroHorizontalPadding(double viewportWidth) { diff --git a/lib/features/connections/connection_creation_flow.dart b/lib/features/connections/connection_creation_flow.dart index 184c5909..935ff472 100644 --- a/lib/features/connections/connection_creation_flow.dart +++ b/lib/features/connections/connection_creation_flow.dart @@ -14,14 +14,15 @@ Future promptCreateConnection( }) async { final type = await showNewConnectionDialog(context); if (type == null) return null; - return switch (type) { - ConnectionType.postgresql => - await showPostgresConnectionForm(context, folderId: folderId), - ConnectionType.mysql => - await showMysqlConnectionForm(context, folderId: folderId), - ConnectionType.mongodb => - await showMongoConnectionForm(context, folderId: folderId), - ConnectionType.redis => - await showRedisConnectionForm(context, folderId: folderId), - }; + if (!context.mounted) return null; + switch (type) { + case ConnectionType.postgresql: + return await showPostgresConnectionForm(context, folderId: folderId); + case ConnectionType.mysql: + return await showMysqlConnectionForm(context, folderId: folderId); + case ConnectionType.mongodb: + return await showMongoConnectionForm(context, folderId: folderId); + case ConnectionType.redis: + return await showRedisConnectionForm(context, folderId: folderId); + } } diff --git a/lib/features/connections/connections_panel.dart b/lib/features/connections/connections_panel.dart index 4a32e54e..10ad02c2 100644 --- a/lib/features/connections/connections_panel.dart +++ b/lib/features/connections/connections_panel.dart @@ -1,4 +1,4 @@ -import 'package:flutter/material.dart' as material show AlertDialog, BoxConstraints, BuildContext, Column, ConstrainedBox, Container, BoxDecoration, Border, BorderSide, InkWell, Icon, Icons, IconData, Image, EdgeInsets, BorderRadius, CrossAxisAlignment, MainAxisSize, MouseRegion, SystemMouseCursors, DefaultTextStyle, TextStyle, CustomScrollView, SliverToBoxAdapter, SliverFillRemaining, SliverPadding, GestureDetector, HitTestBehavior, SizedBox, AnimatedRotation, Row, BoxFit, Text, TextOverflow, Expanded, CircularProgressIndicator, Material, StatelessWidget, Colors, Tooltip, Color, LayoutBuilder, TextPainter, TextSpan, TextDirection, SelectableText, Padding, Widget, Navigator, ValueKey, FontWeight, VoidCallback; +import 'package:flutter/material.dart' as material show AlertDialog, BoxConstraints, BuildContext, Column, ConstrainedBox, Container, BoxDecoration, Border, BorderSide, InkWell, Icon, Icons, IconData, Image, EdgeInsets, BorderRadius, CrossAxisAlignment, MainAxisSize, MouseRegion, SystemMouseCursors, TextStyle, CustomScrollView, SliverToBoxAdapter, SliverFillRemaining, SliverPadding, GestureDetector, HitTestBehavior, SizedBox, AnimatedRotation, Row, BoxFit, Text, TextOverflow, Expanded, CircularProgressIndicator, Material, StatelessWidget, Colors, Tooltip, Color, LayoutBuilder, TextPainter, TextSpan, TextDirection, SelectableText, Padding, Widget, Navigator, ValueKey, FontWeight, VoidCallback; 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'; @@ -376,7 +376,7 @@ class ConnectionsPanelState extends State { 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; + if (!mounted || !menuContext.mounted) return; await _createConnection(dialogContext: menuContext); }, child: const Text('New Connection'), diff --git a/lib/features/main_screen/main_screen.dart b/lib/features/main_screen/main_screen.dart index 58c3fae1..0b424b2f 100644 --- a/lib/features/main_screen/main_screen.dart +++ b/lib/features/main_screen/main_screen.dart @@ -308,7 +308,7 @@ class _CustomTitleBarState extends State<_CustomTitleBar> { child: Row( children: [ const SizedBox(width: 16), - material.Icon( + const material.Icon( material.Icons.search_rounded, size: 18, color: QueryaColors.accentCyan, diff --git a/lib/features/main_screen/workspace_empty_hero.dart b/lib/features/main_screen/workspace_empty_hero.dart index 2c71b215..7c0828f6 100644 --- a/lib/features/main_screen/workspace_empty_hero.dart +++ b/lib/features/main_screen/workspace_empty_hero.dart @@ -38,21 +38,29 @@ class WorkspaceEmptyHero extends StatelessWidget { material.SizedBox(height: compact ? 16 : 20), material.Padding( padding: const material.EdgeInsets.symmetric(horizontal: 4), - child: Text( + child: material.Text( 'A lightweight desktop client for every database you ship', textAlign: material.TextAlign.center, - ) - .large() - .semiBold(), + style: material.TextStyle( + fontSize: 20, + fontWeight: material.FontWeight.w600, + color: cs.foreground, + ), + ), ), material.SizedBox(height: compact ? 10 : 12), material.Padding( padding: const material.EdgeInsets.symmetric(horizontal: 4), - child: Text( + child: material.Text( 'Connect to PostgreSQL, MySQL, Redis and MongoDB from a single ' 'focused app with a calm dark interface.', textAlign: material.TextAlign.center, - ).muted().small(), + style: material.TextStyle( + fontSize: 14, + height: 1.4, + color: cs.mutedForeground, + ), + ), ), const material.SizedBox(height: 10), Text( @@ -118,7 +126,7 @@ class _HeroBadge extends StatelessWidget { ), ), child: material.Row( - mainAxisSize: material.MainAxisSize.min, + mainAxisSize: material.MainAxisSize.max, children: [ material.Icon( material.Icons.auto_awesome_rounded, @@ -126,11 +134,15 @@ class _HeroBadge extends StatelessWidget { color: colorScheme.primary, ), material.SizedBox(width: compact ? 6 : 8), - Text( - 'One app for SQL and NoSQL', - style: material.TextStyle( - fontSize: compact ? 11 : 12, - color: colorScheme.foreground.withValues(alpha: 0.92), + material.Expanded( + child: material.Text( + 'One app for SQL and NoSQL', + maxLines: 2, + overflow: material.TextOverflow.ellipsis, + style: material.TextStyle( + fontSize: compact ? 11 : 12, + color: colorScheme.foreground.withValues(alpha: 0.92), + ), ), ), ], diff --git a/test/features/main_screen/workspace_panel_layout_test.dart b/test/features/main_screen/workspace_panel_layout_test.dart index 850aa696..d65f43e1 100644 --- a/test/features/main_screen/workspace_panel_layout_test.dart +++ b/test/features/main_screen/workspace_panel_layout_test.dart @@ -8,12 +8,14 @@ import 'package:shadcn_flutter/shadcn_flutter.dart'; import '../../support/layout_overflow.dart'; void main() { - const stubRedisConnection = ConnectionRow( + /// Type must not be postgresql/mysql/mongodb/redis so [WorkspacePanel] uses the + /// default Query/Output split (includes resize handle). Not a real DB driver. + const stubSplitWorkspaceConnection = ConnectionRow( id: 1, - type: 'redis', + type: '_layout_test_split', name: 'layout-stub', host: '127.0.0.1', - port: 6379, + port: 0, createdAt: '0', ); @@ -56,7 +58,7 @@ void main() { darkTheme: AppTheme.dark, themeMode: ThemeMode.dark, home: const material.SizedBox.expand( - child: WorkspacePanel(activeConnection: stubRedisConnection), + child: WorkspacePanel(activeConnection: stubSplitWorkspaceConnection), ), ), );