Commit Graph
91 Commits
Author SHA1 Message Date
rzuastiandClaude Opus 4.8 4924146075 Pin Android SDK components and aapt2 override for Flutter builds
The Android SDK lives in the read-only Nix store, so the Gradle build
cannot auto-install missing components. Pin everything the Flutter
Android build needs in the dev shell's composeAndroidPackages:
platforms 33-36 (app targets 36; plugins pin 34 and 35), build-tools
35.0.0 (AGP 8.11.1), NDK 28.2.13676358 (flutter.ndkVersion), and
cmake 3.22.1 (plugin native builds).

Also redirect the Android Gradle Plugin to the Nix-patched aapt2 from
the SDK via android.aapt2FromMavenOverride, written to the user-global
~/.gradle/gradle.properties on shell entry (the project's tracked
gradle.properties must not contain a machine-specific store path).
AGP's Maven-downloaded aapt2 cannot run on NixOS.

Ignore the generated frontend/android/build/ directory.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 20:49:28 -04:00
rzuastiandClaude Opus 4.8 ada989c11b Add reproducible Android emulator to the dev shell
Pin the Android SDK composition (platform 36 + google_apis x86_64 system
image + emulator) via composeAndroidPackages so the emulator image is
always available and reproducible across machines. Auto-create the
"oott_api36" AVD idempotently on dev-shell entry, and add
frontend/android-emulator.sh to launch it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 19:23:33 -04:00
rzuastiandClaude Opus 4.8 49544f0a20 Make the status screen scrollable to avoid bottom overflow
The status screen was a non-scrolling Column, so it overflowed
whenever the offline banner consumed vertical space or the viewport
was too short to fit all scanner cards. Wrap it in a
SingleChildScrollView, matching the other screens.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 16:58:25 -04:00
rzuastiandClaude Opus 4.8 4a1fe60ec3 Derive the version from the Cargo/pubspec manifests
The version string was duplicated across six places. Collapse it to two
ecosystem sources of truth and derive the rest:

- backend/src/web_server.rs: omit the OpenAPI info.version so utoipa fills
  it from CARGO_PKG_VERSION (backend/Cargo.toml); add a test pinning this.
- nix/package.nix: read the version from backend/Cargo.toml via fromTOML.
- nix/frontend.nix: read the version from frontend/pubspec.yaml by splitting
  into lines (a whole-file regex triggers catastrophic backtracking in Nix's
  regex engine).
- frontend/lib/about/about.dart: read the version at runtime via
  package_info_plus instead of a hardcoded constant.

Also drop frontend/pubspec.lock.json: it is unreferenced (Nix's
autoPubspecLock generates its own JSON from pubspec.lock) and was going stale.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 16:49:52 -04:00
rzuastiandClaude Opus 4.8 c9e79e028f Split oott_api.dart into per-domain modules
Break the 372-line frontend API client into focused files under
lib/utils/api/: error mapping (api_error.dart), Dio setup behind a
buildDio factory (dio_config.dart), and device/scanner/notification
endpoints as extensions in part files. Collapse the five near-identical
scanner-status methods via a generic _getModel helper and de-duplicate
the pagination logic via a shared _paginate helper.

The BackendAPI singleton and dioErrorToUserMessage remain importable
from oott_api.dart unchanged, so no call sites are affected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 10:24:37 -04:00
rzuastiandClaude Opus 4.8 72dbbe5e9a Count passive scanner devices seen in the last hour
Replace the lifetime "devices seen since start" counter in the mDNS, SSDP
and DHCP scanners with a rolling count of distinct devices (deduped by
MAC) seen within the last hour.

Each scanner's status now tracks a MAC -> last-seen-time map; the snapshot
prunes entries older than 60 minutes and reports the remaining count. The
API field name (devices_seen) is unchanged, so only its meaning and the
frontend labels ("N devices in the last hour") are updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 08:57:01 -04:00
rzuastiandClaude Opus 4.8 d15411a889 Report devices seen on last scan in ARP/SNMP status
The ARP and SNMP scanner status endpoints and front-end cards now expose
the number of devices found by the most recent successful scan, following
the existing mDNS device-count pattern. The count persists across the
running/waiting transitions, and for SNMP a failed poll keeps the last
good count rather than overwriting it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 08:45:45 -04:00
rzuastiandClaude Opus 4.8 2987f66363 Add SNMP scanner that polls a gateway's ARP table
Introduce a sixth scanner that periodically queries an SNMP agent
(typically the router/firewall) for its ARP/neighbour cache via
SNMPv2c and feeds discovered devices into the shared devices, events
and notifications pipeline. Unlike the ARP scanner it generates no
traffic on the local segment and can surface devices across all
subnets the agent routes.

Scope is intentionally minimal: SNMPv2c only, a single target, and the
ipNetToMediaTable (ARP) only. SNMPv3 and switch MAC/forwarding-table
polling are left as follow-ups in TODO.md.

- backend: csnmp dependency; SnmpScanner config (opt-in, off unless a
  [snmp_scanner] section is present); DeviceEventScanner::Snmp;
  scanners/snmp/{finder,scanner,status}; main.rs wiring; status API
  endpoint wired into OpenAPI
