The list endpoints now return a total count alongside the page so the
front-end can show how many pages exist and offer a last-page jump.
Backend: add count(is_new) and count_devices(...) (sharing a WHERE-builder
with list_devices so page and count can't drift), wrap both list responses
in {items, total_count} structs, and register them with utoipa.
Front-end: parse the wrapper shape (dropping the fetch-one-extra trick),
add a Last-page button and a responsive "Page X of Y" / "X / Y" label to
the shared PaginationBar, and track the total in both lists. Notifications
re-sync the count on every fetch and decrement it locally on mark-read/
unread removals so the count stays accurate without a re-fetch.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the notifications list's wholesale redraw with a SliverAnimatedList
driven by a GlobalKey, keeping `_items` in lockstep with the animated state.
- Background refreshes (poll, pull-to-refresh, resume, route pop, mark-all)
reconcile against the fetched page: departed rows slide out, newly fetched
rows slide in at the top with a theme-coloured arrival highlight, and
surviving rows stay put (with in-place read-state recolouring under "All").
- Filter/page changes and the initial load reset the list (fresh key) so the
new dataset appears instantly without per-row animation.
- Read/unread removals are owned by the list: buttons play a slide/fade exit,
while swipes let Dismissible animate and then reconcile, avoiding double
animation and the disposed-widget race.
Add the arrival highlight overlay to NotificationCard and cover the new
behaviour with widget tests (swipe-out, flash-in, external removal, in-place
"All" mark, filter reset).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
On phone-width layouts (< Breakpoints.medium) the notification and device
lists now request fewer items per page so the list and its pagination bar
fit on screen together on common current phones. Notifications use 4 items
and devices 6 on phones; wider layouts keep 5 and 10 respectively. The
initial fetch is deferred to didChangeDependencies so the page size can read
the screen width from MediaQuery.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The devices list already supported pull-to-refresh; mirror that on the
notifications list by wrapping its CustomScrollView in a RefreshIndicator
with AlwaysScrollableScrollPhysics. Keep the existing list visible during
a refresh (_isLoading = _items.isEmpty) instead of flashing the skeleton,
matching the devices list behaviour.
Add widget tests covering pull-to-refresh for both lists.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The notifications and devices lists kept their scroll offset when paging,
so a new page would open partway down. Attach a ScrollController to each
CustomScrollView and animate back to the top whenever the page changes
via the pagination bar.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
SliverList recycles State objects by index, so when an expanded
notification was marked as read and dropped from the filtered list, the
next notification inherited the expanded state. Give each card a
ValueKey(id) so its expand/collapse state is matched by notification,
not by list position.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>