Route scanner status colors through the active theme

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
rzuasti
2026-06-01 07:55:34 -04:00
co-authored by Claude Opus 4.7
parent 376eb3c367
commit d94cc2183d
4 changed files with 52 additions and 24 deletions
+12 -4
View File
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../model/arp_scanner_status.dart'; import '../model/arp_scanner_status.dart';
import '../theme/app_colors.dart';
import '../utils/duration_formatter.dart'; import '../utils/duration_formatter.dart';
import '../utils/oott_api.dart'; import '../utils/oott_api.dart';
import 'scanner_status_card.dart'; import 'scanner_status_card.dart';
@@ -18,12 +19,19 @@ class ArpScannerCard extends StatelessWidget {
); );
} }
static ScannerStatus _resolve(ArpScannerStatus status, double elapsed) { static ScannerStatus _resolve(
BuildContext context,
ArpScannerStatus status,
double elapsed,
) {
final successColor =
Theme.of(context).extension<AppColorExtension>()!.success;
final neutralColor = Theme.of(context).colorScheme.outline;
if (status.isRunning) { if (status.isRunning) {
final sub = status.runningForSeconds != null final sub = status.runningForSeconds != null
? ['Running for ${formatSeconds(status.runningForSeconds! + elapsed)}'] ? ['Running for ${formatSeconds(status.runningForSeconds! + elapsed)}']
: <String>[]; : <String>[];
return (color: Colors.green, label: 'Running', sublabels: sub); return (color: successColor, label: 'Running', sublabels: sub);
} }
if (status.nextRunInSeconds != null) { if (status.nextRunInSeconds != null) {
final remaining = (status.nextRunInSeconds! - elapsed).clamp( final remaining = (status.nextRunInSeconds! - elapsed).clamp(
@@ -31,11 +39,11 @@ class ArpScannerCard extends StatelessWidget {
double.infinity, double.infinity,
); );
return ( return (
color: Colors.grey, color: neutralColor,
label: 'Waiting for next run', label: 'Waiting for next run',
sublabels: ['Next run in ${formatSeconds(remaining)}'], sublabels: ['Next run in ${formatSeconds(remaining)}'],
); );
} }
return (color: Colors.grey, label: 'Not yet started', sublabels: []); return (color: neutralColor, label: 'Not yet started', sublabels: []);
} }
} }
+16 -3
View File
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../model/mdns_scanner_status.dart'; import '../model/mdns_scanner_status.dart';
import '../theme/app_colors.dart';
import '../utils/duration_formatter.dart'; import '../utils/duration_formatter.dart';
import '../utils/oott_api.dart'; import '../utils/oott_api.dart';
import 'scanner_status_card.dart'; import 'scanner_status_card.dart';
@@ -18,9 +19,17 @@ class MdnsScannerCard extends StatelessWidget {
); );
} }
static ScannerStatus _resolve(MdnsScannerStatus status, double elapsed) { static ScannerStatus _resolve(
BuildContext context,
MdnsScannerStatus status,
double elapsed,
) {
if (!status.isListening) { if (!status.isListening) {
return (color: Colors.grey, label: 'Not yet started', sublabels: []); return (
color: Theme.of(context).colorScheme.outline,
label: 'Not yet started',
sublabels: [],
);
} }
final sublabels = <String>[ final sublabels = <String>[
status.listeningForSeconds != null status.listeningForSeconds != null
@@ -29,6 +38,10 @@ class MdnsScannerCard extends StatelessWidget {
if (status.lastDeviceSeenSecondsAgo != null) if (status.lastDeviceSeenSecondsAgo != null)
'Last device ${formatSeconds(status.lastDeviceSeenSecondsAgo! + elapsed)} ago', 'Last device ${formatSeconds(status.lastDeviceSeenSecondsAgo! + elapsed)} ago',
]; ];
return (color: Colors.green, label: 'Listening', sublabels: sublabels); return (
color: Theme.of(context).extension<AppColorExtension>()!.success,
label: 'Listening',
sublabels: sublabels,
);
} }
} }
@@ -11,7 +11,7 @@ import 'polled_stale_indicator.dart';
typedef ScannerStatus = ({Color color, String label, List<String> sublabels}); typedef ScannerStatus = ({Color color, String label, List<String> sublabels});
typedef ScannerStatusResolver<T> = typedef ScannerStatusResolver<T> =
ScannerStatus Function(T value, double elapsedSeconds); ScannerStatus Function(BuildContext context, T value, double elapsedSeconds);
/// Generic card that polls a scanner status endpoint and renders the result /// Generic card that polls a scanner status endpoint and renders the result
/// using the provided [resolver]. The two scanner cards (ARP, mDNS) are thin /// using the provided [resolver]. The two scanner cards (ARP, mDNS) are thin
@@ -71,7 +71,7 @@ class _ScannerStatusCardState<T> extends State<ScannerStatusCard<T>> {
); );
} }
final status = _resolveStatus(freshness); final status = _resolveStatus(context, freshness);
final isStale = freshness == PolledFreshness.stale; final isStale = freshness == PolledFreshness.stale;
final theme = Theme.of(context); final theme = Theme.of(context);
@@ -124,10 +124,10 @@ class _ScannerStatusCardState<T> extends State<ScannerStatusCard<T>> {
); );
} }
ScannerStatus _resolveStatus(PolledFreshness freshness) { ScannerStatus _resolveStatus(BuildContext context, PolledFreshness freshness) {
if (freshness == PolledFreshness.error) { if (freshness == PolledFreshness.error) {
return ( return (
color: Colors.red, color: Theme.of(context).colorScheme.error,
label: 'Error', label: 'Error',
sublabels: [ sublabels: [
_polled.lastErrorMessage ?? _polled.lastErrorMessage ??
@@ -139,6 +139,6 @@ class _ScannerStatusCardState<T> extends State<ScannerStatusCard<T>> {
.difference(_polled.lastSuccessAt!) .difference(_polled.lastSuccessAt!)
.inSeconds .inSeconds
.toDouble(); .toDouble();
return widget.resolver(_polled.value as T, elapsed); return widget.resolver(context, _polled.value as T, elapsed);
} }
} }
+19 -12
View File
@@ -6,6 +6,7 @@ import 'package:go_router/go_router.dart';
import '../model/arp_scanner_status.dart'; import '../model/arp_scanner_status.dart';
import '../model/mdns_scanner_status.dart'; import '../model/mdns_scanner_status.dart';
import '../navigation.dart'; import '../navigation.dart';
import '../theme/app_colors.dart';
import '../utils/backend_reachability.dart'; import '../utils/backend_reachability.dart';
import '../utils/duration_formatter.dart'; import '../utils/duration_formatter.dart';
import '../utils/oott_api.dart'; import '../utils/oott_api.dart';
@@ -120,8 +121,8 @@ class _ScannersStatusCardState extends State<ScannersStatusCard>
); );
} }
final (arpColor, arpText) = _resolveArp(arpFreshness); final (arpColor, arpText) = _resolveArp(context, arpFreshness);
final (mdnsColor, mdnsText) = _resolveMdns(mdnsFreshness); final (mdnsColor, mdnsText) = _resolveMdns(context, mdnsFreshness);
return Card( return Card(
clipBehavior: Clip.antiAlias, clipBehavior: Clip.antiAlias,
@@ -201,9 +202,12 @@ class _ScannersStatusCardState extends State<ScannersStatusCard>
); );
} }
(Color, String) _resolveArp(PolledFreshness freshness) { (Color, String) _resolveArp(BuildContext context, PolledFreshness freshness) {
final theme = Theme.of(context);
final successColor = theme.extension<AppColorExtension>()!.success;
final neutralColor = theme.colorScheme.outline;
if (freshness == PolledFreshness.error) { if (freshness == PolledFreshness.error) {
return (Colors.red, 'Error'); return (theme.colorScheme.error, 'Error');
} }
final status = _arp.value!; final status = _arp.value!;
final elapsed = DateTime.now() final elapsed = DateTime.now()
@@ -212,21 +216,24 @@ class _ScannersStatusCardState extends State<ScannersStatusCard>
.toDouble(); .toDouble();
if (status.isRunning) { if (status.isRunning) {
final secs = (status.runningForSeconds ?? 0) + elapsed; final secs = (status.runningForSeconds ?? 0) + elapsed;
return (Colors.green, 'Running for ${formatSeconds(secs)}'); return (successColor, 'Running for ${formatSeconds(secs)}');
} }
if (status.nextRunInSeconds != null) { if (status.nextRunInSeconds != null) {
final remaining = (status.nextRunInSeconds! - elapsed).clamp( final remaining = (status.nextRunInSeconds! - elapsed).clamp(
0.0, 0.0,
double.infinity, double.infinity,
); );
return (Colors.grey, 'Next run in ${formatSeconds(remaining)}'); return (neutralColor, 'Next run in ${formatSeconds(remaining)}');
} }
return (Colors.grey, 'Not yet started'); return (neutralColor, 'Not yet started');
} }
(Color, String) _resolveMdns(PolledFreshness freshness) { (Color, String) _resolveMdns(BuildContext context, PolledFreshness freshness) {
final theme = Theme.of(context);
final successColor = theme.extension<AppColorExtension>()!.success;
final neutralColor = theme.colorScheme.outline;
if (freshness == PolledFreshness.error) { if (freshness == PolledFreshness.error) {
return (Colors.red, 'Error'); return (theme.colorScheme.error, 'Error');
} }
final status = _mdns.value!; final status = _mdns.value!;
final elapsed = DateTime.now() final elapsed = DateTime.now()
@@ -236,10 +243,10 @@ class _ScannersStatusCardState extends State<ScannersStatusCard>
if (status.isListening) { if (status.isListening) {
if (status.lastDeviceSeenSecondsAgo != null) { if (status.lastDeviceSeenSecondsAgo != null) {
final secs = status.lastDeviceSeenSecondsAgo! + elapsed; final secs = status.lastDeviceSeenSecondsAgo! + elapsed;
return (Colors.green, 'Last device seen ${formatSeconds(secs)} ago'); return (successColor, 'Last device seen ${formatSeconds(secs)} ago');
} }
return (Colors.green, 'No devices seen yet'); return (successColor, 'No devices seen yet');
} }
return (Colors.grey, 'Not yet started'); return (neutralColor, 'Not yet started');
} }
} }