- frontend: SNMP scanner status model, card, API method, status screen
  and summary card rows, and device-event label
- docs/config: sample TOML, README options, NixOS module option, and
  setup notes for enabling SNMP on pfSense/OPNsense

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 08:03:20 -04:00
rzuastiandClaude Opus 4.8 a90bcc360d Document backend connection options and allow iOS local networking
Add NSAllowsLocalNetworking ATS exception so the iOS app can reach a
backend over HTTP on the local network, and document the supported
connection paths (direct private-IP HTTP vs. reverse proxy with HTTPS)
in the README.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 19:38:43 -04:00
rzuastiandClaude Opus 4.8 abeba88fec Change scanner status label "Not yet started" to "Not started"
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 19:25:26 -04:00
rzuastiandClaude Opus 4.8 4e975598f9 Add DHCP scanner to frontend status displays
Add a DHCP scanner card to the Status screen and a DHCP row to the
consolidated Status card on the Home screen, mirroring the existing
mDNS/SSDP implementations. Also label DHCP-discovered device events as
"DHCP" instead of the raw "Dhcp".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 18:45:38 -04:00
rzuastiandClaude Opus 4.8 440959b10d Add SSDP/UPnP scanner to frontend status displays
Surface the SSDP/UPnP scanner alongside ARP and mDNS: a dedicated card on
the Status screen and a row in the consolidated Status card on Home. Mirrors
the existing mDNS implementation and reuses the generic ScannerStatusCard and
PolledValue infrastructure. Also maps the 'Ssdp' device-event scanner to the
"SSDP/UPnP" label.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 18:13:33 -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 55a6b29d1c Show scanner source in device event tooltip
Surface the originating scanner ("ARP" or "mDNS") in the event timeline
tooltip so users can tell which scanner reported each sighting.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 08:19:49 -04:00
rzuastiandClaude Opus 4.7 d94cc2183d Route scanner status colors through the active theme
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 07:55:34 -04:00
rzuastiandClaude Opus 4.7 376eb3c367 Use grey dot when ARP scanner is waiting for next run
Amber implied a problem when the scanner is simply idle between runs.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 07:46:51 -04:00
rzuastiandClaude Opus 4.7 408585c056 Keep status pills inline and show tooltips only on overflow
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 07:42:43 -04:00
rzuastiandClaude Opus 4.7 0304c50efb Move home screen route from /notifications to /
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 07:17:41 -04:00
rzuastiandClaude Opus 4.7 6303951b3d Show online/offline status dot beside device last-seen
Highlights devices seen within the last 10 minutes with a green dot so
recent activity is scannable at a glance; older devices get a muted
outline-colored dot with an "Appears offline" tooltip.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 07:15:30 -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 9b521e0847 Stop interpolating raw exception into settings snackbar
Defense-in-depth: the persisted value is the (XOR-obfuscated) API key,
so a future SharedPreferences backend that surfaced the value in its
exception could leak it through the UI. Log the detail via debugPrint
and show a fixed message to the user instead.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 19:00:38 -04:00
rzuastiandClaude Sonnet 4.6 140ebab035 Catch uncaught async errors and surface response-shape failures
Wrap main in runZonedGuarded and install FlutterError.onError /
PlatformDispatcher.onError so background async failures get logged
instead of dying silently. Guard PrefUtil.init so a SharedPreferences
platform error no longer aborts startup. Recognize TypeError and
FormatException in dioErrorToUserMessage to give a clearer message
when the backend returns an unexpected JSON shape or bad timestamp.
Wrap Settings._save in try/catch so prefs write failures show a
snackbar instead of leaving the Save button silently dead.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 18:55:35 -04:00
rzuastiandClaude Sonnet 4.6 e70676a193 Surface persistence and fetch errors instead of swallowing them
PrefUtil.setValue now returns the underlying SharedPreferences result
and throws on unsupported types; settings save reports failures rather
than always showing success. Device detail and event history report
the real Dio error message and skip cancellations.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 18:43:46 -04:00
rzuastiandClaude Sonnet 4.6 f7f255e20f Compare parsed /test response instead of stringified Dio dump
Previously BackendAPI.test() called response.toString().contains('OOTT_API_OK'),
which scans the full stringified Response (status line, headers, body, request
path). A reverse proxy or captive portal returning HTML that happens to echo the
token would falsely pass the connectivity check. Compare response.data — the
JSON-decoded String the backend's /api/test handler returns — against the
expected value with exact equality instead.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 18:32:40 -04:00
rzuastiandClaude Opus 4.7 8e9e221048 Omit Authorization header when API key is empty
Send no Authorization header at all on a fresh install (rather than
"Bearer " with an empty token), so the backend can distinguish "no
credentials supplied" from "credentials supplied but invalid".

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 18:26:00 -04:00
rzuastiandClaude Sonnet 4.6 b5da90be59 Pause polling and show a banner when the backend is unreachable
Adds a centralized BackendReachability singleton fed by a Dio
interceptor (connection-level failures only) and connectivity_plus.
PolledValue subscribes to it and pauses on offline / reloads
immediately on reconnect, replacing the previous per-card error
cascade. MainShell renders a global OfflineBanner with Retry and
Settings actions; cards downgrade error→stale when offline so the
banner is the only red element.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 18:21:55 -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 Sonnet 4.6 838b292456 Add dio_smart_retry for automatic GET request retries
Transient network hiccups and temporary backend errors (5xx, timeouts)
now retry transparently up to twice (1 s then 3 s backoff) before
surfacing an error to the user. Non-idempotent methods (POST, PUT,
DELETE), cancellations, and non-retryable status codes are excluded.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 17:19:00 -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 bb677aeebf Centralize status polling with last-known-good, stale, and error tiers
The four polling status cards (ARP, mDNS, scanners summary, devices
summary) each rolled their own Timer/_status/_error/_isLoading state and
flipped to a red 'Error' UI on the first failed poll, throwing away
last-known-good data that was still in memory. A momentary backend blip
made every card flash red.

