refactor: Projektstruktur in app/ und backend/ aufgeteilt

This commit is contained in:
2026-03-24 15:06:44 +01:00
parent 5f5d5995c5
commit 2673ef658d
449 changed files with 28551 additions and 167 deletions

View File

@@ -0,0 +1,168 @@
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<OfflineBanner> createState() => _OfflineBannerState();
}
class _OfflineBannerState extends State<OfflineBanner> {
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<bool>(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,
),
),
],
),
),
],
),
);
}
}