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
101 changes: 59 additions & 42 deletions lib/features/main_screen/main_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1464,6 +1464,12 @@ class _MainContentSplitState extends State<_MainContentSplit>
);
}

final isAnimating = _widthSpring.isAnimating;
final remainingWidth =
math.max(0.0, constraints.maxWidth - currentW - currentHandleW);
final workspaceLaidOutWidth =
isAnimating ? constraints.maxWidth : remainingWidth;

return Row(
children: [
ClipRect(
Expand All @@ -1490,49 +1496,60 @@ class _MainContentSplitState extends State<_MainContentSplit>
),
),
Expanded(
child: material.RepaintBoundary(
child: ValueListenableBuilder<MainScreenWorkspaceState>(
valueListenable: widget.workspace,
builder: (context, ws, _) {
return WorkspacePanel(
activeConnection: ws.activeConnection,
selectedRedisDb: ws.activeRedisDb,
selectedMongoDb: ws.activeMongoDB,
selectedPostgresObject: ws.selectedPostgresObject,
postgresSqlTabRequestToken: ws.postgresSqlTabRequestToken,
postgresSqlEditorContext: ws.postgresSqlEditorContext,
postgresSqlEditorContextToken:
ws.postgresSqlEditorContextToken,
selectedMysqlObject: ws.selectedMysqlObject,
mysqlSqlTabRequestToken: ws.mysqlSqlTabRequestToken,
selectedSqliteObject: ws.selectedSqliteObject,
sqliteSqlTabRequestToken: ws.sqliteSqlTabRequestToken,
selectedExtensionObject: ws.selectedExtensionObject,
lastSelectedPostgresObject: ws.lastSelectedPostgresObject,
lastSelectedMysqlObject: ws.lastSelectedMysqlObject,
lastSelectedSqliteObject: ws.lastSelectedSqliteObject,
lastSelectedExtensionObject:
ws.lastSelectedExtensionObject,
lastSelectedMongoDb: ws.lastSelectedMongoDb,
lastSelectedRedisDb: ws.lastSelectedRedisDb,
onNavigateHome: () {
widget.workspace.value =
widget.workspace.value.unselectActiveObject();
},
onRestoreLastSelectedObject: () {
unawaited(_restoreLastSelectedObject());
key: const ValueKey('main_content_workspace_expanded'),
child: ClipRect(
child: OverflowBox(
alignment: Alignment.topLeft,
minWidth: workspaceLaidOutWidth,
maxWidth: workspaceLaidOutWidth,
child: material.RepaintBoundary(
child: ValueListenableBuilder<MainScreenWorkspaceState>(
valueListenable: widget.workspace,
builder: (context, ws, _) {
return WorkspacePanel(
activeConnection: ws.activeConnection,
selectedRedisDb: ws.activeRedisDb,
selectedMongoDb: ws.activeMongoDB,
selectedPostgresObject: ws.selectedPostgresObject,
postgresSqlTabRequestToken:
ws.postgresSqlTabRequestToken,
postgresSqlEditorContext:
ws.postgresSqlEditorContext,
postgresSqlEditorContextToken:
ws.postgresSqlEditorContextToken,
selectedMysqlObject: ws.selectedMysqlObject,
mysqlSqlTabRequestToken: ws.mysqlSqlTabRequestToken,
selectedSqliteObject: ws.selectedSqliteObject,
sqliteSqlTabRequestToken: ws.sqliteSqlTabRequestToken,
selectedExtensionObject: ws.selectedExtensionObject,
lastSelectedPostgresObject:
ws.lastSelectedPostgresObject,
lastSelectedMysqlObject: ws.lastSelectedMysqlObject,
lastSelectedSqliteObject: ws.lastSelectedSqliteObject,
lastSelectedExtensionObject:
ws.lastSelectedExtensionObject,
lastSelectedMongoDb: ws.lastSelectedMongoDb,
lastSelectedRedisDb: ws.lastSelectedRedisDb,
onNavigateHome: () {
widget.workspace.value =
widget.workspace.value.unselectActiveObject();
},
onRestoreLastSelectedObject: () {
unawaited(_restoreLastSelectedObject());
},
isReadOnly: ws.isReadOnly,
onRequestNewConnection: widget.onRequestNewConnection,
onRequestNewConnectionFromUrl:
widget.onRequestNewConnectionFromUrl,
onRequestOpenSqlite: widget.onRequestOpenSqlite,
onRequestLaunchDemo: widget.onRequestLaunchDemo,
onRequestOpenTour: widget.onRequestOpenTour,
onOpenConnection: widget.onOpenConnection,
initialMongoCollection: widget.initialMongoCollection,
);
},
isReadOnly: ws.isReadOnly,
onRequestNewConnection: widget.onRequestNewConnection,
onRequestNewConnectionFromUrl:
widget.onRequestNewConnectionFromUrl,
onRequestOpenSqlite: widget.onRequestOpenSqlite,
onRequestLaunchDemo: widget.onRequestLaunchDemo,
onRequestOpenTour: widget.onRequestOpenTour,
onOpenConnection: widget.onOpenConnection,
initialMongoCollection: widget.initialMongoCollection,
);
},
),
),
),
),
),
Expand Down
76 changes: 76 additions & 0 deletions test/features/main_screen/sidebar_toggle_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import 'package:querya_desktop/core/storage/local_db.dart';
import 'package:querya_desktop/features/connections/connections_panel.dart';
import 'package:querya_desktop/features/main_screen/main_screen.dart';
import 'package:querya_desktop/features/main_screen/querya_window_title_bar.dart';
import 'package:querya_desktop/features/main_screen/workspace_panel.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart';

import '../../support/querya_theme_test_shell.dart';
Expand Down Expand Up @@ -250,6 +251,81 @@ void main() {
findsNothing,
);
});

testWidgets(
'isolates workspace layout constraints during sidebar spring animation (#984)',
(tester) async {
await tester.pumpWidget(
queryaThemeTestShell(
child: const material.SizedBox(
width: 1200,
height: 800,
child: MainScreen(),
),
),
);
await tester.pumpAndSettle();

final RenderBox mainScreenBox =
tester.renderObject<RenderBox>(find.byType(MainScreen));
final totalWidth = mainScreenBox.size.width;
final expectedExpandedWidth = totalWidth - 260.0 - 6.0;

// Initially expanded: width is totalWidth - 260 - 6.
final initialWorkspaceBox =
tester.renderObject<RenderBox>(find.byType(WorkspacePanel));
expect(
initialWorkspaceBox.constraints.maxWidth,
closeTo(expectedExpandedWidth, 0.5),
);

final toggleButton =
find.byKey(const Key('title_bar_toggle_sidebar_button'));
expect(toggleButton, findsOneWidget);

// Trigger collapse toggle.
await tester.tap(toggleButton);
await tester.pump();

// Intermediate animation frames while collapsing: workspace constraints
// must remain held at constraints.maxWidth, skipping per-frame re-layouts.
for (var i = 0; i < 8; i++) {
await tester.pump(const Duration(milliseconds: 16));
final intermediateBox =
tester.renderObject<RenderBox>(find.byType(WorkspacePanel));
expect(intermediateBox.constraints.maxWidth, totalWidth);
}

await tester.pumpAndSettle();

// Settled collapsed: full width.
final settledCollapsedBox =
tester.renderObject<RenderBox>(find.byType(WorkspacePanel));
expect(settledCollapsedBox.constraints.maxWidth, totalWidth);

// Trigger expand toggle.
await tester.tap(toggleButton);
await tester.pump();

// Intermediate animation frames while expanding: workspace constraints
// must remain held stable at constraints.maxWidth instead of shrinking every frame.
for (var i = 0; i < 8; i++) {
await tester.pump(const Duration(milliseconds: 16));
final intermediateBox =
tester.renderObject<RenderBox>(find.byType(WorkspacePanel));
expect(intermediateBox.constraints.maxWidth, totalWidth);
}

await tester.pumpAndSettle();

// Settled expanded: exactly expectedExpandedWidth.
final settledExpandedBox =
tester.renderObject<RenderBox>(find.byType(WorkspacePanel));
expect(
settledExpandedBox.constraints.maxWidth,
closeTo(expectedExpandedWidth, 0.5),
);
});
});
}

Expand Down
Loading