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.*
|
||||||
+24
-19
@@ -72,33 +72,26 @@ actual fun DeviceInitializationConfig(
|
|||||||
isError = settings.sharedKey.isNotEmpty() && !DeviceInitializationValidator.isKeyValid(settings.sharedKey),
|
isError = settings.sharedKey.isNotEmpty() && !DeviceInitializationValidator.isKeyValid(settings.sharedKey),
|
||||||
errorMessage = "Mindestens ${DeviceInitializationValidator.MIN_KEY_LENGTH} Zeichen erforderlich.",
|
errorMessage = "Mindestens ${DeviceInitializationValidator.MIN_KEY_LENGTH} Zeichen erforderlich.",
|
||||||
visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(),
|
visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(),
|
||||||
imeAction = if (settings.networkRole == NetworkRole.MASTER) ImeAction.Next else ImeAction.Done,
|
imeAction = ImeAction.Next,
|
||||||
keyboardActions = KeyboardActions(
|
keyboardActions = KeyboardActions(
|
||||||
onNext = { focusManager.moveFocus(FocusDirection.Next) },
|
onNext = { focusManager.moveFocus(FocusDirection.Next) }
|
||||||
onDone = {
|
|
||||||
if (DeviceInitializationValidator.canContinue(settings)) {
|
|
||||||
viewModel.completeInitialization()
|
|
||||||
} else {
|
|
||||||
focusManager.clearFocus()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
),
|
),
|
||||||
modifier = Modifier.focusRequester(sharedKeyFocus),
|
modifier = Modifier.focusRequester(sharedKeyFocus),
|
||||||
trailingIcon = if (passwordVisible) Icons.Outlined.VisibilityOff else Icons.Outlined.Visibility,
|
trailingIcon = if (passwordVisible) Icons.Outlined.VisibilityOff else Icons.Outlined.Visibility,
|
||||||
onTrailingIconClick = { passwordVisible = !passwordVisible }
|
onTrailingIconClick = { passwordVisible = !passwordVisible }
|
||||||
)
|
)
|
||||||
|
|
||||||
if (settings.networkRole == NetworkRole.MASTER) {
|
MsFilePicker(
|
||||||
MsFilePicker(
|
label = "Backup-Verzeichnis (Pfad)",
|
||||||
label = "Backup-Verzeichnis (Pfad)",
|
selectedPath = settings.backupPath,
|
||||||
selectedPath = settings.backupPath,
|
onFileSelected = { selectedPath ->
|
||||||
onFileSelected = { selectedPath ->
|
viewModel.updateSettings { s -> s.copy(backupPath = selectedPath) }
|
||||||
viewModel.updateSettings { s -> s.copy(backupPath = selectedPath) }
|
},
|
||||||
},
|
directoryOnly = true,
|
||||||
directoryOnly = true,
|
modifier = Modifier.focusRequester(backupPathFocus)
|
||||||
modifier = Modifier.focusRequester(backupPathFocus)
|
)
|
||||||
)
|
|
||||||
|
|
||||||
|
if (settings.networkRole == NetworkRole.MASTER) {
|
||||||
Text("Sync-Intervall: ${settings.syncInterval} Min.", style = MaterialTheme.typography.labelMedium)
|
Text("Sync-Intervall: ${settings.syncInterval} Min.", style = MaterialTheme.typography.labelMedium)
|
||||||
Slider(
|
Slider(
|
||||||
value = settings.syncInterval.toFloat(),
|
value = settings.syncInterval.toFloat(),
|
||||||
@@ -106,7 +99,19 @@ actual fun DeviceInitializationConfig(
|
|||||||
valueRange = 1f..60f,
|
valueRange = 1f..60f,
|
||||||
steps = 59
|
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)
|
HorizontalDivider(Modifier, DividerDefaults.Thickness, DividerDefaults.color)
|
||||||
Text("👥 Erwartete Clients", style = MaterialTheme.typography.titleSmall)
|
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.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Modifier
|
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.data.local.AuthTokenManager
|
||||||
import at.mocode.frontend.core.auth.presentation.LoginScreen
|
import at.mocode.frontend.core.auth.presentation.LoginScreen
|
||||||
import at.mocode.frontend.core.auth.presentation.LoginViewModel
|
import at.mocode.frontend.core.auth.presentation.LoginViewModel
|
||||||
import at.mocode.frontend.core.designsystem.theme.AppTheme
|
import at.mocode.frontend.core.designsystem.theme.AppTheme
|
||||||
import at.mocode.frontend.core.navigation.AppScreen
|
import at.mocode.frontend.core.navigation.AppScreen
|
||||||
import at.mocode.frontend.features.device.initialization.data.local.DeviceInitializationSettingsManager
|
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.koinInject
|
||||||
import org.koin.compose.viewmodel.koinViewModel
|
import org.koin.compose.viewmodel.koinViewModel
|
||||||
|
|
||||||
@@ -72,7 +72,7 @@ fun DesktopApp() {
|
|||||||
val returnTo = screen.returnTo ?: AppScreen.VeranstaltungVerwaltung
|
val returnTo = screen.returnTo ?: AppScreen.VeranstaltungVerwaltung
|
||||||
nav.navigateToScreen(returnTo)
|
nav.navigateToScreen(returnTo)
|
||||||
},
|
},
|
||||||
onBack = { /* Desktop hat keine PortalDashboard-Page */ },
|
onBack = { nav.navigateBack() },
|
||||||
)
|
)
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
@@ -85,6 +85,7 @@ fun DesktopApp() {
|
|||||||
authTokenManager.clearToken()
|
authTokenManager.clearToken()
|
||||||
nav.navigateToScreen(AppScreen.Login(returnTo = AppScreen.VeranstaltungVerwaltung))
|
nav.navigateToScreen(AppScreen.Login(returnTo = AppScreen.VeranstaltungVerwaltung))
|
||||||
},
|
},
|
||||||
|
isAuthenticated = authState.isAuthenticated
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+53
-23
@@ -5,6 +5,7 @@ import androidx.compose.foundation.clickable
|
|||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
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.automirrored.filled.Logout
|
||||||
import androidx.compose.material.icons.filled.*
|
import androidx.compose.material.icons.filled.*
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
@@ -85,6 +86,7 @@ fun DesktopMainLayout(
|
|||||||
onNavigate: (AppScreen) -> Unit,
|
onNavigate: (AppScreen) -> Unit,
|
||||||
onBack: () -> Unit,
|
onBack: () -> Unit,
|
||||||
onLogout: () -> Unit,
|
onLogout: () -> Unit,
|
||||||
|
isAuthenticated: Boolean = false
|
||||||
) {
|
) {
|
||||||
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)
|
||||||
@@ -106,7 +108,8 @@ fun DesktopMainLayout(
|
|||||||
// Navigation Rail (Modernere Seitenleiste)
|
// Navigation Rail (Modernere Seitenleiste)
|
||||||
DesktopNavRail(
|
DesktopNavRail(
|
||||||
currentScreen = currentScreen,
|
currentScreen = currentScreen,
|
||||||
onNavigate = onNavigate
|
onNavigate = onNavigate,
|
||||||
|
isConfigured = onboardingSettings.isConfigured
|
||||||
)
|
)
|
||||||
|
|
||||||
Column(modifier = Modifier.fillMaxSize()) {
|
Column(modifier = Modifier.fillMaxSize()) {
|
||||||
@@ -115,6 +118,7 @@ fun DesktopMainLayout(
|
|||||||
onNavigate = onNavigate,
|
onNavigate = onNavigate,
|
||||||
onBack = onBack,
|
onBack = onBack,
|
||||||
onLogout = onLogout,
|
onLogout = onLogout,
|
||||||
|
isAuthenticated = isAuthenticated
|
||||||
)
|
)
|
||||||
|
|
||||||
Box(modifier = Modifier.weight(1f).fillMaxWidth()) {
|
Box(modifier = Modifier.weight(1f).fillMaxWidth()) {
|
||||||
@@ -141,7 +145,8 @@ fun DesktopMainLayout(
|
|||||||
@Composable
|
@Composable
|
||||||
private fun DesktopNavRail(
|
private fun DesktopNavRail(
|
||||||
currentScreen: AppScreen,
|
currentScreen: AppScreen,
|
||||||
onNavigate: (AppScreen) -> Unit
|
onNavigate: (AppScreen) -> Unit,
|
||||||
|
isConfigured: Boolean
|
||||||
) {
|
) {
|
||||||
Surface(
|
Surface(
|
||||||
modifier = Modifier.fillMaxHeight().width(Dimens.NavRailWidth),
|
modifier = Modifier.fillMaxHeight().width(Dimens.NavRailWidth),
|
||||||
@@ -174,14 +179,16 @@ private fun DesktopNavRail(
|
|||||||
icon = Icons.Default.Dashboard,
|
icon = Icons.Default.Dashboard,
|
||||||
label = "Admin",
|
label = "Admin",
|
||||||
selected = currentScreen is AppScreen.VeranstaltungVerwaltung || currentScreen is AppScreen.VeranstaltungDetail,
|
selected = currentScreen is AppScreen.VeranstaltungVerwaltung || currentScreen is AppScreen.VeranstaltungDetail,
|
||||||
onClick = { onNavigate(AppScreen.VeranstaltungVerwaltung) }
|
onClick = { onNavigate(AppScreen.VeranstaltungVerwaltung) },
|
||||||
|
enabled = isConfigured
|
||||||
)
|
)
|
||||||
|
|
||||||
NavRailItem(
|
NavRailItem(
|
||||||
icon = Icons.Default.CloudDownload,
|
icon = Icons.Default.CloudDownload,
|
||||||
label = "ZNS-Import",
|
label = "ZNS-Import",
|
||||||
selected = currentScreen is AppScreen.StammdatenImport,
|
selected = currentScreen is AppScreen.StammdatenImport,
|
||||||
onClick = { onNavigate(AppScreen.StammdatenImport) }
|
onClick = { onNavigate(AppScreen.StammdatenImport) },
|
||||||
|
enabled = isConfigured
|
||||||
)
|
)
|
||||||
|
|
||||||
var showStammdatenMenu by remember { mutableStateOf(false) }
|
var showStammdatenMenu by remember { mutableStateOf(false) }
|
||||||
@@ -193,11 +200,12 @@ private fun DesktopNavRail(
|
|||||||
currentScreen is AppScreen.Reiter || currentScreen is AppScreen.ReiterVerwaltung ||
|
currentScreen is AppScreen.Reiter || currentScreen is AppScreen.ReiterVerwaltung ||
|
||||||
currentScreen is AppScreen.Pferde || currentScreen is AppScreen.PferdVerwaltung ||
|
currentScreen is AppScreen.Pferde || currentScreen is AppScreen.PferdVerwaltung ||
|
||||||
currentScreen is AppScreen.FunktionaerVerwaltung,
|
currentScreen is AppScreen.FunktionaerVerwaltung,
|
||||||
onClick = { showStammdatenMenu = true }
|
onClick = { showStammdatenMenu = true },
|
||||||
|
enabled = isConfigured
|
||||||
)
|
)
|
||||||
|
|
||||||
DropdownMenu(
|
DropdownMenu(
|
||||||
expanded = showStammdatenMenu,
|
expanded = showStammdatenMenu && isConfigured,
|
||||||
onDismissRequest = { showStammdatenMenu = false },
|
onDismissRequest = { showStammdatenMenu = false },
|
||||||
offset = DpOffset(Dimens.NavRailWidth, 0.dp)
|
offset = DpOffset(Dimens.NavRailWidth, 0.dp)
|
||||||
) {
|
) {
|
||||||
@@ -240,14 +248,16 @@ private fun DesktopNavRail(
|
|||||||
icon = Icons.Default.Email,
|
icon = Icons.Default.Email,
|
||||||
label = "Mails",
|
label = "Mails",
|
||||||
selected = currentScreen is AppScreen.NennungsEingang,
|
selected = currentScreen is AppScreen.NennungsEingang,
|
||||||
onClick = { onNavigate(AppScreen.NennungsEingang) }
|
onClick = { onNavigate(AppScreen.NennungsEingang) },
|
||||||
|
enabled = isConfigured
|
||||||
)
|
)
|
||||||
|
|
||||||
NavRailItem(
|
NavRailItem(
|
||||||
icon = Icons.Default.WifiTethering,
|
icon = Icons.Default.WifiTethering,
|
||||||
label = "ConnectivityCheck",
|
label = "ConnectivityCheck",
|
||||||
selected = currentScreen is AppScreen.ConnectivityCheck,
|
selected = currentScreen is AppScreen.ConnectivityCheck,
|
||||||
onClick = { onNavigate(AppScreen.ConnectivityCheck) }
|
onClick = { onNavigate(AppScreen.ConnectivityCheck) },
|
||||||
|
enabled = true // Immer aktiv zur Diagnose
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(Modifier.weight(1f))
|
Spacer(Modifier.weight(1f))
|
||||||
@@ -256,7 +266,8 @@ private fun DesktopNavRail(
|
|||||||
icon = Icons.Default.AppRegistration,
|
icon = Icons.Default.AppRegistration,
|
||||||
label = "Setup",
|
label = "Setup",
|
||||||
selected = currentScreen is AppScreen.DeviceInitialization,
|
selected = currentScreen is AppScreen.DeviceInitialization,
|
||||||
onClick = { onNavigate(AppScreen.DeviceInitialization) }
|
onClick = { onNavigate(AppScreen.DeviceInitialization) },
|
||||||
|
enabled = true
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -268,9 +279,11 @@ private fun NavRailItem(
|
|||||||
icon: ImageVector,
|
icon: ImageVector,
|
||||||
label: String,
|
label: String,
|
||||||
selected: Boolean,
|
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
|
val background = if (selected) MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.5f) else Color.Transparent
|
||||||
|
|
||||||
TooltipBox(
|
TooltipBox(
|
||||||
@@ -287,7 +300,7 @@ private fun NavRailItem(
|
|||||||
Surface(
|
Surface(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(48.dp)
|
.size(48.dp)
|
||||||
.clickable(onClick = onClick),
|
.clickable(enabled = enabled, onClick = onClick),
|
||||||
shape = MaterialTheme.shapes.medium,
|
shape = MaterialTheme.shapes.medium,
|
||||||
color = background
|
color = background
|
||||||
) {
|
) {
|
||||||
@@ -315,6 +328,7 @@ private fun DesktopTopHeader(
|
|||||||
onNavigate: (AppScreen) -> Unit,
|
onNavigate: (AppScreen) -> Unit,
|
||||||
onBack: () -> Unit,
|
onBack: () -> Unit,
|
||||||
onLogout: () -> Unit,
|
onLogout: () -> Unit,
|
||||||
|
isAuthenticated: Boolean
|
||||||
) {
|
) {
|
||||||
Surface(
|
Surface(
|
||||||
modifier = Modifier.fillMaxWidth().height(Dimens.TopBarHeight),
|
modifier = Modifier.fillMaxWidth().height(Dimens.TopBarHeight),
|
||||||
@@ -348,18 +362,34 @@ private fun DesktopTopHeader(
|
|||||||
horizontalArrangement = Arrangement.spacedBy(Dimens.SpacingM)
|
horizontalArrangement = Arrangement.spacedBy(Dimens.SpacingM)
|
||||||
) {
|
) {
|
||||||
// Profil / Logout Bereich
|
// Profil / Logout Bereich
|
||||||
Text(
|
if (isAuthenticated) {
|
||||||
text = "Administrator",
|
Text(
|
||||||
style = MaterialTheme.typography.labelMedium,
|
text = "Administrator",
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
style = MaterialTheme.typography.labelMedium,
|
||||||
)
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
IconButton(onClick = onLogout) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.AutoMirrored.Filled.Logout,
|
|
||||||
contentDescription = "Abmelden",
|
|
||||||
modifier = Modifier.size(Dimens.IconSizeM),
|
|
||||||
tint = MaterialTheme.colorScheme.error
|
|
||||||
)
|
)
|
||||||
|
IconButton(onClick = onLogout) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.AutoMirrored.Filled.Logout,
|
||||||
|
contentDescription = "Abmelden",
|
||||||
|
modifier = Modifier.size(Dimens.IconSizeM),
|
||||||
|
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