Commit Graph
25 Commits
Author SHA1 Message Date
rzuastiandClaude Opus 4.8 f847b60c21 Animate notification list inserts, removals and arrivals
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>
2026-06-04 19:35:18 -04:00
rzuastiandClaude Opus 4.8 c949deedb6 Show a page-level progress bar while paginating lists
Changing pages in the notifications or devices list gave no cue that
the next page was loading. Render an indeterminate progress bar at the
shell level, pinned flush against the bottom of the page body (above
the nav bar on phones, the screen bottom on wide layouts), driven by a
shared paginationLoading notifier the lists set while fetching. The
pagination bar keeps disabling its buttons during the fetch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 18:45:08 -04:00
rzuastiandClaude Opus 4.8 f64b4d3559 Collapse list filters to a dropdown on phones
On narrow layouts the devices list's filter chips (Not registered /
Registered / All) competed for horizontal space with the Sort and Filter
icon buttons and overlapped. Introduce a reusable FilterSelector that
keeps the chips on wide layouts but collapses to a compact dropdown
button on phones, and use it for both the devices and notifications
lists for consistency.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 18:22:01 -04:00
rzuastiandClaude Opus 4.8 0a899b7890 Shrink notification and device list page sizes on phones
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>
2026-06-04 18:13:37 -04:00
rzuastiandClaude Opus 4.8 8b8fa5ce89 Add pull-to-refresh to the notifications list
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>
2026-06-04 18:02:07 -04:00
rzuastiandClaude Opus 4.8 51dc5495ec Fold scroll-to-top into _fetchPage
Replace the _goToPage wrapper with an optional scrollToTop flag on
_fetchPage, so there is a single fetch entry point. The pagination bar
passes scrollToTop: true; all other callers keep the current behaviour.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 10:12:36 -04:00
rzuastiandClaude Opus 4.8 aa5f65de2b Scroll to top of list when changing pages
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>
2026-06-04 10:10:13 -04:00
rzuastiandClaude Opus 4.8 aa5a29271b Keep notification cards collapsed when a sibling is removed
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>
2026-06-04 10:02:57 -04:00
rzuastiandClaude Opus 4.8 2a6d480428 Collapse notification cards on tap anywhere
Tapping a notification's body already toggled expand/collapse, but the
action area below it wasn't tappable. Wrap the whole card in an InkWell
so tapping anywhere (body or action row) toggles, while the buttons
still handle their own taps.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 09:57:20 -04:00
rzuastiandClaude Opus 4.8 9367c359d7 Polish frontend: design tokens, theming, AppBar titles, empty/loading states
Frontend usability and aesthetics pass across web and mobile:

- Add shared layout tokens (theme/dimens.dart: Insets + Breakpoints) and
  replace magic-number spacing and per-file breakpoint consts.
- Route both themes through buildAppTheme (theme/theme_builder.dart): explicit
  useMaterial3 and a shared branded textTheme (Barlow Condensed for
  display/headline/title styles); logo now reads its style from the theme.
- Move screen titles into the shared AppBar (route-derived) and drop the
  redundant in-body headers; upgrade Settings buttons to M3 FilledButton.
- Add reusable EmptyState and skeleton loaders; Devices empty state links to
  scanner status, notifications get filter-aware messages.
- Add a first-run welcome intro and Save-disabled helper text in Settings.
- Make device Filter/Sort adaptive: bottom sheet on phones, dialog on wide.
- Collapse the device-list filter chips and Sort/Filter buttons into one row;
  rename the home Scanners card title and align its style.

