From fce7b59218a5bc789aedfde40e766bbdae693668 Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Mon, 15 Jun 2026 13:05:09 +0300 Subject: [PATCH] refactor(motion): adopt QueryaMotion tokens across UI widgets Replace magic animation durations and easeOut curves in dialogs, dropdowns, theme picker, workspace panel, connection tree, and workspace home cards with context.motionDuration/motionCurve. Closes #171 --- lib/features/connections/connections_panel.dart | 2 ++ .../connections/connections_panel_mongo.dart | 3 ++- .../connections/connections_panel_mysql.dart | 6 ++++-- .../connections/connections_panel_pg_tree.dart | 15 ++++++++++----- .../connections_panel_postgres_connection.dart | 3 ++- .../connections/connections_panel_redis.dart | 3 ++- .../connections/connections_panel_sidebar.dart | 3 ++- .../connections/new_connection_dialog.dart | 6 ++++-- lib/features/main_screen/workspace_panel.dart | 12 +++++++----- lib/features/mysql/mysql_workspace_home.dart | 5 ++++- .../postgresql/postgres_workspace_home.dart | 5 ++++- lib/features/settings/theme_picker_button.dart | 14 ++++++-------- lib/shared/widgets/app_dialog.dart | 12 +++++++----- lib/shared/widgets/querya_dropdown.dart | 14 ++++++-------- lib/shared/widgets/querya_dropdown_tokens.dart | 2 -- 15 files changed, 62 insertions(+), 43 deletions(-) diff --git a/lib/features/connections/connections_panel.dart b/lib/features/connections/connections_panel.dart index 5cc49806..0e8d01d9 100644 --- a/lib/features/connections/connections_panel.dart +++ b/lib/features/connections/connections_panel.dart @@ -8,6 +8,8 @@ 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/core/motion/querya_motion.dart'; +import 'package:querya_desktop/core/motion/querya_motion_context.dart'; import 'package:querya_desktop/features/connections/connection_creation_flow.dart'; import 'package:querya_desktop/shared/widgets/widgets.dart'; diff --git a/lib/features/connections/connections_panel_mongo.dart b/lib/features/connections/connections_panel_mongo.dart index 2e5a4228..c512fe2c 100644 --- a/lib/features/connections/connections_panel_mongo.dart +++ b/lib/features/connections/connections_panel_mongo.dart @@ -169,7 +169,8 @@ class _MongoConnectionTileState extends State<_MongoConnectionTile> { padding: const material.EdgeInsets.all(2), child: material.AnimatedRotation( turns: _expanded ? 0.25 : 0, - duration: const Duration(milliseconds: 100), + duration: context.motionDuration(QueryaMotion.fast), + curve: context.motionCurve(QueryaMotion.standardCurve), child: material.Icon( material.Icons.chevron_right_rounded, size: 16, diff --git a/lib/features/connections/connections_panel_mysql.dart b/lib/features/connections/connections_panel_mysql.dart index f6045237..a56fc202 100644 --- a/lib/features/connections/connections_panel_mysql.dart +++ b/lib/features/connections/connections_panel_mysql.dart @@ -135,7 +135,8 @@ class _MysqlConnectionTileState extends State<_MysqlConnectionTile> { padding: const material.EdgeInsets.all(2), child: material.AnimatedRotation( turns: _expanded ? 0.25 : 0, - duration: const Duration(milliseconds: 100), + duration: context.motionDuration(QueryaMotion.fast), + curve: context.motionCurve(QueryaMotion.standardCurve), child: material.Icon( material.Icons.chevron_right_rounded, size: 16, @@ -387,7 +388,8 @@ class _MysqlDatabaseNodeState extends State<_MysqlDatabaseNode> { label: widget.databaseName, leading: material.AnimatedRotation( turns: _expanded ? 0.25 : 0, - duration: const Duration(milliseconds: 100), + duration: context.motionDuration(QueryaMotion.fast), + curve: context.motionCurve(QueryaMotion.standardCurve), child: material.Icon( material.Icons.chevron_right_rounded, size: 14, diff --git a/lib/features/connections/connections_panel_pg_tree.dart b/lib/features/connections/connections_panel_pg_tree.dart index d79fe824..6c0a7804 100644 --- a/lib/features/connections/connections_panel_pg_tree.dart +++ b/lib/features/connections/connections_panel_pg_tree.dart @@ -213,7 +213,8 @@ class _PgDatabasesNodeState extends State<_PgDatabasesNode> { label: 'Databases (${widget.databases.length})', leading: material.AnimatedRotation( turns: _expanded ? 0.25 : 0, - duration: const Duration(milliseconds: 100), + duration: context.motionDuration(QueryaMotion.fast), + curve: context.motionCurve(QueryaMotion.standardCurve), child: material.Icon( material.Icons.chevron_right_rounded, size: 14, @@ -328,7 +329,8 @@ class _PgDatabaseNodeState extends State<_PgDatabaseNode> { label: widget.databaseName, leading: material.AnimatedRotation( turns: _expanded ? 0.25 : 0, - duration: const Duration(milliseconds: 100), + duration: context.motionDuration(QueryaMotion.fast), + curve: context.motionCurve(QueryaMotion.standardCurve), child: material.Icon( material.Icons.chevron_right_rounded, size: 14, @@ -509,7 +511,8 @@ class _PgSchemasNodeState extends State<_PgSchemasNode> { label: 'Schemas (${widget.schemas.length})', leading: material.AnimatedRotation( turns: _expanded ? 0.25 : 0, - duration: const Duration(milliseconds: 100), + duration: context.motionDuration(QueryaMotion.fast), + curve: context.motionCurve(QueryaMotion.standardCurve), child: material.Icon( material.Icons.chevron_right_rounded, size: 14, @@ -649,7 +652,8 @@ class _PgSchemaNodeState extends State<_PgSchemaNode> { label: widget.schemaName, leading: material.AnimatedRotation( turns: _expanded ? 0.25 : 0, - duration: const Duration(milliseconds: 100), + duration: context.motionDuration(QueryaMotion.fast), + curve: context.motionCurve(QueryaMotion.standardCurve), child: material.Icon( material.Icons.chevron_right_rounded, size: 14, @@ -939,7 +943,8 @@ class _PgObjectGroupState extends State<_PgObjectGroup> { label: '${widget.label} (${widget.items.length})', leading: material.AnimatedRotation( turns: _expanded ? 0.25 : 0, - duration: const Duration(milliseconds: 100), + duration: context.motionDuration(QueryaMotion.fast), + curve: context.motionCurve(QueryaMotion.standardCurve), child: material.Icon( material.Icons.chevron_right_rounded, size: 13, diff --git a/lib/features/connections/connections_panel_postgres_connection.dart b/lib/features/connections/connections_panel_postgres_connection.dart index aace6240..a3914b84 100644 --- a/lib/features/connections/connections_panel_postgres_connection.dart +++ b/lib/features/connections/connections_panel_postgres_connection.dart @@ -131,7 +131,8 @@ class _PostgresConnectionTileState extends State<_PostgresConnectionTile> { padding: const material.EdgeInsets.all(2), child: material.AnimatedRotation( turns: _expanded ? 0.25 : 0, - duration: const Duration(milliseconds: 100), + duration: context.motionDuration(QueryaMotion.fast), + curve: context.motionCurve(QueryaMotion.standardCurve), child: material.Icon( material.Icons.chevron_right_rounded, size: 16, diff --git a/lib/features/connections/connections_panel_redis.dart b/lib/features/connections/connections_panel_redis.dart index a70ab123..7e3fda45 100644 --- a/lib/features/connections/connections_panel_redis.dart +++ b/lib/features/connections/connections_panel_redis.dart @@ -148,7 +148,8 @@ class _RedisConnectionTileState extends State<_RedisConnectionTile> { padding: const material.EdgeInsets.all(2), child: material.AnimatedRotation( turns: _expanded ? 0.25 : 0, - duration: const Duration(milliseconds: 100), + duration: context.motionDuration(QueryaMotion.fast), + curve: context.motionCurve(QueryaMotion.standardCurve), child: material.Icon( material.Icons.chevron_right_rounded, size: 16, diff --git a/lib/features/connections/connections_panel_sidebar.dart b/lib/features/connections/connections_panel_sidebar.dart index f1994158..c56c4537 100644 --- a/lib/features/connections/connections_panel_sidebar.dart +++ b/lib/features/connections/connections_panel_sidebar.dart @@ -245,7 +245,8 @@ class _FolderTileState extends State<_FolderTile> { children: [ material.AnimatedRotation( turns: _expanded ? 0.25 : 0, - duration: const Duration(milliseconds: 100), + duration: context.motionDuration(QueryaMotion.fast), + curve: context.motionCurve(QueryaMotion.standardCurve), child: material.Icon( material.Icons.chevron_right_rounded, size: 18, diff --git a/lib/features/connections/new_connection_dialog.dart b/lib/features/connections/new_connection_dialog.dart index 9cf93f7e..1397c669 100644 --- a/lib/features/connections/new_connection_dialog.dart +++ b/lib/features/connections/new_connection_dialog.dart @@ -2,6 +2,8 @@ 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/core/motion/querya_motion.dart'; +import 'package:querya_desktop/core/motion/querya_motion_context.dart'; import 'package:querya_desktop/shared/widgets/widgets.dart'; /// Database type for new connection. @@ -383,8 +385,8 @@ class _DbTypeCardState extends material.State<_DbTypeCard> { child: material.GestureDetector( onTap: widget.onTap, child: material.AnimatedContainer( - duration: const Duration(milliseconds: 120), - curve: material.Curves.easeOut, + duration: context.motionDuration(QueryaMotion.fast), + curve: context.motionCurve(QueryaMotion.enter), 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), diff --git a/lib/features/main_screen/workspace_panel.dart b/lib/features/main_screen/workspace_panel.dart index b61720cd..9af0f1cf 100644 --- a/lib/features/main_screen/workspace_panel.dart +++ b/lib/features/main_screen/workspace_panel.dart @@ -1,5 +1,7 @@ -import 'package:flutter/material.dart' as material show Alignment, Axis, Container, EdgeInsets, BoxDecoration, GestureDetector, Padding, BorderRadius, Center, Icon, Icons, MouseRegion, AnimatedContainer, AnimatedScale, Curves, SystemMouseCursors, SizedBox, SingleChildScrollView, Row, MainAxisSize; +import 'package:flutter/material.dart' as material show Alignment, Axis, Container, EdgeInsets, BoxDecoration, GestureDetector, Padding, BorderRadius, Center, Icon, Icons, MouseRegion, AnimatedContainer, AnimatedScale, SystemMouseCursors, SizedBox, SingleChildScrollView, Row, MainAxisSize; import 'package:querya_desktop/core/layout/vertical_split_pane.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/storage/local_db.dart'; import 'package:querya_desktop/shared/widgets/widgets.dart'; @@ -341,8 +343,8 @@ class _TabButtonState extends State<_TabButton> { child: material.GestureDetector( onTap: widget.onTap, child: material.AnimatedContainer( - duration: const Duration(milliseconds: 120), - curve: material.Curves.easeOut, + duration: context.motionDuration(QueryaMotion.fast), + curve: context.motionCurve(QueryaMotion.enter), padding: const material.EdgeInsets.symmetric(horizontal: 12, vertical: 8), decoration: material.BoxDecoration( color: bgColor, @@ -376,8 +378,8 @@ class _RunButtonState extends State<_RunButton> { cursor: material.SystemMouseCursors.click, child: material.AnimatedScale( scale: _hovered ? 1.03 : 1.0, - duration: const Duration(milliseconds: 100), - curve: material.Curves.easeOut, + duration: context.motionDuration(QueryaMotion.fast), + curve: context.motionCurve(QueryaMotion.enter), child: OutlineButton( onPressed: () {}, leading: const material.Icon(material.Icons.play_arrow, size: 18), diff --git a/lib/features/mysql/mysql_workspace_home.dart b/lib/features/mysql/mysql_workspace_home.dart index 94132a29..faa1ebe3 100644 --- a/lib/features/mysql/mysql_workspace_home.dart +++ b/lib/features/mysql/mysql_workspace_home.dart @@ -1,6 +1,8 @@ import 'dart:async' show unawaited; import 'package:flutter/material.dart' as material; +import 'package:querya_desktop/core/motion/querya_motion.dart'; +import 'package:querya_desktop/core/motion/querya_motion_context.dart'; import 'package:querya_desktop/core/storage/local_db.dart'; import 'package:querya_desktop/features/mysql/mysql_sql_workspace.dart'; import 'package:querya_desktop/features/mysql/mysql_stats_view.dart'; @@ -82,7 +84,8 @@ class _MysqlWorkspaceHomeState extends material.State { child: material.GestureDetector( onTap: () => unawaited(_selectTab(i)), child: material.AnimatedContainer( - duration: const Duration(milliseconds: 120), + duration: context.motionDuration(QueryaMotion.fast), + curve: context.motionCurve(QueryaMotion.enter), padding: const material.EdgeInsets.symmetric( horizontal: 12, vertical: 8, diff --git a/lib/features/postgresql/postgres_workspace_home.dart b/lib/features/postgresql/postgres_workspace_home.dart index 7a170bc8..eb62732e 100644 --- a/lib/features/postgresql/postgres_workspace_home.dart +++ b/lib/features/postgresql/postgres_workspace_home.dart @@ -1,6 +1,8 @@ import 'dart:async' show unawaited; import 'package:flutter/material.dart' as material; +import 'package:querya_desktop/core/motion/querya_motion.dart'; +import 'package:querya_desktop/core/motion/querya_motion_context.dart'; import 'package:querya_desktop/core/storage/local_db.dart'; import 'package:querya_desktop/features/postgresql/postgres_object_kind.dart'; import 'package:querya_desktop/features/postgresql/postgres_sql_workspace.dart'; @@ -123,7 +125,8 @@ class _PostgresWorkspaceHomeState extends material.State child: material.GestureDetector( onTap: () => _selectTab(i), child: material.AnimatedContainer( - duration: const Duration(milliseconds: 120), + duration: context.motionDuration(QueryaMotion.fast), + curve: context.motionCurve(QueryaMotion.enter), padding: const material.EdgeInsets.symmetric( horizontal: 12, vertical: 8, diff --git a/lib/features/settings/theme_picker_button.dart b/lib/features/settings/theme_picker_button.dart index 1e108cd6..5dfac989 100644 --- a/lib/features/settings/theme_picker_button.dart +++ b/lib/features/settings/theme_picker_button.dart @@ -2,6 +2,8 @@ import 'dart:async'; import 'package:flutter/material.dart' as material; import 'package:querya_desktop/core/layout/ui_scale.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.dart'; import 'package:querya_desktop/core/theme/theme_definition.dart'; import 'package:querya_desktop/features/settings/theme_preview_card.dart'; @@ -379,10 +381,8 @@ class _ThemePickerButtonState extends material.State { onEnter: _enabled ? (_) => setState(() => _triggerHovered = true) : null, onExit: _enabled ? (_) => setState(() => _triggerHovered = false) : null, child: material.AnimatedContainer( - duration: const Duration( - milliseconds: QueryaDropdownTokens.hoverAnimationMs, - ), - curve: material.Curves.easeOut, + duration: context.motionDuration(QueryaMotion.fast), + curve: context.motionCurve(QueryaMotion.enter), height: triggerHeight, padding: QueryaDropdownTokens.scaledTriggerPadding(context), decoration: material.BoxDecoration( @@ -490,10 +490,8 @@ class _ThemePickerRowState extends material.State<_ThemePickerRow> { onTap: widget.onSelected, borderRadius: material.BorderRadius.circular(radius), child: material.AnimatedContainer( - duration: const Duration( - milliseconds: QueryaDropdownTokens.hoverAnimationMs, - ), - curve: material.Curves.easeOut, + duration: context.motionDuration(QueryaMotion.fast), + curve: context.motionCurve(QueryaMotion.enter), constraints: material.BoxConstraints(minHeight: itemHeight), padding: material.EdgeInsets.symmetric( horizontal: diff --git a/lib/shared/widgets/app_dialog.dart b/lib/shared/widgets/app_dialog.dart index a5e42ac5..36ff20e3 100644 --- a/lib/shared/widgets/app_dialog.dart +++ b/lib/shared/widgets/app_dialog.dart @@ -2,6 +2,9 @@ import 'dart:ui'; import 'package:flutter/material.dart'; +import 'package:querya_desktop/core/motion/querya_motion.dart'; +import 'package:querya_desktop/core/motion/querya_motion_context.dart'; + /// Shows a modal dialog with a frosted, dimmed backdrop over the app. /// /// Use instead of [showDialog] so every overlay has consistent blur. @@ -15,7 +18,7 @@ Future showAppDialog({ barrierDismissible: false, barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel, barrierColor: Colors.transparent, - transitionDuration: const Duration(milliseconds: 200), + transitionDuration: context.motionDuration(QueryaMotion.standard), pageBuilder: (ctx, animation, secondaryAnimation) { return _BlurredDialogScaffold( barrierDismissible: barrierDismissible, @@ -44,12 +47,11 @@ class _BlurredDialogScaffold extends StatelessWidget { final Animation animation; final Widget child; - // Eased curve for dialog card fade-in / scale-up. - static final _curve = CurveTween(curve: Curves.easeOutCubic); - @override Widget build(BuildContext context) { - final curved = animation.drive(_curve); + final curved = animation.drive( + CurveTween(curve: context.motionCurve(QueryaMotion.enter)), + ); return Material( type: MaterialType.transparency, child: Stack( diff --git a/lib/shared/widgets/querya_dropdown.dart b/lib/shared/widgets/querya_dropdown.dart index cfcd5860..580640d4 100644 --- a/lib/shared/widgets/querya_dropdown.dart +++ b/lib/shared/widgets/querya_dropdown.dart @@ -1,6 +1,8 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart' as material; import 'package:querya_desktop/core/layout/ui_scale.dart'; +import 'package:querya_desktop/core/motion/querya_motion.dart'; +import 'package:querya_desktop/core/motion/querya_motion_context.dart'; import 'package:querya_desktop/shared/widgets/querya_dropdown_tokens.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; @@ -154,10 +156,8 @@ class _QueryaDropdownState extends material.State> { onEnter: widget.enabled ? (_) => setState(() => _triggerHovered = true) : null, onExit: widget.enabled ? (_) => setState(() => _triggerHovered = false) : null, child: material.AnimatedContainer( - duration: const Duration( - milliseconds: QueryaDropdownTokens.hoverAnimationMs, - ), - curve: material.Curves.easeOut, + duration: context.motionDuration(QueryaMotion.fast), + curve: context.motionCurve(QueryaMotion.enter), height: triggerHeight, padding: QueryaDropdownTokens.scaledTriggerPadding(context), decoration: material.BoxDecoration( @@ -350,10 +350,8 @@ class _QueryaDropdownMenuItemState extends material.State<_QueryaDropdownMenu ), onPressed: widget.enabled ? widget.onPick : null, child: material.AnimatedContainer( - duration: const Duration( - milliseconds: QueryaDropdownTokens.hoverAnimationMs, - ), - curve: material.Curves.easeOut, + duration: context.motionDuration(QueryaMotion.fast), + curve: context.motionCurve(QueryaMotion.enter), constraints: material.BoxConstraints(minHeight: itemHeight), padding: material.EdgeInsets.symmetric( horizontal: context.scaled(QueryaDropdownTokens.menuItemPadding.horizontal), diff --git a/lib/shared/widgets/querya_dropdown_tokens.dart b/lib/shared/widgets/querya_dropdown_tokens.dart index 367bbaaf..6a4a3721 100644 --- a/lib/shared/widgets/querya_dropdown_tokens.dart +++ b/lib/shared/widgets/querya_dropdown_tokens.dart @@ -40,8 +40,6 @@ abstract final class QueryaDropdownTokens { static const double selectedCheckSlotWidth = 18.0; - static const int hoverAnimationMs = 120; - static double scaledTriggerHeight(material.BuildContext context) => context.scaled(triggerHeight);