diff --git a/lib/features/workspace/result_grid_view.dart b/lib/features/workspace/result_grid_view.dart index 71e5172..22d62cc 100644 --- a/lib/features/workspace/result_grid_view.dart +++ b/lib/features/workspace/result_grid_view.dart @@ -696,6 +696,19 @@ class _VirtualResultGridState extends material.State { bool _userHasResized = false; double _scrollOffset = 0; + /// Cache for [distributeResultGridSpareWidth] (#982): sampling every + /// column's header/content width is O(columns x sampled rows) and only + /// needs to rerun when [_columnWidths] itself was recomputed (a new list, + /// so `identical` catches it) or the viewport width changed, not on every + /// `LayoutBuilder` layout pass. + List _distributedWidths = const []; + List? _distributedForColumnWidths; + double? _distributedForAvailableWidth; + + /// Counts calls to [distributeResultGridSpareWidth], i.e. cache misses. + @material.visibleForTesting + int columnWidthDistributionCount = 0; + int? _sortColumnIndex; ResultGridSortOrder? _sortOrder; int _sortVersion = 0; @@ -2098,14 +2111,23 @@ class _VirtualResultGridState extends material.State { if (!_userHasResized && tableWidth < availableWidth && _columnWidths.isNotEmpty) { - displayWidths = distributeResultGridSpareWidth( - columnWidths: _columnWidths, - columns: widget.columns, - rows: _baseRows, - availableWidth: availableWidth, - maxColumnWidth: - context.scaled(ResultGridMetrics.maxColumnWidth), - ); + if (identical(_distributedForColumnWidths, _columnWidths) && + _distributedForAvailableWidth == availableWidth) { + displayWidths = _distributedWidths; + } else { + displayWidths = distributeResultGridSpareWidth( + columnWidths: _columnWidths, + columns: widget.columns, + rows: _baseRows, + availableWidth: availableWidth, + maxColumnWidth: + context.scaled(ResultGridMetrics.maxColumnWidth), + ); + _distributedWidths = displayWidths; + _distributedForColumnWidths = _columnWidths; + _distributedForAvailableWidth = availableWidth; + columnWidthDistributionCount++; + } final distributedWidth = displayWidths.fold(0.0, (sum, w) => sum + w); tableWidth = math.max(distributedWidth, availableWidth); diff --git a/test/features/workspace/result_grid_column_width_cache_test.dart b/test/features/workspace/result_grid_column_width_cache_test.dart new file mode 100644 index 0000000..7fdd8fc --- /dev/null +++ b/test/features/workspace/result_grid_column_width_cache_test.dart @@ -0,0 +1,82 @@ +import 'package:flutter/material.dart' as material; +import 'package:flutter_test/flutter_test.dart'; +import 'package:querya_desktop/features/workspace/result_grid_view.dart'; + +import '../../support/querya_theme_test_shell.dart'; + +material.Widget _testShell({required material.Widget child}) { + return queryaThemeTestShell( + child: material.Scaffold( + body: child, + ), + ); +} + +void main() { + group('VirtualResultGrid column-width distribution cache (#982)', () { + testWidgets( + 'an unrelated setState (same width, same columns/rows) does not resample column widths again', + (tester) async { + await tester.pumpWidget( + _testShell( + child: const material.SizedBox( + width: 800, + height: 400, + child: VirtualResultGrid( + columns: ['id', 'name', 'role'], + rows: [ + ['1', 'Alice', 'Admin'], + ['2', 'Bob', 'User'], + ], + ), + ), + ), + ); + await tester.pumpAndSettle(); + + final dynamic state = tester.state(find.byType(VirtualResultGrid)); + final firstCount = state.columnWidthDistributionCount as int; + expect(firstCount, greaterThanOrEqualTo(1)); + + // Force a rebuild of the grid's own State without touching its widget + // (rows/columns/width all unchanged) — the same shape of rebuild a + // selection change or any other internal setState would cause. The + // O(columns x rows) sampling must not rerun for it. + state.setState(() {}); + await tester.pumpAndSettle(); + + expect(state.columnWidthDistributionCount, firstCount); + }); + + testWidgets('resizing the viewport does resample column widths', + (tester) async { + final key = material.GlobalKey(); + + material.Widget build(double width) => _testShell( + child: material.SizedBox( + key: key, + width: width, + height: 400, + child: const VirtualResultGrid( + columns: ['id', 'name', 'role'], + rows: [ + ['1', 'Alice', 'Admin'], + ['2', 'Bob', 'User'], + ], + ), + ), + ); + + await tester.pumpWidget(build(800)); + await tester.pumpAndSettle(); + + final dynamic state = tester.state(find.byType(VirtualResultGrid)); + final firstCount = state.columnWidthDistributionCount as int; + + await tester.pumpWidget(build(500)); + await tester.pumpAndSettle(); + + expect(state.columnWidthDistributionCount, greaterThan(firstCount)); + }); + }); +}