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>
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>
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>
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>
- 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>