chore: implementiere Auth-Status-abhängige Navigation und Icons, deaktiviere Module ohne Initialisierung und passe NavRail sowie Header für besseren UX an
Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
---
|
||||
type: Journal
|
||||
status: FINAL
|
||||
owner: Curator
|
||||
date: 2026-04-20
|
||||
---
|
||||
|
||||
# Session Log – Finalisierung Onboarding & Start-Sequenz (Phase 13)
|
||||
|
||||
## 🏗️ Status-Update
|
||||
Die Nachmittags-Session konzentriert sich auf die Bereinigung der App-Start-Sequenz nach dem **ADR-0024 Plug-and-Play Pattern**. Der erste Meilenstein (Onboarding) wurde erfolgreich abgeschlossen.
|
||||
|
||||
## 🛠️ Umfang & Änderungen (Punkt 1: Onboarding)
|
||||
- **Sidebar-Blocking:** Fachliche Module (`ZNS-Import`, `Stammdaten`, `Nennungen`) werden nun deaktiviert, solange das Gerät nicht initialisiert ist. Dies verhindert inkonsistente Zustände vor der Namens-/Key-Vergabe.
|
||||
- **Client-Datensicherheit:** Der `backupPath` in der `settings.json` ist nun für **alle** Netzwerk-Rollen (Master & Client) verpflichtend. Dies stellt sicher, dass auch dezentrale Workstations (z.B. Richterturm) im Offline-Fall lokale Snapshots sichern.
|
||||
- **Navigations-Fix:** Die "Sackgasse" im Login-Screen wurde behoben. Der Zurück-Button führt nun via `navigateBack()` korrekt zum vorherigen Kontext.
|
||||
- **Dynamischer Header:** Der Header unterscheidet nun visuell zwischen "Gast" (nicht eingeloggt) und "Administrator" (eingeloggt), inklusive passender Login/Logout-Icons.
|
||||
- **Setup-UX:** Einführung eines dedizierten Abschluss-Buttons für die `Client`-Initialisierung, um den Workflow für Nicht-Master-Geräte zu straffen.
|
||||
|
||||
## 📐 Architektur-Check (ADR-0024)
|
||||
- **Kapselung:** Die Logik verbleibt im `device-initialization` Modul.
|
||||
- **Hoisting:** Navigations-States werden sauber an die Shell (`meldestelle-desktop`) delegiert.
|
||||
- **Konformität:** Alle Änderungen unterstützen das Ziel einer autarken, offline-fähigen Workstation.
|
||||
|
||||
## 📅 Nächste Schritte
|
||||
1. **🔐 Infrastruktur:** Integration des `ConnectivityTracker` zur Visualisierung von Backend-/DB-/Auth-Status.
|
||||
2. **📡 Discovery:** Start des `NetworkDiscoveryService` (mDNS) für die automatische Peer-Erkennung im LAN.
|
||||
3. **🗺️ Layout:** Finalisierung der `Navigation-Rail` und des `Sync-Indikators`.
|
||||
|
||||
---
|
||||
*Dokumentation erstellt durch den Curator im Rahmen des "Meldestelle"-Protokolls.*
|
||||
+15
-10
@@ -72,23 +72,15 @@ actual fun DeviceInitializationConfig(
|
||||
isError = settings.sharedKey.isNotEmpty() && !DeviceInitializationValidator.isKeyValid(settings.sharedKey),
|
||||
errorMessage = "Mindestens ${DeviceInitializationValidator.MIN_KEY_LENGTH} Zeichen erforderlich.",
|
||||
visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(),
|
||||
imeAction = if (settings.networkRole == NetworkRole.MASTER) ImeAction.Next else ImeAction.Done,
|
||||
imeAction = ImeAction.Next,
|
||||
keyboardActions = KeyboardActions(
|
||||
onNext = { focusManager.moveFocus(FocusDirection.Next) },
|
||||
onDone = {
|
||||
if (DeviceInitializationValidator.canContinue(settings)) {
|
||||
viewModel.completeInitialization()
|
||||
} else {
|
||||
focusManager.clearFocus()
|
||||
}
|
||||
}
|
||||
onNext = { focusManager.moveFocus(FocusDirection.Next) }
|
||||
),
|
||||
modifier = Modifier.focusRequester(sharedKeyFocus),
|
||||
trailingIcon = if (passwordVisible) Icons.Outlined.VisibilityOff else Icons.Outlined.Visibility,
|
||||
onTrailingIconClick = { passwordVisible = !passwordVisible }
|
||||
)
|
||||
|
||||
if (settings.networkRole == NetworkRole.MASTER) {
|
||||
MsFilePicker(
|
||||
label = "Backup-Verzeichnis (Pfad)",
|
||||
selectedPath = settings.backupPath,
|
||||
@@ -99,6 +91,7 @@ actual fun DeviceInitializationConfig(
|
||||
modifier = Modifier.focusRequester(backupPathFocus)
|
||||
)
|
||||
|
||||
if (settings.networkRole == NetworkRole.MASTER) {
|
||||
Text("Sync-Intervall: ${settings.syncInterval} Min.", style = MaterialTheme.typography.labelMedium)
|
||||
Slider(
|
||||
value = settings.syncInterval.toFloat(),
|
||||
@@ -106,7 +99,19 @@ actual fun DeviceInitializationConfig(
|
||||
valueRange = 1f..60f,
|
||||
steps = 59
|
||||
)
|
||||
} else {
|
||||
// Button zum Abschließen für Clients, da diese keinen Slider/Clients haben
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Button(
|
||||
onClick = { viewModel.completeInitialization() },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
enabled = DeviceInitializationValidator.canContinue(settings)
|
||||
) {
|
||||
Text("Konfiguration abschließen")
|
||||
}
|
||||
}
|
||||
|
||||
if (settings.networkRole == NetworkRole.MASTER) {
|
||||
HorizontalDivider(Modifier, DividerDefaults.Thickness, DividerDefaults.color)
|
||||
Text("👥 Erwartete Clients", style = MaterialTheme.typography.titleSmall)
|
||||
|
||||
|
||||
+4
-3
@@ -8,14 +8,14 @@ import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import at.mocode.frontend.shell.desktop.navigation.DesktopNavigationPort
|
||||
import at.mocode.frontend.shell.desktop.screens.layout.DesktopMainLayout
|
||||
import at.mocode.frontend.core.auth.data.local.AuthTokenManager
|
||||
import at.mocode.frontend.core.auth.presentation.LoginScreen
|
||||
import at.mocode.frontend.core.auth.presentation.LoginViewModel
|
||||
import at.mocode.frontend.core.designsystem.theme.AppTheme
|
||||
import at.mocode.frontend.core.navigation.AppScreen
|
||||
import at.mocode.frontend.features.device.initialization.data.local.DeviceInitializationSettingsManager
|
||||
import at.mocode.frontend.shell.desktop.navigation.DesktopNavigationPort
|
||||
import at.mocode.frontend.shell.desktop.screens.layout.DesktopMainLayout
|
||||
import org.koin.compose.koinInject
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
|
||||
@@ -72,7 +72,7 @@ fun DesktopApp() {
|
||||
val returnTo = screen.returnTo ?: AppScreen.VeranstaltungVerwaltung
|
||||
nav.navigateToScreen(returnTo)
|
||||
},
|
||||
onBack = { /* Desktop hat keine PortalDashboard-Page */ },
|
||||
onBack = { nav.navigateBack() },
|
||||
)
|
||||
|
||||
else -> {
|
||||
@@ -85,6 +85,7 @@ fun DesktopApp() {
|
||||
authTokenManager.clearToken()
|
||||
nav.navigateToScreen(AppScreen.Login(returnTo = AppScreen.VeranstaltungVerwaltung))
|
||||
},
|
||||
isAuthenticated = authState.isAuthenticated
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+42
-12
@@ -5,6 +5,7 @@ import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.automirrored.filled.Login
|
||||
import androidx.compose.material.icons.automirrored.filled.Logout
|
||||
import androidx.compose.material.icons.filled.*
|
||||
import androidx.compose.material3.*
|
||||
@@ -85,6 +86,7 @@ fun DesktopMainLayout(
|
||||
onNavigate: (AppScreen) -> Unit,
|
||||
onBack: () -> Unit,
|
||||
onLogout: () -> Unit,
|
||||
isAuthenticated: Boolean = false
|
||||
) {
|
||||
println("[Navigation] Rendering Screen: ${currentScreen::class.simpleName} (Details: $currentScreen)")
|
||||
// DeviceInitialization-Daten (On-the-fly geladen oder Default)
|
||||
@@ -106,7 +108,8 @@ fun DesktopMainLayout(
|
||||
// Navigation Rail (Modernere Seitenleiste)
|
||||
DesktopNavRail(
|
||||
currentScreen = currentScreen,
|
||||
onNavigate = onNavigate
|
||||
onNavigate = onNavigate,
|
||||
isConfigured = onboardingSettings.isConfigured
|
||||
)
|
||||
|
||||
Column(modifier = Modifier.fillMaxSize()) {
|
||||
@@ -115,6 +118,7 @@ fun DesktopMainLayout(
|
||||
onNavigate = onNavigate,
|
||||
onBack = onBack,
|
||||
onLogout = onLogout,
|
||||
isAuthenticated = isAuthenticated
|
||||
)
|
||||
|
||||
Box(modifier = Modifier.weight(1f).fillMaxWidth()) {
|
||||
@@ -141,7 +145,8 @@ fun DesktopMainLayout(
|
||||
@Composable
|
||||
private fun DesktopNavRail(
|
||||
currentScreen: AppScreen,
|
||||
onNavigate: (AppScreen) -> Unit
|
||||
onNavigate: (AppScreen) -> Unit,
|
||||
isConfigured: Boolean
|
||||
) {
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxHeight().width(Dimens.NavRailWidth),
|
||||
@@ -174,14 +179,16 @@ private fun DesktopNavRail(
|
||||
icon = Icons.Default.Dashboard,
|
||||
label = "Admin",
|
||||
selected = currentScreen is AppScreen.VeranstaltungVerwaltung || currentScreen is AppScreen.VeranstaltungDetail,
|
||||
onClick = { onNavigate(AppScreen.VeranstaltungVerwaltung) }
|
||||
onClick = { onNavigate(AppScreen.VeranstaltungVerwaltung) },
|
||||
enabled = isConfigured
|
||||
)
|
||||
|
||||
NavRailItem(
|
||||
icon = Icons.Default.CloudDownload,
|
||||
label = "ZNS-Import",
|
||||
selected = currentScreen is AppScreen.StammdatenImport,
|
||||
onClick = { onNavigate(AppScreen.StammdatenImport) }
|
||||
onClick = { onNavigate(AppScreen.StammdatenImport) },
|
||||
enabled = isConfigured
|
||||
)
|
||||
|
||||
var showStammdatenMenu by remember { mutableStateOf(false) }
|
||||
@@ -193,11 +200,12 @@ private fun DesktopNavRail(
|
||||
currentScreen is AppScreen.Reiter || currentScreen is AppScreen.ReiterVerwaltung ||
|
||||
currentScreen is AppScreen.Pferde || currentScreen is AppScreen.PferdVerwaltung ||
|
||||
currentScreen is AppScreen.FunktionaerVerwaltung,
|
||||
onClick = { showStammdatenMenu = true }
|
||||
onClick = { showStammdatenMenu = true },
|
||||
enabled = isConfigured
|
||||
)
|
||||
|
||||
DropdownMenu(
|
||||
expanded = showStammdatenMenu,
|
||||
expanded = showStammdatenMenu && isConfigured,
|
||||
onDismissRequest = { showStammdatenMenu = false },
|
||||
offset = DpOffset(Dimens.NavRailWidth, 0.dp)
|
||||
) {
|
||||
@@ -240,14 +248,16 @@ private fun DesktopNavRail(
|
||||
icon = Icons.Default.Email,
|
||||
label = "Mails",
|
||||
selected = currentScreen is AppScreen.NennungsEingang,
|
||||
onClick = { onNavigate(AppScreen.NennungsEingang) }
|
||||
onClick = { onNavigate(AppScreen.NennungsEingang) },
|
||||
enabled = isConfigured
|
||||
)
|
||||
|
||||
NavRailItem(
|
||||
icon = Icons.Default.WifiTethering,
|
||||
label = "ConnectivityCheck",
|
||||
selected = currentScreen is AppScreen.ConnectivityCheck,
|
||||
onClick = { onNavigate(AppScreen.ConnectivityCheck) }
|
||||
onClick = { onNavigate(AppScreen.ConnectivityCheck) },
|
||||
enabled = true // Immer aktiv zur Diagnose
|
||||
)
|
||||
|
||||
Spacer(Modifier.weight(1f))
|
||||
@@ -256,7 +266,8 @@ private fun DesktopNavRail(
|
||||
icon = Icons.Default.AppRegistration,
|
||||
label = "Setup",
|
||||
selected = currentScreen is AppScreen.DeviceInitialization,
|
||||
onClick = { onNavigate(AppScreen.DeviceInitialization) }
|
||||
onClick = { onNavigate(AppScreen.DeviceInitialization) },
|
||||
enabled = true
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -268,9 +279,11 @@ private fun NavRailItem(
|
||||
icon: ImageVector,
|
||||
label: String,
|
||||
selected: Boolean,
|
||||
onClick: () -> Unit
|
||||
onClick: () -> Unit,
|
||||
enabled: Boolean = true
|
||||
) {
|
||||
val tint = if (selected) MaterialTheme.colorScheme.primary else AppColors.NavigationContent
|
||||
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
|
||||
|
||||
TooltipBox(
|
||||
@@ -287,7 +300,7 @@ private fun NavRailItem(
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.size(48.dp)
|
||||
.clickable(onClick = onClick),
|
||||
.clickable(enabled = enabled, onClick = onClick),
|
||||
shape = MaterialTheme.shapes.medium,
|
||||
color = background
|
||||
) {
|
||||
@@ -315,6 +328,7 @@ private fun DesktopTopHeader(
|
||||
onNavigate: (AppScreen) -> Unit,
|
||||
onBack: () -> Unit,
|
||||
onLogout: () -> Unit,
|
||||
isAuthenticated: Boolean
|
||||
) {
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxWidth().height(Dimens.TopBarHeight),
|
||||
@@ -348,6 +362,7 @@ private fun DesktopTopHeader(
|
||||
horizontalArrangement = Arrangement.spacedBy(Dimens.SpacingM)
|
||||
) {
|
||||
// Profil / Logout Bereich
|
||||
if (isAuthenticated) {
|
||||
Text(
|
||||
text = "Administrator",
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
@@ -361,6 +376,21 @@ private fun DesktopTopHeader(
|
||||
tint = MaterialTheme.colorScheme.error
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Text(
|
||||
text = "Gast",
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
IconButton(onClick = { onNavigate(AppScreen.Login(returnTo = currentScreen)) }) {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Filled.Login,
|
||||
contentDescription = "Anmelden",
|
||||
modifier = Modifier.size(Dimens.IconSizeM),
|
||||
tint = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user