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.
Long-content bottom sheets (Details' Versions/Activity tabs with many
rows) opened locked to nearly full screen height with no room left to
adjust. showNooSheet now measures its content once against the
available height: anything that fits comfortably still sizes to
content as before, but content tall enough to need scrolling opens at
a native-style draggable peek instead, which the user can drag up to
reveal more of (and drag back down to dismiss).
The media viewer's stage was pinned to black regardless of theme,
which read as jarringly out of place in light mode - it now follows
the app's own `bg`. The floating top/bottom bars stay a fixed dark
panel either way (they need to read over arbitrary photo/video
brightness, not over the stage), but both had their SafeArea nested
the wrong way around: it wrapped the whole panel instead of just its
content, so the panel's background stopped short of the actual screen
edge - most visibly the bottom bar ending above the gesture area
instead of extending behind it. The background now wraps the SafeArea
instead.
Every sheet and dialog barrier goes solid black instead of dimming the
screen behind it whenever dynamic color ("Match wallpaper") is on -
Material 3's ColorScheme.scrim role is fully opaque black by spec,
meant to be used at reduced opacity by whoever paints it, but
NooColors.fromDynamicScheme mapped it straight into barrierColor.
Use the fixed palette's already-translucent scrim instead, matching
how success/warning/info already fall back to it since they have no
dynamic equivalent either.
Also fix the Details sheet's Info/Versions/Activity segmented control
refetching a tab's data every time it's selected: switching on the
active tab picked which single widget to build, so Flutter tore down
and recreated whichever tab wasn't showing on every switch, discarding
its fetched data. Keep all three mounted in an IndexedStack instead
(the same pattern the app's own main tab bar already uses for this
exact reason), so each tab's state - and its one-shot fetch - survives
switching back and forth.
NooColors (the color source for nearly the whole redesigned UI) only
had two states: the fixed violet palette, or a Material You dynamic
scheme when "Match wallpaper" was on. The 5-swatch custom accent
picker still wrote to SettingsController.seedColor, but nothing read
it back into NooColors - it only reached the underlying Material
ColorScheme, which the Noo-styled UI doesn't use, so every custom
swatch looked identically purple.
Add NooColors.fromSeed, which retints just the accent/accentText/
accentSoft roles from a chosen seed color via ColorScheme.fromSeed
(bg/surface/fg etc. stay the fixed warm-neutral palette, unlike
dynamic color which reshapes everything to match the wallpaper), and
wire it into AppTheme.light/dark. Add a "Default" (Noo violet) swatch
to the picker, and move SettingsController's initial seed color to
that same violet so a fresh install - or any device without dynamic
color - keeps today's look instead of picking up the leftover
Nextcloud-blue default from before seed colors did anything visible.
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.
- Bottom bar: labels now sit below each icon instead of beside it, and
the active-tab pill is one shared indicator that slides between tabs
instead of popping in/out per item.
- Upload FAB stays mounted across every tab and collapses to an
icon-only circle off Files/Photos, instead of the Scaffold popping it
fully in/out on every tab switch.
- Tapping the top-bar avatar now opens the accounts sheet instead of
jumping straight to Settings.
- Settings tab reordering only starts a drag from the grip icon now -
previously the whole row claimed vertical drags, so scrolling the
section could trigger an accidental reorder.
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>
- Offline tab is now FilesView(offline: true) over a FolderBrowser interface
(FilesController / OfflineController) sharing controls, tiles, thumbnails
- Synced files follow the Files Cache rule (default: refresh every 15 min):
per-account WorkManager jobs, in-app timer, resume and pull triggers;
root-etag shortcut skips full walks when nothing changed
- Single sync at a time (shared lock); logging out stops that account's sync
- Sync safety: PROPFIND failures skip the path instead of deleting local files
- Mirror empty folders and remove deleted ones; drop synced paths deleted on
the server; missing local files are re-downloaded
- Notifications: silent per-account sync notifications, "Background sync
notifications" setting, audible upload/download completion
- Files refresh in place (no spinner flash); retry after network returns
- Fix sync badges not updating (shared native status stream), "Sync off" on
launch (eager providers), Files Cache section moved under Device Sync
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>
Major additions since the initial rebuild:
- New tabs: Trash, Shares, Recent, configurable tab visibility/order/default
- Nextcloud sharing: search users/teams, internal link, email shares,
public links, "others with access" - in its own Share bottom sheet
- Details bottom sheet (Info/Versions/Activity) with a persistent peek
panel in the media viewer, reachable from selection, swipe, and the
media viewer
- Files/Photos: tap-and-hold multi-select with a sticky selection toolbar,
swipe actions (favorite/delete/share), sticky sort/filter controls,
list/grid views, breadcrumb chips, whole-account Photos view
- Media viewer: pinch/double-tap zoom on images, predictive-back support,
fixed PDF zoom-out floor
- Settings: Material You theming incl. AMOLED black, configurable bottom
bar tabs, swipe actions, media seek bar style, and folder-listing cache
policy (never/periodic/manual) to cut down on refetching
- Login flow moved to an in-app Chrome Custom Tab with retry-tolerant
polling
- Replaced ad hoc widgets with native Material 3 equivalents (toggle
IconButtons, SliverAppBar selection toolbar, SearchBar, drag handles)
- Rebrand: app renamed to Noo throughout, package id -> dev.ayushya.noo,
new adaptive launcher icon
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>