diff --git a/app/lib/ui/providers/wifi_connection_provider.dart b/app/lib/ui/providers/wifi_connection_provider.dart index 1845318..53fb712 100644 --- a/app/lib/ui/providers/wifi_connection_provider.dart +++ b/app/lib/ui/providers/wifi_connection_provider.dart @@ -32,6 +32,53 @@ final wifiLinkErrorReasonProvider = Provider((ref) { return ref.watch(wifiTransportProvider).lastErrorReason; }); +/// Zeigt an, ob gerade UDP-Pakete ankommen ("receiving"/"not receiving"), +/// unabhaengig vom (binaeren) Verbindungszustand - der Socket kann offen +/// sein (LinkState.connected), ohne dass die Gegenstelle tatsaechlich +/// sendet. Faellt [_receivingTimeout] nach dem letzten Paket automatisch +/// wieder auf "not receiving" zurueck, dafuer muss ein Timer laufen statt +/// nur auf den incoming-Stream zu reagieren. +class WifiStreamActivityNotifier extends Notifier { + static const _receivingTimeout = Duration(seconds: 2); + + StreamSubscription? _dataSub; + StreamSubscription? _stateSub; + Timer? _timeoutTimer; + + @override + bool build() { + final transport = ref.watch(wifiTransportProvider); + _dataSub?.cancel(); + _stateSub?.cancel(); + _timeoutTimer?.cancel(); + _dataSub = transport.incoming.listen((_) => _onPacket()); + _stateSub = transport.state.listen(_onStateChanged); + ref.onDispose(() { + _dataSub?.cancel(); + _stateSub?.cancel(); + _timeoutTimer?.cancel(); + }); + return false; + } + + void _onPacket() { + state = true; + _timeoutTimer?.cancel(); + _timeoutTimer = Timer(_receivingTimeout, () => state = false); + } + + void _onStateChanged(LinkState linkState) { + if (linkState != LinkState.connected) { + _timeoutTimer?.cancel(); + state = false; + } + } +} + +final wifiStreamActivityProvider = + NotifierProvider( + WifiStreamActivityNotifier.new); + /// Ein Eintrag im WLAN-Verbindungsprotokoll (Doku 7). class WifiConnectionLogEntry { const WifiConnectionLogEntry(this.timestamp, this.message); diff --git a/app/lib/ui/screens/settings/settings_screen.dart b/app/lib/ui/screens/settings/settings_screen.dart index 94c3c23..68dcd8d 100644 --- a/app/lib/ui/screens/settings/settings_screen.dart +++ b/app/lib/ui/screens/settings/settings_screen.dart @@ -143,6 +143,7 @@ class _WifiPanelState extends ConsumerState<_WifiPanel> { Widget build(BuildContext context) { final linkState = ref.watch(wifiLinkStateProvider).value; final errorReason = ref.watch(wifiLinkErrorReasonProvider); + final isReceiving = ref.watch(wifiStreamActivityProvider); final connectionSettings = ref.watch(connectionSettingsProvider).value; final log = ref.watch(wifiConnectionLogProvider); final packetCount = ref.watch(wifiConnectionLogProvider.notifier).packetCount; @@ -163,6 +164,8 @@ class _WifiPanelState extends ConsumerState<_WifiPanel> { children: [ Text('WiFi (UDP)', style: Theme.of(context).textTheme.titleSmall), const Spacer(), + _StreamActivityPill(isReceiving: isReceiving), + const SizedBox(width: 6), _StatePill(state: linkState), ], ), @@ -459,6 +462,32 @@ class _BluetoothPanel extends ConsumerWidget { } } +/// Zeigt neben [_StatePill], ob gerade UDP-Pakete ankommen ("receiving") - +/// unabhaengig vom Verbindungszustand, der Socket kann offen sein, ohne +/// dass die Gegenstelle sendet (siehe wifiStreamActivityProvider). +class _StreamActivityPill extends StatelessWidget { + const _StreamActivityPill({required this.isReceiving}); + + final bool isReceiving; + + @override + Widget build(BuildContext context) { + final color = isReceiving ? DmcColors.liveGreen : Colors.grey; + return Container( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), + decoration: BoxDecoration( + color: color.withValues(alpha: 0.15), + borderRadius: BorderRadius.circular(12), + border: Border.all(color: color), + ), + child: Text( + isReceiving ? 'Receiving' : 'Not receiving', + style: TextStyle(color: color, fontSize: 12), + ), + ); + } +} + class _StatePill extends StatelessWidget { const _StatePill({required this.state});