- Give the floating bar and search satellite the app's one shadow
(nooDialogShadow) plus a smaller, tighter height on both platforms.
- Let every tab's scrollable list compute its own bottom clearance
(bottomBarClearance) instead of a flat 100px, so the floating bar no
longer overlaps the last row/card.
- Square the Android upload FAB off to a rounded-square radius when the
bottom bar is attached, keeping it fully round only when floating.
- Let a swipe on a file row trigger its action directly once dragged
past 1.8x the reveal width, instead of requiring a second tap.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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>
- Every tab's RefreshIndicator+CustomScrollView was missing an explicit
AlwaysScrollableScrollPhysics, so pull-to-refresh silently couldn't be
triggered once the list was short enough to fit the viewport (empty,
or a single item) - the platform default physics only allows the
overscroll drag RefreshIndicator listens for once content overflows.
- StickyHeaderDelegate (Files'/Photos'/Favorites' pinned controls row)
painted its background from Theme.of(context).colorScheme.surface -
Flutter's own Material 3 scheme, reseeded by the user's accent color
choice - instead of context.nooColors.bg like the rest of the screen,
showing as a stray colored cast behind the row.
- NooSegmentedControl gains onSurface: true wherever one sits inside a
sheet (Files'/Photos' sort and type-filter controls) - it defaulted to
the same surface fill as the sheet itself, so the control had no
visible boundary showing it as a grouped unit.
- Shares tab: removed the per-row "..." overflow menu on every scope
(With you/By you/Links); tapping a row now opens the full Share sheet
for that item instead, fetched via the new
FilesController.fetchItemAtPath (a share only carries enough metadata
for its own row, not what the sheet's header needs).
- fetchShares() now reuses _shareFromJson instead of its own thinner
inline parser, so url/token/permissions/expireDate are actually
populated - a public-link share could never show "Copy link" before.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
NooSelectionBar's fixed inline slots (3 mobile / 4 desktop, the rest
behind "More") previously just took the first N actions in whatever
order each screen happened to build them in. Give bulk actions a
stable SelectionActionKind identity (independent of their dynamic
icon/label, e.g. favorite vs. unfavorite) so Settings can offer a
drag-to-reorder list - mirroring the existing Tabs section, minus the
pin/hide concept, which doesn't apply here - and each screen applies
that saved priority order via orderSelectionActions before handing
its action list to the bar.
The multi-select action bar (Files/Photos/Favorites) hid bulk actions
behind a horizontally-scrolling row with no visible cue that there was
more to find, once there were more than a handful (up to 9: favorite,
share, download, delete, copy, move, rename, sync, details).
Designed the replacement on the linked design canvas first (Selection
action bar boards), then implemented it: NooSelectionBar now shows a
fixed number of actions inline (3 on mobile, 4 on desktop) with the rest
behind a trailing "More" button that opens the same overflow sheet a
file row's own "..." menu already uses. Removed the now-dead
scroll-hint plumbing from the three screens that render it.
Replaces the Material 3 screens with the Noo design system: warm
neutrals, one violet accent, pill controls, Schibsted Grotesk/
Instrument Sans, Lucide icons, no gradients/shadows.
- New lib/widgets/noo/ component kit (core, lists, files, media, nav,
overlays) plus lib/theme/design_tokens.dart for the color/type/space/
radius/motion tokens.
- Rebuilt the app shell (top/bottom bars, drawer, desktop sidebar and
toolbar), every tab (Files/Offline, Photos, Favorites, Recent,
Activity, Trash, Shares), Settings, the lock and login screens, the
media viewer, search, the details/share sheets, and the share-upload
and move/copy destination pickers.
- Added a design canvas (linked from DESIGN_SYSTEM.md) covering the
screens the spec didn't already describe, with matching Android and
iOS chrome; wrote up the approved recipes into DESIGN_SYSTEM.md §4.
- Removed now-dead legacy widgets (media_grid_tile, swipeable_item,
sync_status_badge, selectable_thumbnail) and updated
architecture.md/styling.md/standards.md to describe the new
structure and component/testing conventions.
- Added widget tests for the noo/ component kit.
This is a UI-only rework: no provider/model/service behavior changed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Consolidates duplicated GET/PUT/notification-channel logic across
DownloadService/ShareUploadService/SyncEngine into shared Kotlin helpers,
gives upload/download real batch queueing instead of dropping a second
concurrent batch, and dedupes repeated Dart channel-argument boilerplate.
Replaces the 2300+ line ServerProvider god object with ten focused
ChangeNotifiers (SessionController, SettingsController, FilesController,
PhotosController, FavoritesController, TrashController, SharesController,
RecentController, SyncStatusController, PickController) plus ItemOperations,
a plain coordinator for cross-domain item mutations - fixing the coupling
where device-sync status, per-tab data, and global UI prefs all lived in
one object. Updates every view/widget call site accordingly and refreshes
the architecture/server/standards/styling docs to match.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- WebDAV MOVE/COPY-backed copy/move for files and folders, with a
destination picker (independent nav state so it doesn't disturb the
Files tab's browsing position) and a conflict-resolution sheet
(overwrite all / keep both / decide per item).
- Downloads now use the same foreground-service + notification approach
as uploads, surviving app closure.
- Favorites is now its own tab (account-wide, via a WebDAV SEARCH query)
instead of a Files-tab filter toggle, which couldn't represent
favorited items outside the currently browsed folder correctly.
- Share sheet restyled to match the app's design language, plus a
"share file directly" action via the OS share sheet; details sheet
tab icons resized to match the bottom nav.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>