diff --git a/docs/04_Agents/Journal/2026-04-20_Session-Log_Onboarding.md b/docs/04_Agents/Journal/2026-04-20_Session-Log_Onboarding.md index b74ac2d3..90b76b33 100644 --- a/docs/04_Agents/Journal/2026-04-20_Session-Log_Onboarding.md +++ b/docs/04_Agents/Journal/2026-04-20_Session-Log_Onboarding.md @@ -24,8 +24,9 @@ Die Nachmittags-Session konzentriert sich auf die Bereinigung der App-Start-Sequ ## 📅 Nächste Schritte 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 ✓). -3. **🗺️ Layout:** Finalisierung der `Navigation-Rail` und des `Sync-Indikators`. +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` (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.* diff --git a/frontend/shells/meldestelle-desktop/src/jvmMain/kotlin/at/mocode/frontend/shell/desktop/screens/layout/DesktopMainLayout.kt b/frontend/shells/meldestelle-desktop/src/jvmMain/kotlin/at/mocode/frontend/shell/desktop/screens/layout/DesktopMainLayout.kt index 6e2fafcb..441c2f87 100644 --- a/frontend/shells/meldestelle-desktop/src/jvmMain/kotlin/at/mocode/frontend/shell/desktop/screens/layout/DesktopMainLayout.kt +++ b/frontend/shells/meldestelle-desktop/src/jvmMain/kotlin/at/mocode/frontend/shell/desktop/screens/layout/DesktopMainLayout.kt @@ -25,6 +25,7 @@ import at.mocode.frontend.core.domain.zns.ZnsImportProvider 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.core.network.sync.SyncManager import at.mocode.frontend.features.billing.presentation.BillingScreen import at.mocode.frontend.features.billing.presentation.BillingViewModel import at.mocode.frontend.features.device.initialization.data.local.DeviceInitializationSettingsManager @@ -88,6 +89,8 @@ fun DesktopMainLayout( onLogout: () -> Unit, isAuthenticated: Boolean = false ) { + val syncManager = koinInject() + val connectedPeers by syncManager.getConnectedPeers().collectAsState(initial = emptyList()) println("[Navigation] Rendering Screen: ${currentScreen::class.simpleName} (Details: $currentScreen)") // DeviceInitialization-Daten (On-the-fly geladen oder Default) var onboardingSettings by remember { @@ -118,7 +121,8 @@ fun DesktopMainLayout( onNavigate = onNavigate, onBack = onBack, onLogout = onLogout, - isAuthenticated = isAuthenticated + isAuthenticated = isAuthenticated, + connectedPeersCount = connectedPeers.size ) Box(modifier = Modifier.weight(1f).fillMaxWidth()) { @@ -150,8 +154,8 @@ private fun DesktopNavRail( ) { Surface( modifier = Modifier.fillMaxHeight().width(Dimens.NavRailWidth), - color = AppColors.NavigationSurface, - contentColor = AppColors.NavigationContent, + color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f), + tonalElevation = 2.dp ) { Column( modifier = Modifier.fillMaxHeight().padding(vertical = Dimens.SpacingM), @@ -160,7 +164,7 @@ private fun DesktopNavRail( ) { // App Icon / Logo Platzhalter Surface( - modifier = Modifier.size(40.dp), + modifier = Modifier.size(40.dp).clickable { onNavigate(AppScreen.VeranstaltungVerwaltung) }, shape = MaterialTheme.shapes.medium, color = MaterialTheme.colorScheme.primary ) { @@ -283,8 +287,8 @@ private fun NavRailItem( enabled: Boolean = true ) { val contentAlpha = if (enabled) 1.0f else 0.38f - val tint = if (selected) MaterialTheme.colorScheme.primary else AppColors.NavigationContent.copy(alpha = contentAlpha) - val background = if (selected) MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.5f) else Color.Transparent + val tint = if (selected) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = contentAlpha) + val background = if (selected) MaterialTheme.colorScheme.primaryContainer else Color.Transparent TooltipBox( positionProvider = TooltipDefaults.rememberTooltipPositionProvider( @@ -328,7 +332,8 @@ private fun DesktopTopHeader( onNavigate: (AppScreen) -> Unit, onBack: () -> Unit, onLogout: () -> Unit, - isAuthenticated: Boolean + isAuthenticated: Boolean, + connectedPeersCount: Int = 0 ) { Surface( modifier = Modifier.fillMaxWidth().height(Dimens.TopBarHeight), @@ -361,6 +366,31 @@ private fun DesktopTopHeader( verticalAlignment = Alignment.CenterVertically, 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 if (isAuthenticated) { 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 -> { + 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() Text( text = "Veranstaltung #${currentScreen.id}", - style = MaterialTheme.typography.bodyMedium, + style = MaterialTheme.typography.bodyMedium.copy(fontWeight = FontWeight.SemiBold), ) } 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() Text( text = "Neue Veranstaltung", - style = MaterialTheme.typography.bodyMedium, + style = MaterialTheme.typography.bodyMedium.copy(fontWeight = FontWeight.SemiBold), ) } @@ -480,14 +530,14 @@ private fun BreadcrumbContent( BreadcrumbSeparator() Text( 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 { onNavigate(AppScreen.VeranstaltungDetail(currentScreen.veranstaltungId)) }, ) BreadcrumbSeparator() Text( - text = "Turnier ${currentScreen.turnierId}", + text = "Turnier #${currentScreen.turnierId}", style = MaterialTheme.typography.bodyMedium.copy(fontWeight = FontWeight.SemiBold), ) } @@ -496,17 +546,9 @@ private fun BreadcrumbContent( BreadcrumbSeparator() Text( 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 { - onNavigate(AppScreen.VeranstaltungProfil(0, 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)) + onNavigate(AppScreen.VeranstaltungDetail(currentScreen.veranstaltungId)) }, ) BreadcrumbSeparator() @@ -520,7 +562,7 @@ private fun BreadcrumbContent( BreadcrumbSeparator() Text( 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 { onNavigate(AppScreen.VeranstaltungDetail(currentScreen.veranstaltungId)) }, @@ -528,7 +570,7 @@ private fun BreadcrumbContent( BreadcrumbSeparator() Text( text = "Neues Turnier", - style = MaterialTheme.typography.bodyMedium, + style = MaterialTheme.typography.bodyMedium.copy(fontWeight = FontWeight.SemiBold), ) } @@ -1072,18 +1114,20 @@ private fun StatusIndicator( label: String, color: Color ) { - Row(verticalAlignment = Alignment.CenterVertically) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(4.dp) + ) { Icon( imageVector = icon, contentDescription = null, - tint = color, - modifier = Modifier.size(Dimens.IconSizeS) + tint = color.copy(alpha = 0.8f), + modifier = Modifier.size(14.dp) ) - Spacer(Modifier.width(Dimens.SpacingXS)) Text( text = label, - style = MaterialTheme.typography.labelSmall, - color = MaterialTheme.colorScheme.onSurface + style = MaterialTheme.typography.labelSmall.copy(fontSize = 10.sp), + color = MaterialTheme.colorScheme.onSurfaceVariant ) } }