Extract ScannerStatusCard<T> and PaginationBar so the ARP/mDNS cards and
the two paginated lists share one implementation each. Also fold the
four UISnackbars methods over a severity enum and fix Notification.toJson
serializing a method tear-off instead of the enum name.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Remove dead _pageSize from BackendAPI and centralise the
"fetch perPage+1, detect hasNextPage, trim" logic into
listDevices and listNotifications. Both methods now accept
page/perPage and return ({items, hasNextPage}) records,
eliminating the duplicated boilerplate at each call site.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
PolledValue gains pause()/resume() methods. ScannersStatusCard and
NotificationsList now implement RouteAware (didPushNext/didPopNext)
and WidgetsBindingObserver to stop API calls and tick timers when
the widget is not visible, resuming with an immediate fetch on return.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Three intertwined fixes so that marking a notification as read from the
trailing menu, swipe-dismiss, and 'Mark all as read' all consistently
update the UI and surface backend failures.
- Guard the mark-read, mark-new, and mark-all calls with try/catch; on
failure show a snackbar with the mapped error and leave the list
untouched.
- Use the State's own `mounted` and `context` after the await instead of
the per-card BuildContext, so the SliverList rebuild reliably runs
even when the originating NotificationCard's element has been swapped
out by the reconciliation. _setRead and _markAllAsRead no longer take
a BuildContext.
- Replace `Dismissible(key: UniqueKey())` with `ValueKey(item.id)` in
the notification card. UniqueKey was regenerated on every build,
forcing the Dismissible/PopupMenuButton subtree to be disposed on any
parent rebuild; the open menu's `mounted` check would then drop
`onSelected` silently.
- Add `ValueKey(device.macAddress)` to the device list rows for the
same reason, anchoring each row's Element (and its PopupMenuButton
State) to its device identity instead of its list position.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Rapidly switching filter chips, sort columns, or pagination on the devices
and notifications lists could overlap fetches, and an older response
landing after a newer one would silently overwrite the list with results
that no longer match the active query.
Thread an optional CancelToken into listDevices and listNotifications.
Each State now keeps one active token, cancels it on every new fetch and
on dispose, and bails from both success and catch branches when its token
has been superseded. DioExceptionType.cancel is silenced so internal
cancellations don't surface as user-visible errors. The notifications
list's prior `if (_isLoading) return;` guard is removed so the 1-min
periodic refresh is no longer skipped during user activity.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Set 5s/15s/15s connect/receive/send timeouts so calls fail fast instead of
hanging. Introduce dioErrorToUserMessage() and route DioException-throwing
call sites (device actions, devices list, notifications list, scanners
status card) through it, replacing raw e.toString() output. Notifications
list now surfaces load errors inline in the theme's error color, matching
the devices list pattern. Status dots show the mapped detail as a tooltip.
Stop logging the API key in cleartext.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Subscribes the devices list and notifications list to a shared
RouteObserver so they refetch when a pushed route (e.g. device detail)
is popped, avoiding stale state after registering or updating a device.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Mirror the notifications list paging pattern (offset/limit, fetch one
extra row to detect the next page, First/Prev/Next controls) across the
devices DB query, REST endpoint, API client and UI. Also add a First
page button to the notifications pagination row.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
HomeScreen is now a StatelessWidget responsible only for the responsive
layout structure. All notification state, pagination, filtering, and
mark-as-read logic lives in the new NotificationsList widget.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>