Introduce PolledValue<T>, a ChangeNotifier that owns the periodic timer,
in-flight CancelToken, last value, last error, and a freshness
classification (initialLoading / fresh / stale / error). 'stale' kicks in
on the first failure but keeps the value visible; 'error' only takes over
after staleErrorAfter elapses without a success (30s for the 5s scanner
polls, 3min for the 1min device-summary poll). All four cards now branch
on freshness and show a small live-updating warning icon with a tooltip
('Last updated N ago — <error>') while stale, instead of flipping red.

Also threads CancelToken? through the three polled GET endpoints.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 10:03:19 -04:00
rzuastiandClaude Opus 4.7 d7276c8383 Replace per-method debugPrints with a debug-only Dio LogInterceptor
Every BackendAPI method had hand-rolled 'About to call ...' and
'Received: ...' debug lines, and the constructor printed the base URL and
a masked API-key marker. Replace all of that with a single LogInterceptor
attached under `if (kDebugMode)` to both the singleton Dio and the
ephemeral Dio used by BackendAPI.test(). Headers and bodies are kept off
so the Authorization bearer never lands in the console. The interceptor
is tree-shaken from release builds.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 09:39:06 -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 d3b6c64e8b Apply Settings changes to the live BackendAPI without restart
Settings._save wrote new base_url/api_key to prefs but the BackendAPI
singleton kept the Dio it built at first access, so URL/key changes only
took effect after an app restart. Expose reconfigureFromPrefs() on
BackendAPI and call it from Settings._save so subsequent requests pick up
the new config immediately.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 09:17:15 -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 0f6296759d Add edit dialog for registered devices
Extend PUT /api/devices/{mac} to accept an optional name and surface an
Edit action from both the device detail screen and the per-row overflow
menu, letting users modify owner, device type, vendor and name without
forgetting and re-registering.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 11:27:52 -04:00
rzuastiandClaude Opus 4.7 3a579f0321 Redesign devices list as a sortable, responsive headered list
GET /api/devices accepts sort_by/sort_order (whitelisted columns; default
last_seen DESC, stable secondary sort on mac_address), device registration
captures an optional hostname, and the Flutter list switches between a
wide headered layout and a compact ListTile under 600px. Per-row overflow
menu retains View details / Register / Forget actions.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 11:09:55 -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 2ba51769ec Show registered devices by owner's device type name
Display registered devices with a "John's Laptop" title and move their
IP address into the card body, instead of showing the owner separately.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 14:24:40 -04:00
rzuastiandClaude Opus 4.7 7fb457ca4a Make device detail IP and info fields selectable
Use SelectableText for the IP address and all info-card values so users
can select and copy them (e.g. the MAC address).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 13:56:16 -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 2fe850c070 Auto-focus owner field and submit on Enter in register device dialog
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 08:29:50 -04:00
rzuastiandClaude Sonnet 4.6 1da0306b61 Replace device detail popup menu with inline CTA buttons
Register and Forget Device actions are now always-visible buttons
(FilledButton and error-styled OutlinedButton) placed inline below
the device info card, following M3 guidelines for in-content actions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 08:25:11 -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 e5c27f067c Align navigation with Material 3 responsive recommendations
- Replace single NavigationRail with adaptive navigation: NavigationBar
  (bottom) for compact <600dp, icon-only NavigationRail for medium
  600–840dp, and extended NavigationRail for expanded ≥840dp
- Remove nested Scaffold/AppBar from About, Settings, DeviceList, and
  DeviceDetail — all screens now render as plain widgets inside the
  single shell Scaffold; page titles surface as inline headings

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 19:51:31 -04:00
rzuastiandClaude Sonnet 4.6 ff83ada576 Improve home screen widget UX: visual hierarchy and navigation
- DeviceSummaryCard and ArpScannerCard are now tappable, navigating to
  the devices list and status screen respectively
- Elevated "Devices" title to titleLarge; section subtitles promoted to
  bodyMedium/w600 while row content is demoted to bodySmall
- Renamed top-level row label to "Registered in the system"

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 19:34:42 -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