feat: füge ConnectivityTracker hinzu, erweitere networkModule, aktualisiere DesktopFooterBar mit Gerätestatus und mDNS-Discovery

Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
This commit is contained in:
2026-04-16 00:00:11 +02:00
parent 67d7b38d79
commit 7581f15dfb
3 changed files with 111 additions and 25 deletions
@@ -21,6 +21,8 @@ import at.mocode.desktop.screens.onboarding.SettingsManager
import at.mocode.frontend.core.designsystem.theme.AppColors
import at.mocode.frontend.core.designsystem.theme.Dimens
import at.mocode.frontend.core.navigation.AppScreen
import at.mocode.frontend.core.network.ConnectivityTracker
import at.mocode.frontend.core.network.discovery.NetworkDiscoveryService
import at.mocode.frontend.features.billing.presentation.BillingScreen
import at.mocode.frontend.features.billing.presentation.BillingViewModel
import at.mocode.frontend.features.nennung.presentation.NennungViewModel
@@ -40,8 +42,10 @@ import at.mocode.turnier.feature.presentation.TurnierDetailScreen
import at.mocode.veranstaltung.feature.presentation.AdminUebersichtScreen
import at.mocode.veranstaltung.feature.presentation.VeranstaltungDetailScreen
import at.mocode.veranstaltung.feature.presentation.VeranstaltungNeuScreen
import kotlinx.coroutines.delay
import org.koin.compose.koinInject
import org.koin.compose.viewmodel.koinViewModel
import kotlin.time.Duration.Companion.milliseconds
// Primärfarbe der TopBar (kann später ins Theme ausgelagert werden)
private val TopBarColor = Color(0xFF1E3A8A)
@@ -91,7 +95,7 @@ fun DesktopMainLayout(
}
HorizontalDivider(thickness = Dimens.BorderThin, color = MaterialTheme.colorScheme.outlineVariant)
DesktopFooterBar()
DesktopFooterBar(settings = onboardingSettings)
}
}
}
@@ -249,7 +253,10 @@ private fun DesktopTopHeader(
BreadcrumbContent(currentScreen, onNavigate)
}
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(Dimens.SpacingM)) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(Dimens.SpacingM)
) {
// Profil / Logout Bereich
Text(
text = "Administrator",
@@ -296,6 +303,7 @@ private fun BreadcrumbContent(
style = MaterialTheme.typography.bodyMedium.copy(fontWeight = FontWeight.SemiBold),
)
}
is AppScreen.VeranstalterDetail -> {
BreadcrumbSeparator()
Text(
@@ -309,6 +317,7 @@ private fun BreadcrumbContent(
style = MaterialTheme.typography.bodyMedium.copy(fontWeight = FontWeight.SemiBold),
)
}
is AppScreen.VeranstaltungProfil -> {
BreadcrumbSeparator()
Text(
@@ -330,6 +339,7 @@ private fun BreadcrumbContent(
style = MaterialTheme.typography.bodyMedium.copy(fontWeight = FontWeight.SemiBold),
)
}
is AppScreen.VeranstaltungDetail -> {
BreadcrumbSeparator()
Text(
@@ -337,6 +347,7 @@ private fun BreadcrumbContent(
style = MaterialTheme.typography.bodyMedium,
)
}
is AppScreen.VeranstaltungNeu -> {
BreadcrumbSeparator()
Text(
@@ -344,6 +355,7 @@ private fun BreadcrumbContent(
style = MaterialTheme.typography.bodyMedium,
)
}
is AppScreen.TurnierDetail -> {
BreadcrumbSeparator()
Text(
@@ -359,6 +371,7 @@ private fun BreadcrumbContent(
style = MaterialTheme.typography.bodyMedium.copy(fontWeight = FontWeight.SemiBold),
)
}
is AppScreen.Billing -> {
BreadcrumbSeparator()
Text(
@@ -382,6 +395,7 @@ private fun BreadcrumbContent(
style = MaterialTheme.typography.bodyMedium.copy(fontWeight = FontWeight.SemiBold),
)
}
is AppScreen.TurnierNeu -> {
BreadcrumbSeparator()
Text(
@@ -413,6 +427,7 @@ private fun BreadcrumbContent(
style = MaterialTheme.typography.bodyMedium.copy(fontWeight = FontWeight.SemiBold),
)
}
is AppScreen.Meisterschaften -> {
BreadcrumbSeparator()
Text(
@@ -420,6 +435,7 @@ private fun BreadcrumbContent(
style = MaterialTheme.typography.bodyMedium.copy(fontWeight = FontWeight.SemiBold),
)
}
is AppScreen.Cups -> {
BreadcrumbSeparator()
Text(
@@ -427,21 +443,22 @@ private fun BreadcrumbContent(
style = MaterialTheme.typography.bodyMedium.copy(fontWeight = FontWeight.SemiBold),
)
}
else -> {}
}
}
// Hilfsfunktion: OEPS-Bundeslandcode → Abkürzung
private fun mapOepsToBundesland(code: String): String = when (code.uppercase()) {
"OOE" -> "OÖ"
"NOE" -> "NÖ"
"ST" -> "Stmk."
"" -> "Oberösterreich"
"" -> "Niederösterreich"
"ST" -> "Steiermark"
"W" -> "Wien"
"BGLD", "B" -> "Bgld."
"K" -> "Ktn."
"S" -> "Sbg."
"B" -> "Burgenland"
"K" -> "Kärnten"
"S" -> "Salzburg"
"T" -> "Tirol"
"V" -> "Vbg."
"V" -> "Vorarlberg"
else -> code
}
@@ -621,6 +638,7 @@ private fun DesktopContentArea(
onCancel = onBack,
onVereinCreated = { newId -> onNavigate(AppScreen.VeranstalterProfil(newId)) }
)
is AppScreen.VeranstalterDetail -> {
val vId = currentScreen.veranstalterId
if (vId != 1L) { // Temporärer Check für Mock-Daten
@@ -637,6 +655,7 @@ private fun DesktopContentArea(
)
}
}
is AppScreen.VeranstaltungKonfig -> {
val vId = currentScreen.veranstalterId
// Falls vId == 0, kommen wir aus der Gesamtübersicht und wählen erst im Wizard
@@ -694,7 +713,8 @@ private fun DesktopContentArea(
val v = at.mocode.desktop.v2.StoreV2.vereine.firstOrNull { vv ->
at.mocode.desktop.v2.StoreV2.eventsFor(vv.id).any { it.id == currentScreen.id }
}
val veranstaltung = v?.let { at.mocode.desktop.v2.StoreV2.eventsFor(it.id).firstOrNull { e -> e.id == currentScreen.id } }
val veranstaltung =
v?.let { at.mocode.desktop.v2.StoreV2.eventsFor(it.id).firstOrNull { e -> e.id == currentScreen.id } }
val list = at.mocode.desktop.v2.TurnierStoreV2.list(currentScreen.id)
val newId = (list.maxOfOrNull { it.id } ?: 0L) + 1L
val draft = at.mocode.desktop.v2.TurnierV2(
@@ -709,6 +729,7 @@ private fun DesktopContentArea(
},
onTurnierOeffnen = { tid -> onNavigate(AppScreen.TurnierDetail(currentScreen.id, tid)) },
)
is AppScreen.VeranstaltungNeu -> VeranstaltungNeuScreen(
onBack = onBack,
onSave = { onBack() },
@@ -743,6 +764,7 @@ private fun DesktopContentArea(
)
}
}
is AppScreen.TurnierNeu -> {
val evtId = currentScreen.veranstaltungId
// V2: Wir erlauben Turnier-Nr nur, wenn die Veranstaltung im V2-Store existiert
@@ -800,11 +822,11 @@ private fun DesktopContentArea(
}
is AppScreen.Meisterschaften -> {
SeriesScreen(title = "Meisterschaften", onBack = onBack)
SeriesScreen(title = "Meisterschaften", onBack = onBack)
}
is AppScreen.Cups -> {
SeriesScreen(title = "Cups", onBack = onBack)
SeriesScreen(title = "Cups", onBack = onBack)
}
is AppScreen.Nennung -> {
@@ -832,11 +854,22 @@ private fun DesktopContentArea(
}
@Composable
private fun DesktopFooterBar() {
// Echte Status-Logik vorbereitet
val online = remember { mutableStateOf(true) }
val deviceConnected = remember { mutableStateOf(true) }
val deviceName = "Richter-Turm"
private fun DesktopFooterBar(settings: OnboardingSettings) {
val connectivityTracker = koinInject<ConnectivityTracker>()
val discoveryService = koinInject<NetworkDiscoveryService>()
val online by connectivityTracker.isOnline.collectAsState()
val discoveredServices = remember { mutableStateOf(discoveryService.getDiscoveredServices()) }
val deviceName = settings.geraetName.ifBlank { "Unbekannt" }
// Periodisches Update der LAN-Geräte (mDNS)
LaunchedEffect(Unit) {
discoveryService.startDiscovery()
while (true) {
discoveredServices.value = discoveryService.getDiscoveredServices()
delay(5000.milliseconds)
}
}
Surface(
color = MaterialTheme.colorScheme.surface,
@@ -854,18 +887,19 @@ private fun DesktopFooterBar() {
Row(verticalAlignment = Alignment.CenterVertically) {
// Status: Cloud Sync
StatusIndicator(
icon = if (online.value) Icons.Filled.CloudDone else Icons.Filled.CloudOff,
label = if (online.value) "Cloud synchronisiert" else "Offline (Lokal)",
color = if (online.value) Color(0xFF2E7D32) else MaterialTheme.colorScheme.error
icon = if (online) Icons.Filled.CloudDone else Icons.Filled.CloudOff,
label = if (online) "Cloud synchronisiert" else "Offline (Lokal)",
color = if (online) Color(0xFF2E7D32) else MaterialTheme.colorScheme.error
)
Spacer(Modifier.width(Dimens.SpacingM))
// Status: LAN Devices (mDNS)
val deviceCount = discoveredServices.value.size
StatusIndicator(
icon = Icons.Filled.Lan,
label = if (deviceConnected.value) "Verbunden: $deviceName" else "Suche nach Geräten...",
color = if (deviceConnected.value) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.outline
label = if (deviceCount > 0) "Verbunden: $deviceName ($deviceCount im Netz)" else "Lokal: $deviceName",
color = if (deviceCount > 0) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.outline
)
}