import 'dart:async'; import 'package:flutter/material.dart'; import 'package:votianlt_app/services/developer.dart' as developer; import 'package:votianlt_app/services/websocket_service.dart'; import 'package:votianlt_app/services/dart_mq.dart'; class OfflineBanner extends StatefulWidget { const OfflineBanner({super.key}); @override State createState() => _OfflineBannerState(); } class _OfflineBannerState extends State { final StompService _stompService = StompService(); DartMQSubscription? _connSub; Timer? _countdownTimer; int _secondsToRetry = 15; bool _hadConnection = false; // Track if we ever had a successful connection @override void initState() { super.initState(); // Check if we're already connected (e.g., coming back to this screen) _hadConnection = _stompService.isConnected && _stompService.isAuthenticated; // Initialize countdown based on current connection state _onConnectionChange(_stompService.isConnected && _stompService.isAuthenticated); _connSub = DartMQ().subscribe(MQTopics.connectionStatus, _onConnectionChange); } void _onConnectionChange(bool isConnected) { if (!mounted) return; if (isConnected) { _hadConnection = true; // Mark that we had a successful connection _stopCountdown(); setState(() {}); } else { _startCountdown(); } } void _startCountdown() { _stopCountdown(); setState(() { _secondsToRetry = 15; }); _countdownTimer = Timer.periodic(const Duration(seconds: 1), (_) async { if (!mounted) return; if (_stompService.isConnected) { _stopCountdown(); return; } // Decrement until 0, then attempt reconnect if (_secondsToRetry > 1) { setState(() { _secondsToRetry = _secondsToRetry - 1; }); return; } // Show 0 for one tick and try to reconnect now setState(() { _secondsToRetry = 0; }); try { // Only auto-reconnect if we already know the target; discovery remains user-initiated await _stompService.connect(); } catch (e, stackTrace) { developer.log('Auto-reconnect attempt failed: $e', name: 'OfflineBanner'); developer.log('Stack trace: $stackTrace', name: 'OfflineBanner'); } if (!mounted) return; if (!_stompService.isConnected) { // Still offline -> reset countdown for next attempt setState(() { _secondsToRetry = 15; }); } }); } void _stopCountdown() { _countdownTimer?.cancel(); _countdownTimer = null; } @override void dispose() { _stopCountdown(); _connSub?.cancel(); _connSub = null; super.dispose(); } @override Widget build(BuildContext context) { final isOnline = _stompService.isConnected && _stompService.isAuthenticated; if (isOnline) return const SizedBox.shrink(); // Different messages for initial connection vs connection lost final String title; final String subtitle; final IconData icon; final Color? bgColor; final Color? iconColor; final Color? titleColor; final Color? subtitleColor; if (_hadConnection) { // Connection was lost title = 'Offline – Verbindung verloren'; subtitle = 'Verbindung wird wiederhergestellt.'; icon = Icons.wifi_off; bgColor = Colors.red[50]; iconColor = Colors.red[700]; titleColor = Colors.red[900]; subtitleColor = Colors.red[800]; } else { // Initial connection attempt title = 'Verbinde mit Server...'; subtitle = 'Bitte warten.'; icon = Icons.sync; bgColor = Colors.orange[50]; iconColor = Colors.orange[700]; titleColor = Colors.orange[900]; subtitleColor = Colors.orange[800]; } return Container( width: double.infinity, padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 12), color: bgColor, child: Row( children: [ Icon(icon, color: iconColor), const SizedBox(width: 8), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Text( title, style: TextStyle( color: titleColor, fontWeight: FontWeight.w700, ), ), const SizedBox(height: 2), Text( subtitle, style: TextStyle( color: subtitleColor, fontSize: 12, ), ), ], ), ), ], ), ); } }