chore: füge SyncManager und Peer-Zähler hinzu, verbessere Navigation-Breadcrumbs und passe MD3-Stil an

Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
This commit is contained in:
2026-04-20 15:09:21 +02:00
parent b94e0f2d9d
commit 6feb139a46
2 changed files with 76 additions and 31 deletions
@@ -24,8 +24,9 @@ Die Nachmittags-Session konzentriert sich auf die Bereinigung der App-Start-Sequ
## 📅 Nächste Schritte ## 📅 Nächste Schritte
1. **🔐 Infrastruktur:** Integration des `ConnectivityTracker` zur Visualisierung von Backend-/DB-/Auth-Status (Plug-and-Play konform umgesetzt ✓). 1. **🔐 Infrastruktur:** Integration des `ConnectivityTracker` zur Visualisierung von Backend-/DB-/Auth-Status (Plug-and-Play konform umgesetzt ✓).
2. **📡 Discovery:** Start des `NetworkDiscoveryService` (mDNS) für die automatische Peer-Erkennung im LAN (Plug-and-Play konform umgesetzt ✓). 2. **📡 Discovery:** Start des `NetworkDiscoveryService` (mDNS) für die automatische Peer-Recogniton im LAN (Plug-and-Play konform umgesetzt ✓).
3. **🗺️ Layout:** Finalisierung der `Navigation-Rail` und des `Sync-Indikators`. 3. **🗺️ Layout:** Finalisierung der `Navigation-Rail` (MD3-Refinement), des `Sync-Indikators` (WebSocket-Peer-Zähler) und der navigierbaren `Breadcrumbs` (✓).
4. **🏟️ Fachlicher Einstieg:** `VeranstaltungVerwaltung` als Default-Landeseite nach Onboarding validiert (✓).
--- ---
*Dokumentation erstellt durch den Curator im Rahmen des "Meldestelle"-Protokolls.* *Dokumentation erstellt durch den Curator im Rahmen des "Meldestelle"-Protokolls.*
@@ -25,6 +25,7 @@ import at.mocode.frontend.core.domain.zns.ZnsImportProvider
import at.mocode.frontend.core.navigation.AppScreen import at.mocode.frontend.core.navigation.AppScreen
import at.mocode.frontend.core.network.ConnectivityTracker import at.mocode.frontend.core.network.ConnectivityTracker
import at.mocode.frontend.core.network.discovery.NetworkDiscoveryService import at.mocode.frontend.core.network.discovery.NetworkDiscoveryService
import at.mocode.frontend.core.network.sync.SyncManager
import at.mocode.frontend.features.billing.presentation.BillingScreen import at.mocode.frontend.features.billing.presentation.BillingScreen
import at.mocode.frontend.features.billing.presentation.BillingViewModel import at.mocode.frontend.features.billing.presentation.BillingViewModel
import at.mocode.frontend.features.device.initialization.data.local.DeviceInitializationSettingsManager import at.mocode.frontend.features.device.initialization.data.local.DeviceInitializationSettingsManager
@@ -88,6 +89,8 @@ fun DesktopMainLayout(
onLogout: () -> Unit, onLogout: () -> Unit,
isAuthenticated: Boolean = false isAuthenticated: Boolean = false
) { ) {
val syncManager = koinInject<SyncManager>()
val connectedPeers by syncManager.getConnectedPeers().collectAsState(initial = emptyList())
println("[Navigation] Rendering Screen: ${currentScreen::class.simpleName} (Details: $currentScreen)") println("[Navigation] Rendering Screen: ${currentScreen::class.simpleName} (Details: $currentScreen)")
// DeviceInitialization-Daten (On-the-fly geladen oder Default) // DeviceInitialization-Daten (On-the-fly geladen oder Default)
var onboardingSettings by remember { var onboardingSettings by remember {
@@ -118,7 +121,8 @@ fun DesktopMainLayout(
onNavigate = onNavigate, onNavigate = onNavigate,
onBack = onBack, onBack = onBack,
onLogout = onLogout, onLogout = onLogout,
isAuthenticated = isAuthenticated isAuthenticated = isAuthenticated,
connectedPeersCount = connectedPeers.size
) )
Box(modifier = Modifier.weight(1f).fillMaxWidth()) { Box(modifier = Modifier.weight(1f).fillMaxWidth()) {
@@ -150,8 +154,8 @@ private fun DesktopNavRail(
) { ) {
Surface( Surface(
modifier = Modifier.fillMaxHeight().width(Dimens.NavRailWidth), modifier = Modifier.fillMaxHeight().width(Dimens.NavRailWidth),
color = AppColors.NavigationSurface, color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f),
contentColor = AppColors.NavigationContent, tonalElevation = 2.dp
) { ) {
Column( Column(
modifier = Modifier.fillMaxHeight().padding(vertical = Dimens.SpacingM), modifier = Modifier.fillMaxHeight().padding(vertical = Dimens.SpacingM),
@@ -160,7 +164,7 @@ private fun DesktopNavRail(
) { ) {
// App Icon / Logo Platzhalter // App Icon / Logo Platzhalter
Surface( Surface(
modifier = Modifier.size(40.dp), modifier = Modifier.size(40.dp).clickable { onNavigate(AppScreen.VeranstaltungVerwaltung) },
shape = MaterialTheme.shapes.medium, shape = MaterialTheme.shapes.medium,
color = MaterialTheme.colorScheme.primary color = MaterialTheme.colorScheme.primary
) { ) {
@@ -283,8 +287,8 @@ private fun NavRailItem(
enabled: Boolean = true enabled: Boolean = true
) { ) {
val contentAlpha = if (enabled) 1.0f else 0.38f val contentAlpha = if (enabled) 1.0f else 0.38f
val tint = if (selected) MaterialTheme.colorScheme.primary else AppColors.NavigationContent.copy(alpha = contentAlpha) val tint = if (selected) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = contentAlpha)
val background = if (selected) MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.5f) else Color.Transparent val background = if (selected) MaterialTheme.colorScheme.primaryContainer else Color.Transparent
TooltipBox( TooltipBox(
positionProvider = TooltipDefaults.rememberTooltipPositionProvider( positionProvider = TooltipDefaults.rememberTooltipPositionProvider(
@@ -328,7 +332,8 @@ private fun DesktopTopHeader(
onNavigate: (AppScreen) -> Unit, onNavigate: (AppScreen) -> Unit,
onBack: () -> Unit, onBack: () -> Unit,
onLogout: () -> Unit, onLogout: () -> Unit,
isAuthenticated: Boolean isAuthenticated: Boolean,
connectedPeersCount: Int = 0
) { ) {
Surface( Surface(
modifier = Modifier.fillMaxWidth().height(Dimens.TopBarHeight), modifier = Modifier.fillMaxWidth().height(Dimens.TopBarHeight),
@@ -361,6 +366,31 @@ private fun DesktopTopHeader(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(Dimens.SpacingM) horizontalArrangement = Arrangement.spacedBy(Dimens.SpacingM)
) { ) {
// Sync-Status Indikator
val syncColor = if (connectedPeersCount > 0) AppColors.Success else MaterialTheme.colorScheme.outline
val syncText = if (connectedPeersCount > 0) "$connectedPeersCount Peer(s)" else "Offline"
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(Dimens.SpacingS),
modifier = Modifier
.background(syncColor.copy(alpha = 0.1f), MaterialTheme.shapes.small)
.padding(horizontal = Dimens.SpacingS, vertical = 4.dp)
) {
Surface(
modifier = Modifier.size(8.dp),
shape = MaterialTheme.shapes.extraSmall,
color = syncColor
) {}
Text(
text = syncText,
style = MaterialTheme.typography.labelSmall,
color = syncColor
)
}
VerticalDivider(modifier = Modifier.height(16.dp), thickness = 1.dp, color = MaterialTheme.colorScheme.outlineVariant)
// Profil / Logout Bereich // Profil / Logout Bereich
if (isAuthenticated) { if (isAuthenticated) {
Text( Text(
@@ -460,19 +490,39 @@ private fun BreadcrumbContent(
) )
} }
is AppScreen.VeranstaltungVerwaltung -> {
BreadcrumbSeparator()
Text(
text = "Veranstaltungs-Verwaltung",
style = MaterialTheme.typography.bodyMedium.copy(fontWeight = FontWeight.SemiBold),
)
}
is AppScreen.VeranstaltungDetail -> { is AppScreen.VeranstaltungDetail -> {
BreadcrumbSeparator()
Text(
text = "Veranstaltungs-Verwaltung",
style = MaterialTheme.typography.bodyMedium.copy(color = MaterialTheme.colorScheme.primary.copy(alpha = 0.7f)),
modifier = Modifier.clickable { onNavigate(AppScreen.VeranstaltungVerwaltung) },
)
BreadcrumbSeparator() BreadcrumbSeparator()
Text( Text(
text = "Veranstaltung #${currentScreen.id}", text = "Veranstaltung #${currentScreen.id}",
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium.copy(fontWeight = FontWeight.SemiBold),
) )
} }
is AppScreen.VeranstaltungNeu -> { is AppScreen.VeranstaltungNeu -> {
BreadcrumbSeparator()
Text(
text = "Veranstaltungs-Verwaltung",
style = MaterialTheme.typography.bodyMedium.copy(color = MaterialTheme.colorScheme.primary.copy(alpha = 0.7f)),
modifier = Modifier.clickable { onNavigate(AppScreen.VeranstaltungVerwaltung) },
)
BreadcrumbSeparator() BreadcrumbSeparator()
Text( Text(
text = "Neue Veranstaltung", text = "Neue Veranstaltung",
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium.copy(fontWeight = FontWeight.SemiBold),
) )
} }
@@ -480,14 +530,14 @@ private fun BreadcrumbContent(
BreadcrumbSeparator() BreadcrumbSeparator()
Text( Text(
text = "Veranstaltung #${currentScreen.veranstaltungId}", text = "Veranstaltung #${currentScreen.veranstaltungId}",
style = MaterialTheme.typography.bodyMedium.copy(color = MaterialTheme.colorScheme.onPrimary.copy(alpha = 0.75f)), style = MaterialTheme.typography.bodyMedium.copy(color = MaterialTheme.colorScheme.primary.copy(alpha = 0.7f)),
modifier = Modifier.clickable { modifier = Modifier.clickable {
onNavigate(AppScreen.VeranstaltungDetail(currentScreen.veranstaltungId)) onNavigate(AppScreen.VeranstaltungDetail(currentScreen.veranstaltungId))
}, },
) )
BreadcrumbSeparator() BreadcrumbSeparator()
Text( Text(
text = "Turnier ${currentScreen.turnierId}", text = "Turnier #${currentScreen.turnierId}",
style = MaterialTheme.typography.bodyMedium.copy(fontWeight = FontWeight.SemiBold), style = MaterialTheme.typography.bodyMedium.copy(fontWeight = FontWeight.SemiBold),
) )
} }
@@ -496,17 +546,9 @@ private fun BreadcrumbContent(
BreadcrumbSeparator() BreadcrumbSeparator()
Text( Text(
text = "Veranstaltung #${currentScreen.veranstaltungId}", text = "Veranstaltung #${currentScreen.veranstaltungId}",
style = MaterialTheme.typography.bodyMedium.copy(color = MaterialTheme.colorScheme.onPrimary.copy(alpha = 0.75f)), style = MaterialTheme.typography.bodyMedium.copy(color = MaterialTheme.colorScheme.primary.copy(alpha = 0.7f)),
modifier = Modifier.clickable { modifier = Modifier.clickable {
onNavigate(AppScreen.VeranstaltungProfil(0, currentScreen.veranstaltungId)) onNavigate(AppScreen.VeranstaltungDetail(currentScreen.veranstaltungId))
},
)
BreadcrumbSeparator()
Text(
text = "Turnier ${currentScreen.turnierId}",
style = MaterialTheme.typography.bodyMedium.copy(color = MaterialTheme.colorScheme.onPrimary.copy(alpha = 0.75f)),
modifier = Modifier.clickable {
onNavigate(AppScreen.TurnierDetail(currentScreen.veranstaltungId, currentScreen.turnierId))
}, },
) )
BreadcrumbSeparator() BreadcrumbSeparator()
@@ -520,7 +562,7 @@ private fun BreadcrumbContent(
BreadcrumbSeparator() BreadcrumbSeparator()
Text( Text(
text = "Veranstaltung #${currentScreen.veranstaltungId}", text = "Veranstaltung #${currentScreen.veranstaltungId}",
style = MaterialTheme.typography.bodyMedium.copy(color = MaterialTheme.colorScheme.onPrimary.copy(alpha = 0.75f)), style = MaterialTheme.typography.bodyMedium.copy(color = MaterialTheme.colorScheme.primary.copy(alpha = 0.7f)),
modifier = Modifier.clickable { modifier = Modifier.clickable {
onNavigate(AppScreen.VeranstaltungDetail(currentScreen.veranstaltungId)) onNavigate(AppScreen.VeranstaltungDetail(currentScreen.veranstaltungId))
}, },
@@ -528,7 +570,7 @@ private fun BreadcrumbContent(
BreadcrumbSeparator() BreadcrumbSeparator()
Text( Text(
text = "Neues Turnier", text = "Neues Turnier",
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium.copy(fontWeight = FontWeight.SemiBold),
) )
} }
@@ -1072,18 +1114,20 @@ private fun StatusIndicator(
label: String, label: String,
color: Color color: Color
) { ) {
Row(verticalAlignment = Alignment.CenterVertically) { Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(4.dp)
) {
Icon( Icon(
imageVector = icon, imageVector = icon,
contentDescription = null, contentDescription = null,
tint = color, tint = color.copy(alpha = 0.8f),
modifier = Modifier.size(Dimens.IconSizeS) modifier = Modifier.size(14.dp)
) )
Spacer(Modifier.width(Dimens.SpacingXS))
Text( Text(
text = label, text = label,
style = MaterialTheme.typography.labelSmall, style = MaterialTheme.typography.labelSmall.copy(fontSize = 10.sp),
color = MaterialTheme.colorScheme.onSurface color = MaterialTheme.colorScheme.onSurfaceVariant
) )
} }
} }