From 700c44084fd4e2eb54eb70378e1958490a74cb74 Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Tue, 28 Jul 2026 16:18:39 +0300 Subject: [PATCH] =?UTF-8?q?ui(motion):=20morph=20connection=20A=E2=86=92B?= =?UTF-8?q?=20switches=20in=20WorkspacePanel=20(#494)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wrap the connected slot in QueryaFadeSlide keyed by connection id so switching connections cross-fades without disturbing empty↔connected or home↔object morphs. --- lib/features/main_screen/workspace_panel.dart | 15 ++++++- .../workspace_panel_layout_test.dart | 44 +++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/lib/features/main_screen/workspace_panel.dart b/lib/features/main_screen/workspace_panel.dart index bf9f3e9..c33bd71 100644 --- a/lib/features/main_screen/workspace_panel.dart +++ b/lib/features/main_screen/workspace_panel.dart @@ -133,6 +133,9 @@ class _WorkspacePanelState extends State { /// Keeps the last connected workspace mounted so empty↔active can cross-fade. material.Widget? _cachedActiveBody; + /// Connection id for the cached active body (stable FadeSlide key on empty). + int? _lastConnectedId; + @override Widget build(BuildContext context) { final theme = Theme.of(context); @@ -152,17 +155,27 @@ class _WorkspacePanelState extends State { if (activeConn == null) { activeBody = _cachedActiveBody ?? const material.SizedBox.expand(); } else { + _lastConnectedId = activeConn.id; activeBody = _buildActiveConnectionBody(theme, activeConn); _cachedActiveBody = activeBody; } + // Connection A→B: keyed FadeSlide inside the connected slot (#494). + // Keep last id when deselected so empty↔connected SwitchingBody is undisturbed. + final connKey = activeConn?.id ?? _lastConnectedId ?? 0; + return material.Container( color: theme.colorScheme.background, child: QueryaSwitchingBody( index: activeConn == null ? 0 : 1, children: [ empty, - material.SizedBox.expand(child: activeBody), + QueryaFadeSlide( + child: material.SizedBox.expand( + key: ValueKey('ws_conn_$connKey'), + child: activeBody, + ), + ), ], ), ); diff --git a/test/features/main_screen/workspace_panel_layout_test.dart b/test/features/main_screen/workspace_panel_layout_test.dart index 3811132..79f795c 100644 --- a/test/features/main_screen/workspace_panel_layout_test.dart +++ b/test/features/main_screen/workspace_panel_layout_test.dart @@ -211,5 +211,49 @@ void main() { expect(find.byType(RedisView), findsOneWidget); expect(find.byType(QueryaSwitchingBody), findsNWidgets(2)); }); + + testWidgets('connection A→B morph uses outer QueryaFadeSlide', + (tester) async { + const redisB = ConnectionRow( + id: 43, + type: 'redis', + name: 'redis-b', + host: '127.0.0.1', + port: 6380, + createdAt: '0', + ); + + await pumpWidgetWithSurfaceSize( + tester, + const material.Size(800, 600), + queryaThemeTestShell( + child: const material.SizedBox.expand( + child: WorkspacePanel(activeConnection: redisConnection), + ), + ), + ); + await tester.pump(); + + expect(find.byKey(const material.ValueKey('ws_conn_42')), findsOneWidget); + expect(find.byType(RedisView), findsOneWidget); + + await pumpWidgetWithSurfaceSize( + tester, + const material.Size(800, 600), + queryaThemeTestShell( + child: const material.SizedBox.expand( + child: WorkspacePanel(activeConnection: redisB), + ), + ), + ); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 50)); + + expect(find.byKey(const material.ValueKey('ws_conn_43')), findsOneWidget); + // Outer empty↔connected FadeSlide + home↔object FadeSlide. + expect(find.byType(QueryaFadeSlide), findsWidgets); + expect(find.byType(RedisView), findsOneWidget); + expect(find.byType(QueryaSwitchingBody), findsNWidgets(2)); + }); }); }