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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 13 additions & 7 deletions lib/core/motion/querya_cross_fade_stack.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import 'querya_motion_context.dart';

/// Like [IndexedStack] but cross-fades the active child; off-screen children
/// stay mounted (preserves SQL editor state, etc.).
///
/// Enter/exit curves and index clamping match [QueryaSwitchingBody] (without
/// the optional slide).
class QueryaCrossFadeStack extends StatelessWidget {
const QueryaCrossFadeStack({
super.key,
Expand All @@ -17,26 +20,29 @@ class QueryaCrossFadeStack extends StatelessWidget {

@override
Widget build(BuildContext context) {
assert(children.isNotEmpty, 'QueryaCrossFadeStack requires children');
final safeIndex = index.clamp(0, children.length - 1);
final duration = context.motionDuration(QueryaMotion.standard);
final curve = context.motionCurve(QueryaMotion.enter);
final inCurve = context.motionCurve(QueryaMotion.enter);
final outCurve = context.motionCurve(QueryaMotion.exit);

return Stack(
fit: StackFit.expand,
children: [
for (var i = 0; i < children.length; i++)
Positioned.fill(
child: IgnorePointer(
ignoring: index != i,
ignoring: i != safeIndex,
child: ExcludeFocus(
excluding: index != i,
excluding: i != safeIndex,
child: ExcludeSemantics(
excluding: index != i,
excluding: i != safeIndex,
child: AnimatedOpacity(
opacity: index == i ? 1 : 0,
opacity: i == safeIndex ? 1 : 0,
duration: duration,
curve: curve,
curve: i == safeIndex ? inCurve : outCurve,
child: TickerMode(
enabled: index == i,
enabled: i == safeIndex,
child: RepaintBoundary(child: children[i]),
),
),
Expand Down
4 changes: 4 additions & 0 deletions lib/core/motion/querya_hover_surface.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import 'querya_motion.dart';
import 'querya_motion_context.dart';

/// Unified hover background / border using motion tokens (Responsive chrome).
///
/// **Scope:** selection / picker cards (e.g. connection type tiles). Dense
/// trees and explorer rows keep lighter `InkWell` / `MouseRegion` hover —
/// do not broaden adoption without an explicit follow-up.
class QueryaHoverSurface extends StatefulWidget {
const QueryaHoverSurface({
super.key,
Expand Down
6 changes: 4 additions & 2 deletions lib/core/motion/querya_stagger.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class _QueryaStaggerState extends State<QueryaStagger>
with SingleTickerProviderStateMixin {
late final AnimationController _controller;
bool _played = false;
Duration _effectiveStep = kQueryaStaggerStep;

@override
void initState() {
Expand All @@ -47,12 +48,13 @@ class _QueryaStaggerState extends State<QueryaStagger>
if (n == 0) return;

final base = context.motionDuration(QueryaMotion.fast);
_effectiveStep = context.motionDuration(widget.step);
if (base == QueryaMotion.instant) {
_controller.value = 1;
return;
}

final total = base + widget.step * n;
final total = base + _effectiveStep * n;
_controller.duration = total;
_controller.forward();
}
Expand Down Expand Up @@ -96,7 +98,7 @@ class _QueryaStaggerState extends State<QueryaStagger>
final totalMs = _controller.duration!.inMilliseconds;
if (totalMs <= 0) return 1;

final stepMs = widget.step.inMilliseconds;
final stepMs = _effectiveStep.inMilliseconds;
final start = (stepMs * index) / totalMs;
final end = (start + 0.35).clamp(0.0, 1.0);
final t = _controller.value;
Expand Down
13 changes: 9 additions & 4 deletions lib/core/sdui/sdui_tree_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'package:querya_desktop/core/motion/querya_motion_context.dart';
import 'package:querya_desktop/core/sdui/sdui_tree_schema.dart';
import 'package:querya_desktop/core/ui/querya_icon_sizes.dart';
import 'package:querya_desktop/core/ui/querya_icons.dart';
import 'package:querya_desktop/core/ui/querya_tree_tokens.dart';
import 'package:querya_desktop/shared/widgets/widgets.dart';

/// Renders a sidebar-style tree from an SDUI schema with lazy expansion.
Expand Down Expand Up @@ -174,7 +175,9 @@ class SduiTreeBuilderState extends material.State<SduiTreeBuilder> {
final row = rows[index];
if (row.isError) {
return material.Padding(
padding: material.EdgeInsets.only(left: 36.0 + row.depth * 16.0),
padding: material.EdgeInsets.only(
left: 36.0 + row.depth * QueryaTreeTokens.indent,
),
child: material.Align(
alignment: material.Alignment.centerLeft,
child: Text(row.error!).muted().xSmall(),
Expand All @@ -196,7 +199,6 @@ class SduiTreeBuilderState extends material.State<SduiTreeBuilder> {
material.Widget _buildNodeRow(SduiTreeNode node, {required int depth}) {
final theme = Theme.of(context);
final muted = theme.colorScheme.mutedForeground;
final primary = theme.colorScheme.primary;
final canExpand = node.expandable || node.hasChildren;
final isExpanded = _expanded.contains(node.id);
final isLoading = _loading.contains(node.id);
Expand All @@ -205,8 +207,11 @@ class SduiTreeBuilderState extends material.State<SduiTreeBuilder> {
// Same hierarchy as native trees (#476 / #497) — no separate sduiNode size.
final iconSize =
canExpand ? QueryaIconSizes.treeGroup : QueryaIconSizes.treeLeaf;
final iconColor = isBrowsable ? primary.withValues(alpha: 0.5) : muted;
final rowLeft = 8.0 + depth * 16.0 + (canExpand ? 0 : 4.0);
final iconColor = isBrowsable
? QueryaTreeTokens.leafIconColor(theme.colorScheme.primary)
: muted;
final rowLeft =
8.0 + depth * QueryaTreeTokens.indent + (canExpand ? 0 : 4.0);

return material.InkWell(
onTap: () {
Expand Down
11 changes: 9 additions & 2 deletions lib/core/ui/querya_icons.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ abstract final class QueryaIcons {
material.Icons.account_tree_rounded;
static const material.IconData schema = material.Icons.diamond_rounded;
static const material.IconData extension = material.Icons.extension_rounded;
static const material.IconData publicSchema = material.Icons.public_rounded;
static const material.IconData foreignData = material.Icons.hub_rounded;

static const material.IconData tableGroup =
material.Icons.table_chart_rounded;
Expand All @@ -23,11 +23,18 @@ abstract final class QueryaIcons {
static const material.IconData viewLeaf = material.Icons.view_week_rounded;
static const material.IconData materializedViewGroup =
material.Icons.dynamic_feed_rounded;
static const material.IconData materializedViewLeaf =
material.Icons.layers_rounded;
static const material.IconData functionGroup =
material.Icons.functions_rounded;
static const material.IconData functionLeaf = material.Icons.code_rounded;
static const material.IconData sequence =
static const material.IconData sequenceGroup =
material.Icons.format_list_numbered_rounded;
static const material.IconData sequenceLeaf =
material.Icons.looks_one_rounded;

/// Alias kept for call sites that still say "sequence" as the group icon.
static const material.IconData sequence = sequenceGroup;
static const material.IconData indexes = material.Icons.table_rows_rounded;
static const material.IconData triggers = material.Icons.bolt_rounded;
static const material.IconData types = material.Icons.category_rounded;
Expand Down
2 changes: 2 additions & 0 deletions lib/core/ui/querya_tooltip.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/// Shared [Tooltip.waitDuration] for dense chrome (trees, grids, …).
const Duration kQueryaTooltipWait = Duration(milliseconds: 450);
14 changes: 14 additions & 0 deletions lib/core/ui/querya_tree_tokens.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import 'package:flutter/material.dart';

/// Shared connection-tree metrics and colors (PG / MySQL / SQLite / SDUI).
abstract final class QueryaTreeTokens {
/// Indent for schema rows and sibling object folders under a database.
static const double indent = 16;

/// Leaf-row icon tint (tables, views, sequences, …).
///
/// Takes [primary] (not [ColorScheme]) so both Material and shadcn schemes
/// can pass `.primary` without a type clash.
static Color leafIconColor(Color primary) =>
primary.withValues(alpha: 0.5);
}
2 changes: 2 additions & 0 deletions lib/features/connections/connections_panel.dart
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ import 'package:querya_desktop/core/storage/local_db.dart';
import 'package:querya_desktop/core/theme/querya_typography.dart';
import 'package:querya_desktop/core/ui/querya_icon_sizes.dart';
import 'package:querya_desktop/core/ui/querya_icons.dart';
import 'package:querya_desktop/core/ui/querya_tooltip.dart';
import 'package:querya_desktop/core/ui/querya_tree_tokens.dart';
import 'package:querya_desktop/core/motion/querya_animated_expand.dart';
import 'package:querya_desktop/core/motion/querya_motion.dart';
import 'package:querya_desktop/core/motion/querya_motion_context.dart';
Expand Down
4 changes: 3 additions & 1 deletion lib/features/connections/connections_panel_mysql.dart
Original file line number Diff line number Diff line change
Expand Up @@ -668,7 +668,9 @@ class _MysqlObjectGroupState extends State<_MysqlObjectGroup> {
label: item,
icon: widget.itemIcon,
iconSize: QueryaIconSizes.treeLeaf,
iconColor: theme.colorScheme.mutedForeground,
iconColor: QueryaTreeTokens.leafIconColor(
theme.colorScheme.primary,
),
textStyle: material.TextStyle(
fontSize: 11,
color: theme.colorScheme.foreground,
Expand Down
16 changes: 9 additions & 7 deletions lib/features/connections/connections_panel_pg_tree.dart
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ class _PgTreeRowLabel extends material.StatelessWidget {
if (label.length < _tooltipMinLength) return text;
return material.Tooltip(
message: label,
waitDuration: const Duration(milliseconds: 450),
waitDuration: kQueryaTooltipWait,
child: text,
);
}
Expand Down Expand Up @@ -381,7 +381,7 @@ class _PgDatabaseNodeState extends State<_PgDatabaseNode> {
connection: widget.connection,
databaseName: widget.databaseName,
label: 'Foreign data',
icon: QueryaIcons.publicSchema,
icon: QueryaIcons.foreignData,
kind: PostgresObjectKind.databaseForeignData,
onPostgresObjectSelected: widget.onPostgresObjectSelected,
onPostgresOpenSqlWorkspace: widget.onPostgresOpenSqlWorkspace,
Expand Down Expand Up @@ -682,7 +682,7 @@ class _PgSchemaNodeState extends State<_PgSchemaNode> {
Widget build(BuildContext context) {
final theme = Theme.of(context);
return material.Padding(
padding: const material.EdgeInsets.only(left: 12),
padding: const material.EdgeInsets.only(left: QueryaTreeTokens.indent),
child: material.Column(
crossAxisAlignment: material.CrossAxisAlignment.start,
mainAxisSize: material.MainAxisSize.min,
Expand Down Expand Up @@ -794,7 +794,7 @@ class _PgSchemaNodeState extends State<_PgSchemaNode> {
onRefresh: _loadObjects,
label: 'Materialized views',
icon: QueryaIcons.materializedViewGroup,
itemIcon: QueryaIcons.materializedViewGroup,
itemIcon: QueryaIcons.materializedViewLeaf,
items: _matviews,
onItemTap: widget.onPostgresObjectSelected != null
? (name) => widget.onPostgresObjectSelected!(
Expand Down Expand Up @@ -837,8 +837,8 @@ class _PgSchemaNodeState extends State<_PgSchemaNode> {
widget.onPostgresOpenSqlWorkspace,
onRefresh: _loadObjects,
label: 'Sequences',
icon: QueryaIcons.sequence,
itemIcon: QueryaIcons.sequence,
icon: QueryaIcons.sequenceGroup,
itemIcon: QueryaIcons.sequenceLeaf,
items: _sequences,
onItemTap: widget.onPostgresObjectSelected != null
? (name) => widget.onPostgresObjectSelected!(
Expand Down Expand Up @@ -1045,7 +1045,9 @@ class _PgObjectGroupState extends State<_PgObjectGroup> {
label: item,
icon: widget.itemIcon,
iconSize: QueryaIconSizes.treeLeaf,
iconColor: theme.colorScheme.primary.withValues(alpha: 0.5),
iconColor: QueryaTreeTokens.leafIconColor(
theme.colorScheme.primary,
),
textStyle: material.TextStyle(
fontSize: 11,
color: theme.colorScheme.foreground,
Expand Down
4 changes: 3 additions & 1 deletion lib/features/connections/connections_panel_sqlite.dart
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,9 @@ class _SqliteObjectGroupState extends State<_SqliteObjectGroup> {
label: item,
icon: widget.itemIcon,
iconSize: QueryaIconSizes.treeLeaf,
iconColor: theme.colorScheme.mutedForeground,
iconColor: QueryaTreeTokens.leafIconColor(
theme.colorScheme.primary,
),
textStyle: material.TextStyle(
fontSize: 11,
color: theme.colorScheme.foreground,
Expand Down
3 changes: 2 additions & 1 deletion lib/features/main_screen/result_grid_view.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart' as material;
import 'package:flutter/services.dart' show Clipboard, ClipboardData;
import 'package:querya_desktop/core/layout/ui_scale.dart';
import 'package:querya_desktop/core/ui/querya_tooltip.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart';

/// Layout metrics for [VirtualResultGrid].
Expand Down Expand Up @@ -485,7 +486,7 @@ class _GridCell extends material.StatelessWidget {

return material.Tooltip(
message: text,
waitDuration: const Duration(milliseconds: 400),
waitDuration: kQueryaTooltipWait,
child: interactiveCell,
);
}
Expand Down
39 changes: 24 additions & 15 deletions lib/features/main_screen/workspace_empty_hero.dart
Original file line number Diff line number Diff line change
Expand Up @@ -182,22 +182,31 @@ class _WorkspaceEmptyHeroState extends State<WorkspaceEmptyHero> {
QueryaFadeSlide(
alignment: material.Alignment.topCenter,
offset: const material.Offset(0, 0.03),
child: showRecent
? _RecentConnectionsSection(
key: const material.ValueKey('empty_recent_section'),
connections: _recent,
onOpenConnection: widget.onOpenConnection,
compact: compact,
child: !_loaded
? const material.SizedBox(
key: material.ValueKey('empty_section_loading'),
height: 120,
)
: _QuickStartSection(
key: const material.ValueKey('empty_quick_start'),
compact: compact,
surface: wb.surface,
borderColor:
wb.borderSubtle.withValues(alpha: 0.55),
foreground: cs.foreground,
primary: cs.primary,
),
: showRecent
? _RecentConnectionsSection(
key: const material.ValueKey(
'empty_recent_section',
),
connections: _recent,
onOpenConnection: widget.onOpenConnection,
compact: compact,
)
: _QuickStartSection(
key: const material.ValueKey(
'empty_quick_start',
),
compact: compact,
surface: wb.surface,
borderColor:
wb.borderSubtle.withValues(alpha: 0.55),
foreground: cs.foreground,
primary: cs.primary,
),
),
],
),
Expand Down
21 changes: 20 additions & 1 deletion lib/features/updater/update_dialog.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import 'dart:io';

import 'package:flutter/material.dart' as material;
import 'package:querya_desktop/core/layout/window_layout.dart';
import 'package:querya_desktop/core/motion/querya_motion.dart';
import 'package:querya_desktop/core/motion/querya_motion_context.dart';
import 'package:querya_desktop/core/theme/querya_theme_scope.dart';
import 'package:querya_desktop/core/updater/app_updater_service.dart';
import 'package:querya_desktop/core/updater/update_manifest.dart';
Expand Down Expand Up @@ -273,7 +275,24 @@ class _UpdateDialogContentState extends material.State<_UpdateDialogContent> {
horizontal: 24,
vertical: 8,
),
child: _body(context),
child: material.AnimatedSwitcher(
duration: context.motionDuration(QueryaMotion.standard),
switchInCurve: context.motionCurve(QueryaMotion.enter),
switchOutCurve: context.motionCurve(QueryaMotion.exit),
layoutBuilder: (currentChild, previousChildren) {
return material.Stack(
alignment: material.Alignment.topCenter,
children: [
...previousChildren,
if (currentChild != null) currentChild,
],
);
},
child: material.KeyedSubtree(
key: material.ValueKey(_phase),
child: _body(context),
),
),
),
),
material.Container(
Expand Down
Loading
Loading