diff --git a/lib/providers/busylight_provider.dart b/lib/providers/busylight_provider.dart index fa775f3..635e61d 100644 --- a/lib/providers/busylight_provider.dart +++ b/lib/providers/busylight_provider.dart @@ -149,6 +149,11 @@ final colorProvider = StateNotifierProvider( }, ); +// ── Connection lost indicator ───────────────────────────────────────────────── +// Set to true by the polling notifier when a poll fails, false on success. + +final connectionLostProvider = StateProvider((_) => false); + // ── Background polling ──────────────────────────────────────────────────────── // Periodically pulls status + color from the device and silently updates state. @@ -181,8 +186,11 @@ class PollingNotifier extends StateNotifier { _ref.read(busylightStatusProvider.notifier).setLocalStatus(status); _ref.read(colorProvider.notifier).silentSet(color); _ref.read(brightnessProvider.notifier).silentSet(color.brightness); + // Connection restored — clear the lost flag + _ref.read(connectionLostProvider.notifier).state = false; } catch (_) { - // Silently ignore poll errors — connection issues are shown on manual refresh + // Signal connectivity issue to the UI + _ref.read(connectionLostProvider.notifier).state = true; } } diff --git a/lib/screens/home_screen.dart b/lib/screens/home_screen.dart index 3235863..186ab29 100644 --- a/lib/screens/home_screen.dart +++ b/lib/screens/home_screen.dart @@ -118,10 +118,11 @@ class _BodyState extends ConsumerState<_Body> { @override Widget build(BuildContext context) { - final statusAsync = ref.watch(busylightStatusProvider); - final brightness = ref.watch(brightnessProvider); - final color = ref.watch(colorProvider); - final presets = ref.watch(presetsProvider); + final statusAsync = ref.watch(busylightStatusProvider); + final brightness = ref.watch(brightnessProvider); + final color = ref.watch(colorProvider); + final presets = ref.watch(presetsProvider); + final connectionLost = ref.watch(connectionLostProvider); final status = statusAsync.valueOrNull ?? BusylightStatus.off; final displayColor = _statusColor(status, color); @@ -129,7 +130,7 @@ class _BodyState extends ConsumerState<_Body> { return ListView( padding: const EdgeInsets.all(24), children: [ - // Live color preview dot + // Live color preview dot — shows wifi-off icon when connection lost Center( child: AnimatedContainer( duration: const Duration(milliseconds: 400), @@ -137,10 +138,12 @@ class _BodyState extends ConsumerState<_Body> { height: 100, decoration: BoxDecoration( shape: BoxShape.circle, - color: status == BusylightStatus.off + color: connectionLost ? Colors.grey.shade900 - : displayColor.withOpacity(brightness), - boxShadow: status != BusylightStatus.off + : status == BusylightStatus.off + ? Colors.grey.shade900 + : displayColor.withOpacity(brightness), + boxShadow: (!connectionLost && status != BusylightStatus.off) ? [BoxShadow( color: displayColor.withOpacity(0.5 * brightness), blurRadius: 40, @@ -148,13 +151,20 @@ class _BodyState extends ConsumerState<_Body> { )] : null, ), + child: connectionLost + ? const Icon(Icons.wifi_off, color: Colors.grey, size: 36) + : null, ), ), const SizedBox(height: 12), Center( child: Text( - status.label.toUpperCase(), - style: const TextStyle(color: Colors.grey, letterSpacing: 2, fontSize: 13), + connectionLost ? 'NO CONNECTION' : status.label.toUpperCase(), + style: TextStyle( + color: connectionLost ? Colors.red.shade700 : Colors.grey, + letterSpacing: 2, + fontSize: 13, + ), ), ), const SizedBox(height: 36), @@ -177,9 +187,11 @@ class _BodyState extends ConsumerState<_Body> { BusylightStatus.off, ].map((s) => StatusButton( status: s, - isActive: status == s, + isActive: !connectionLost && status == s, isPending: _pendingStatus == s, - onTap: _pendingStatus == null ? () => _setStatus(s) : () {}, + onTap: (!connectionLost && _pendingStatus == null) + ? () => _setStatus(s) + : () {}, )).toList(), ), const SizedBox(height: 32), @@ -188,7 +200,7 @@ class _BodyState extends ConsumerState<_Body> { _PresetsScroller( presets: presets, pendingPresetId: _pendingPresetId, - onPresetTap: (_pendingStatus == null && _pendingPresetId == null) + onPresetTap: (!connectionLost && _pendingStatus == null && _pendingPresetId == null) ? _applyPreset : (_) {}, onPresetDelete: (preset) => ref.read(presetsProvider.notifier).remove(preset.id),