Fix sheet peek for async content, and rework several screens' details

showNooSheet's peek detection previously measured content once, right
after the first frame - Details' Versions/Activity tabs fetch their
data asynchronously, so that one-shot measurement only ever saw the
initial loading-spinner height, never the eventual (often long) list,
and the sheet still snapped to full height once data arrived. Replaced
it with a ScrollMetricsNotification-based check that keeps content
capped at the peek height from frame one and upgrades to a draggable
sheet whenever it actually overflows that cap, however and whenever
that happens - fixed for good this time, with a regression test that
mirrors the async-load shape.

Also, in the same pass:
- Upload/Move/Copy's bottom bar meta line now always reads as a count
  ("Uploading 3 items"), never a filename for the single-item case,
  and is centered rather than left-aligned - matches the approved
  design and removes a bespoke marquee/chip implementation that only
  Upload had.
- Upload/Move/Copy now show the same FilesControlsRow (sort, filter,
  List/Grid) Files itself uses, including a folder-grid rendering
  path for when List/Grid is set to grid.
- Files' filter chip reads "Filters" instead of "All types" when idle.
- Trash's overflow button - whose only action is "Delete forever" -
  is now a direct delete icon (mobile) / "Delete" danger pill
  (desktop) instead of a generic "..." menu that hid a single action
  behind an extra tap.
- The media viewer's floating chrome (top bar, bottom action bar,
  video transport row) now follows the app's light/dark theme instead
  of a fixed dark panel - a dark panel read as out of place over a
  light-theme stage. Panel tint and icon/text colors come from the
  theme's own surface/fg1 tokens (at a higher blur opacity to stay
  legible over arbitrary photo/video brightness), and the edge scrim
  gradients fade toward the panel's own tint instead of a fixed black.
  Mirrored the same change into the design canvas's Main.dc.html/
  MainAndroid.dc.html mockups (now with a theme prop) so they match.
This commit is contained in:
2026-09-28 19:26:18 -04:00
parent c0f702b00e
commit 0abea46b1c
12 changed files with 441 additions and 207 deletions
+69
View File
@@ -18,6 +18,43 @@ Widget _launcher(void Function(BuildContext) open) => Builder(
TextButton(onPressed: () => open(context), child: const Text('open')),
);
/// A short placeholder that swaps in a long list one microtask later -
/// standing in for a sheet tab that fetches its content on first build
/// (`_requested`, as `DetailsVersionsTab`/`DetailsActivityTab` do) rather
/// than having it all synchronously on the very first frame.
class _AsyncGrowingContent extends StatefulWidget {
const _AsyncGrowingContent();
@override
State<_AsyncGrowingContent> createState() => _AsyncGrowingContentState();
}
class _AsyncGrowingContentState extends State<_AsyncGrowingContent> {
bool _loaded = false;
@override
void initState() {
super.initState();
Future.delayed(Duration.zero, () {
if (mounted) setState(() => _loaded = true);
});
}
@override
Widget build(BuildContext context) {
if (!_loaded) {
return const SizedBox(height: 40, child: Center(child: Text('Loading')));
}
return Column(
mainAxisSize: MainAxisSize.min,
children: [
for (var i = 0; i < 30; i++)
SizedBox(height: 40, child: Text('Row $i')),
],
);
}
}
Finder _dialogCard() => find.byWidgetPredicate(
(w) =>
w is Container &&
@@ -136,6 +173,38 @@ void main() {
expect(sheetHeight, lessThan(500));
expect(sheetHeight, greaterThan(150));
});
testNooWidgets(
'content that grows after an async load still switches to peek',
(tester, theme, c) async {
// Mirrors DetailsVersionsTab/DetailsActivityTab: a small placeholder
// first, then a long list once a fetch "completes" - the sheet has
// to catch the overflow whenever it happens, not just on its first
// frame (that one-shot approach is what shipped the original bug).
await pumpNoo(
tester,
_launcher(
(ctx) => showNooSheet<void>(
ctx,
children: [const _AsyncGrowingContent()],
),
),
theme: theme,
surfaceSize: const Size(400, 700),
);
await tester.tap(find.text('open'));
await tester.pump();
expect(find.byType(DraggableScrollableSheet), findsNothing);
await tester.pumpAndSettle();
expect(find.byType(DraggableScrollableSheet), findsOneWidget);
final sheetHeight = tester
.getSize(find.byType(DraggableScrollableSheet))
.height;
expect(sheetHeight, lessThan(500));
expect(sheetHeight, greaterThan(150));
},
);
});
group('NooDialog', () {