Files
noo/lib/widgets/app_tab_view_builder.dart
T
ayushyaandClaude Sonnet 5 5dea9038f7 Make the shell top bar hide/reveal with scroll, per tab
Each regular tab (Files/Photos/Favorites/Shares/Recent/Activity/Trash)
now plants its own AppTopBar as the first sliver in its own
CustomScrollView, via a new topBarSliver() helper (tab_state_slivers.dart)
wrapping the framework's SliverFloatingHeader, instead of one shared
instance living in Scaffold.appBar.

This gives each tab's top bar Material's native floating-app-bar
behavior - tied to that tab's own ScrollController - scrolling away as
the list scrolls down and reappearing the moment the drag reverses,
not only once scrolled all the way back to the top, which a single
fixed Scaffold.appBar (or the "put it in a SliverToBoxAdapter" escape
hatch NooTopBar's doc comment already mentioned) can't do. It sits
above each tab's own pinned in-content sticky header (the sort/filter
row, or the selection bar that replaces it), which keeps working
unchanged underneath it.

main.dart builds a separate, correctly-labelled AppTopBar per tab
(rather than one for "whichever tab is selected") and passes it through
buildAppTabView's new optional topBar parameter; it's null on desktop
(which keeps its own NooToolbar) and while picking, matching the old
Scaffold.appBar: pickRequest == null guard.

SliverFloatingHeader sizes itself from the bar's own natural layout
(like SliverToBoxAdapter) rather than a fixed extent declared up front,
so NooTopBar's own internal SafeArea keeps handling the status-bar inset
correctly with no extra height math needed on this side.

Adds a widget test (test/widgets/tabs/tab_state_slivers_test.dart)
driving a raw gesture mid-drag to verify the reveal-follows-finger
behavior, since a single tester.drag() only pumps after the gesture -
including release - has already finished, which doesn't exercise the
live per-frame reveal this change depends on.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-30 16:28:36 -04:00

58 lines
2.3 KiB
Dart

import 'package:flutter/material.dart';
import '../models/app_tab.dart';
import '../views/activity_view.dart';
import '../views/favorites_view.dart';
import '../views/files_view.dart';
import '../views/photos_view.dart';
import '../views/recent_view.dart';
import '../views/shares_view.dart';
import '../views/trash_view.dart';
/// Builds the view widget for a given [AppTab]. Shared by the main shell
/// (for visible tabs) and the "more tabs" dropdown (for launching a tab
/// that's currently hidden from the bottom nav bar).
///
/// [topBar], when given, is that tab's own [AppTopBar] instance (built by
/// the caller, which owns `NooLayout.navStyle`/pick-mode/search-in-bottom-
/// bar state) - each view plants it as its own first sliver (see
/// `topBarSliver` in `tabs/tab_state_slivers.dart`) so it scrolls away and
/// reappears independently, tied to that tab's own `ScrollController`
/// rather than living in the shared `Scaffold.appBar`. Null on desktop
/// (which shows `NooToolbar` instead) and while picking (no top bar at
/// all), matching `Scaffold.appBar`'s old `pickRequest == null` guard.
Widget buildAppTabView(
AppTab tab,
ScrollController controller, {
PreferredSizeWidget? topBar,
}) {
switch (tab) {
case AppTab.files:
// Keyed so the Files and Offline tabs (same widget type) never share
// State when the visible-tab list shifts positions in the IndexedStack.
return FilesView(
key: const ValueKey('files'),
scrollController: controller,
topBar: topBar,
);
case AppTab.photos:
return PhotosView(scrollController: controller, topBar: topBar);
case AppTab.favorites:
return FavoritesView(scrollController: controller, topBar: topBar);
case AppTab.activity:
return ActivityView(scrollController: controller, topBar: topBar);
case AppTab.trash:
return TrashView(scrollController: controller, topBar: topBar);
case AppTab.shares:
return SharesView(scrollController: controller, topBar: topBar);
case AppTab.recent:
return RecentView(scrollController: controller, topBar: topBar);
case AppTab.offline:
return FilesView(
key: const ValueKey('offline'),
scrollController: controller,
offline: true,
topBar: topBar,
);
}
}