Tests: add EmptyState/skeleton widget tests and Settings first-run cases;
update tests for the FilledButton swap and relocated titles. 89 passing,
dart analyze clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 08:39:37 -04:00
rzuastiandClaude Sonnet 4.6 29a5b7cf3e Expand notification cards in-place instead of navigating on tap
Tapping a notification card now toggles an expanded view showing the
full body text and M3 action buttons (Mark as read/unread, View device),
replacing the former popup menu and direct navigation on tap.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 16:18:40 -04:00
rzuastiandClaude Opus 4.7 17712c25f1 Collapse scanner-card and pagination duplication into shared widgets
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>
2026-06-01 07:09:24 -04:00
rzuastiandClaude Sonnet 4.6 8be65f3ffa Fold pagination +1 trick into API methods, return typed records
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>
2026-05-31 17:40:18 -04:00
rzuastiandClaude Sonnet 4.6 f75d1ce313 Pause polling when screens are off-route or app is backgrounded
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>
2026-05-31 17:34:19 -04:00
rzuastiandClaude Opus 4.7 61f2579152 Make notification mark-as-read reliably update the list
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>
2026-05-31 10:28:23 -04:00
rzuastiandClaude Opus 4.7 30df22df56 Cancel in-flight paginated fetches to prevent stale-write races
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>
2026-05-31 09:30:03 -04:00
rzuastiandClaude Opus 4.7 7954a9c755 Add Dio timeouts and map backend errors to user-friendly messages
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>
2026-05-31 09:06:56 -04:00
rzuastiandClaude Opus 4.7 926a902377 Refresh device and notification lists on return navigation
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>
2026-05-29 15:13:00 -04:00
rzuastiandClaude Opus 4.7 251c06277e Add pagination to the devices list screen
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>
2026-05-29 13:07:27 -04:00
rzuastiandClaude Sonnet 4.6 75f597bfe4 Add mDNS scanner status to Status and Home screens
Add an mDNS Scanner status card to the Status screen mirroring the ARP
card, and replace the ARP card on the Home screen with a combined Status
card that summarizes both scanners in one line each. Extract the shared
duration formatting helper into utils/duration_formatter.dart.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 10:19:50 -04:00
rzuastiandClaude Sonnet 4.6 39f382fd0d Extract NotificationsList widget from HomeScreen into its own file
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>
2026-05-29 08:06:30 -04:00
rzuastiandClaude Sonnet 4.6 85b86026cb Replace infinite-scroll notifications with button-based pagination
- Swap PagingController/PagedSliverList for explicit page state and
  Previous/Next icon buttons below the list
- Fetch pageSize+1 items to detect the last page without a total-count
  API; pagination controls are hidden when there is only one page
- Remove unused infinite_scroll_pagination dependency

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 07:56:03 -04:00
rzuastiandClaude Sonnet 4.6 d14c8beb60 Fix empty-state layout: compact message instead of viewport-filling indicator
Replace Center-in-Expanded / SliverFillRemaining empty states in the
notifications and devices lists with compact, centered inline text so
status widgets below remain visible without scrolling. Also align
notification filter chips to match the devices list spacing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 20:03:11 -04:00
rzuastiandClaude Sonnet 4.6 f113da9cd0 Refactor frontend: extract widgets and eliminate duplicated scanner state
- Convert ArpScannerCard to a self-managing StatefulWidget (owns its own 5s refresh + 1s tick timers), removing the duplicated state management that existed in both HomeScreen and StatusScreen
- Extract NotificationCard to home/notification_card.dart
- Extract DeviceSummaryCard (with its 1-min refresh timer) to widgets/device_summary_card.dart
- HomeScreen drops from 515 to 253 lines; StatusScreen from 82 to 19 lines

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 19:23:27 -04:00
rzuastiandClaude Sonnet 4.6 251d8a479e Redesign Home screen with device summary and scanner status
Adds a responsive Home screen combining the notification list with a
device summary panel and ARP scanner status, visible side-by-side on
wide screens and stacked on narrow ones.

Backend:
- New GET /api/devices/summary endpoint returning registered device
  counts and "seen in last 24h / 7 days" breakdowns by registration
  status
- Migration 07 adds indexes on (is_registered) and (last_seen,
  is_registered) so summary queries use index range scans instead of
  full table scans

Frontend:
- HomeScreen replaces NotificationList, embedding notifications,
  device summary card, and ArpScannerCard in a LayoutBuilder-driven
  two-column (≥700 px) or single-column layout
- ArpScannerCard extracted to a shared public widget reused by both
  HomeScreen and StatusScreen
- Nav rail entry renamed to "Home" with home icon

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 19:10:08 -04:00