Refine MsTextField component: introduce compact mode, enhance visual styling and error handling, and improve placeholder and keyboard interaction logic. Add Dimens and Colors updates, implement navigation rail and header layout for the desktop shell, and update ROADMAP documentation with planned phases.
This commit is contained in:
+369
-269
@@ -4,20 +4,20 @@ import androidx.compose.foundation.background
|
||||
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.Chat
|
||||
import androidx.compose.material.icons.filled.Devices
|
||||
import androidx.compose.material.icons.filled.Wifi
|
||||
import androidx.compose.material.icons.filled.WifiOff
|
||||
import androidx.compose.material.icons.automirrored.filled.*
|
||||
import androidx.compose.material.icons.filled.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
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.features.billing.presentation.BillingScreen
|
||||
import at.mocode.frontend.features.billing.presentation.BillingViewModel
|
||||
@@ -49,11 +49,9 @@ private val TopBarTextColor = Color.White
|
||||
* Haupt-Layout der Desktop-App gemäß Vision_03.
|
||||
*
|
||||
* Struktur:
|
||||
* - TopBar (dunkelblau): App-Titel + Breadcrumb + Logout
|
||||
* - NavigationRail (links): Globale Navigation
|
||||
* - Header (oben): Breadcrumb + Status + Logout
|
||||
* - Content: kontextabhängiger Screen
|
||||
*
|
||||
* Kein Nav-Rail, keine Sidebar – Navigation erfolgt über
|
||||
* Breadcrumb-Klicks und horizontale Tabs innerhalb der Screens.
|
||||
*/
|
||||
@Composable
|
||||
fun DesktopMainLayout(
|
||||
@@ -62,18 +60,25 @@ fun DesktopMainLayout(
|
||||
onBack: () -> Unit,
|
||||
onLogout: () -> Unit,
|
||||
) {
|
||||
// Onboarding-Eingaben zwischen Navigationswechseln behalten → State hier (außerhalb des when) hosten
|
||||
// Onboarding-Eingaben zwischen Navigationswechseln behalten
|
||||
var obGeraet by rememberSaveable { mutableStateOf("") }
|
||||
var obKey by rememberSaveable { mutableStateOf("") }
|
||||
|
||||
Column(modifier = Modifier.fillMaxSize()) {
|
||||
DesktopTopBar(
|
||||
Row(modifier = Modifier.fillMaxSize().background(MaterialTheme.colorScheme.background)) {
|
||||
// Navigation Rail (Modernere Seitenleiste)
|
||||
DesktopNavRail(
|
||||
currentScreen = currentScreen,
|
||||
onNavigate = onNavigate,
|
||||
onBack = onBack,
|
||||
onLogout = onLogout,
|
||||
onNavigate = onNavigate
|
||||
)
|
||||
|
||||
Column(modifier = Modifier.fillMaxSize()) {
|
||||
DesktopTopHeader(
|
||||
currentScreen = currentScreen,
|
||||
onNavigate = onNavigate,
|
||||
onBack = onBack,
|
||||
onLogout = onLogout,
|
||||
)
|
||||
|
||||
Box(modifier = Modifier.weight(1f).fillMaxWidth()) {
|
||||
DesktopContentArea(
|
||||
currentScreen = currentScreen,
|
||||
@@ -85,247 +90,316 @@ fun DesktopMainLayout(
|
||||
onObKeyChange = { obKey = it },
|
||||
)
|
||||
}
|
||||
|
||||
HorizontalDivider(thickness = Dimens.BorderThin, color = MaterialTheme.colorScheme.outlineVariant)
|
||||
DesktopFooterBar()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TopBar: dunkelblauer Balken mit Breadcrumb-Navigation und Logout-Button.
|
||||
*
|
||||
* Breadcrumb-Logik:
|
||||
* - Root: "🏠 Admin - Verwaltung"
|
||||
* - Veranstaltung: "🏠 Admin - Verwaltung / Veranstaltung #<id>"
|
||||
* - Turnier: "🏠 Admin - Verwaltung / Veranstaltung #<id> / Turnier <tid>"
|
||||
*/
|
||||
@Composable
|
||||
private fun DesktopNavRail(
|
||||
currentScreen: AppScreen,
|
||||
onNavigate: (AppScreen) -> Unit
|
||||
) {
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxHeight().width(Dimens.NavRailWidth),
|
||||
color = AppColors.NavigationSurface,
|
||||
contentColor = AppColors.NavigationContent,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxHeight().padding(vertical = Dimens.SpacingM),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(Dimens.SpacingS)
|
||||
) {
|
||||
// App Icon / Logo Platzhalter
|
||||
Surface(
|
||||
modifier = Modifier.size(40.dp),
|
||||
shape = MaterialTheme.shapes.medium,
|
||||
color = MaterialTheme.colorScheme.primary
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Adjust,
|
||||
contentDescription = "Logo",
|
||||
tint = MaterialTheme.colorScheme.onPrimary,
|
||||
modifier = Modifier.padding(Dimens.SpacingS)
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(Modifier.height(Dimens.SpacingL))
|
||||
|
||||
// Navigations-Items
|
||||
NavRailItem(
|
||||
icon = Icons.Default.Dashboard,
|
||||
label = "Admin",
|
||||
selected = currentScreen is AppScreen.VeranstaltungVerwaltung || currentScreen is AppScreen.VeranstaltungDetail,
|
||||
onClick = { onNavigate(AppScreen.VeranstaltungVerwaltung) }
|
||||
)
|
||||
|
||||
NavRailItem(
|
||||
icon = Icons.Default.People,
|
||||
label = "Vereine",
|
||||
selected = currentScreen is AppScreen.VereinVerwaltung,
|
||||
onClick = { onNavigate(AppScreen.VereinVerwaltung) }
|
||||
)
|
||||
|
||||
NavRailItem(
|
||||
icon = Icons.Default.Settings,
|
||||
label = "Tools",
|
||||
selected = currentScreen is AppScreen.Ping,
|
||||
onClick = { onNavigate(AppScreen.Ping) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DesktopTopBar(
|
||||
private fun NavRailItem(
|
||||
icon: ImageVector,
|
||||
label: String,
|
||||
selected: Boolean,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
val tint = if (selected) MaterialTheme.colorScheme.primary else AppColors.NavigationContent
|
||||
val background = if (selected) MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.5f) else Color.Transparent
|
||||
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.size(48.dp)
|
||||
.clickable(onClick = onClick),
|
||||
shape = MaterialTheme.shapes.medium,
|
||||
color = background
|
||||
) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = label,
|
||||
tint = tint,
|
||||
modifier = Modifier.size(Dimens.IconSizeM)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TopHeader: Schlanke Leiste mit Breadcrumb und Logout.
|
||||
*/
|
||||
@Composable
|
||||
private fun DesktopTopHeader(
|
||||
currentScreen: AppScreen,
|
||||
onNavigate: (AppScreen) -> Unit,
|
||||
onBack: () -> Unit,
|
||||
onLogout: () -> Unit,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(48.dp)
|
||||
.background(TopBarColor)
|
||||
.padding(horizontal = 16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxWidth().height(Dimens.TopBarHeight),
|
||||
color = MaterialTheme.colorScheme.surface,
|
||||
tonalElevation = 1.dp
|
||||
) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
// Zurück-Pfeil: für alle außer Onboarding anzeigen (damit man von "Verwaltung" zurück kommt)
|
||||
if (currentScreen !is AppScreen.Onboarding) {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
||||
contentDescription = "Zurück",
|
||||
tint = TopBarTextColor,
|
||||
modifier = Modifier
|
||||
.size(20.dp)
|
||||
.clickable { onBack() },
|
||||
)
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Row(
|
||||
modifier = Modifier.fillMaxSize().padding(horizontal = Dimens.SpacingL),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
if (currentScreen !is AppScreen.Onboarding) {
|
||||
IconButton(onClick = onBack) {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
||||
contentDescription = "Zurück",
|
||||
modifier = Modifier.size(Dimens.IconSizeM),
|
||||
tint = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
}
|
||||
Spacer(Modifier.width(Dimens.SpacingS))
|
||||
}
|
||||
|
||||
// Breadcrumb-Segmente
|
||||
BreadcrumbContent(currentScreen, onNavigate)
|
||||
}
|
||||
|
||||
// Root-Link
|
||||
Text(
|
||||
text = "Verwaltung",
|
||||
color = TopBarTextColor,
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
modifier = Modifier.clickable { onNavigate(AppScreen.VeranstaltungVerwaltung) },
|
||||
)
|
||||
|
||||
// Breadcrumb-Segmente je nach Screen
|
||||
when (currentScreen) {
|
||||
is AppScreen.VeranstalterAuswahl -> {
|
||||
BreadcrumbSeparator()
|
||||
Text(
|
||||
text = "Veranstalter auswählen",
|
||||
color = TopBarTextColor,
|
||||
fontSize = 14.sp,
|
||||
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(Dimens.SpacingM)) {
|
||||
// Profil / Logout Bereich
|
||||
Text(
|
||||
text = "Administrator",
|
||||
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
|
||||
)
|
||||
}
|
||||
|
||||
is AppScreen.VeranstalterNeu -> {
|
||||
BreadcrumbSeparator()
|
||||
Text(
|
||||
text = "Veranstalter-Verwaltung",
|
||||
color = TopBarTextColor.copy(alpha = 0.75f),
|
||||
fontSize = 14.sp,
|
||||
modifier = Modifier.clickable { onNavigate(AppScreen.VeranstalterVerwaltung) },
|
||||
)
|
||||
BreadcrumbSeparator()
|
||||
Text(
|
||||
text = "Neuer Veranstalter",
|
||||
color = TopBarTextColor,
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
)
|
||||
}
|
||||
is AppScreen.VeranstalterDetail -> {
|
||||
BreadcrumbSeparator()
|
||||
Text(
|
||||
text = "Veranstalter-Verwaltung",
|
||||
color = TopBarTextColor.copy(alpha = 0.75f),
|
||||
fontSize = 14.sp,
|
||||
modifier = Modifier.clickable { onNavigate(AppScreen.VeranstalterVerwaltung) },
|
||||
)
|
||||
BreadcrumbSeparator()
|
||||
Text(
|
||||
text = "Veranstalter #${currentScreen.veranstalterId}",
|
||||
color = TopBarTextColor,
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
)
|
||||
}
|
||||
is AppScreen.VeranstaltungProfil -> {
|
||||
BreadcrumbSeparator()
|
||||
Text(
|
||||
text = "Veranstalter-Verwaltung",
|
||||
color = TopBarTextColor.copy(alpha = 0.75f),
|
||||
fontSize = 14.sp,
|
||||
modifier = Modifier.clickable { onNavigate(AppScreen.VeranstalterVerwaltung) },
|
||||
)
|
||||
BreadcrumbSeparator()
|
||||
Text(
|
||||
text = "Veranstalter #${currentScreen.veranstalterId}",
|
||||
color = TopBarTextColor.copy(alpha = 0.75f),
|
||||
fontSize = 14.sp,
|
||||
modifier = Modifier.clickable {
|
||||
onNavigate(AppScreen.VeranstalterDetail(currentScreen.veranstalterId))
|
||||
},
|
||||
)
|
||||
BreadcrumbSeparator()
|
||||
Text(
|
||||
text = "Veranstaltung #${currentScreen.veranstaltungId}",
|
||||
color = TopBarTextColor,
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
)
|
||||
}
|
||||
is AppScreen.VeranstaltungDetail -> {
|
||||
BreadcrumbSeparator()
|
||||
Text(
|
||||
text = "Veranstaltung #${currentScreen.id}",
|
||||
color = TopBarTextColor,
|
||||
fontSize = 14.sp,
|
||||
)
|
||||
}
|
||||
is AppScreen.VeranstaltungNeu -> {
|
||||
BreadcrumbSeparator()
|
||||
Text(
|
||||
text = "Neue Veranstaltung",
|
||||
color = TopBarTextColor,
|
||||
fontSize = 14.sp,
|
||||
)
|
||||
}
|
||||
is AppScreen.TurnierDetail -> {
|
||||
BreadcrumbSeparator()
|
||||
Text(
|
||||
text = "Veranstaltung #${currentScreen.veranstaltungId}",
|
||||
color = TopBarTextColor.copy(alpha = 0.75f),
|
||||
fontSize = 14.sp,
|
||||
modifier = Modifier.clickable {
|
||||
onNavigate(AppScreen.VeranstaltungDetail(currentScreen.veranstaltungId))
|
||||
},
|
||||
)
|
||||
BreadcrumbSeparator()
|
||||
Text(
|
||||
text = "Turnier ${currentScreen.turnierId}",
|
||||
color = TopBarTextColor,
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
)
|
||||
}
|
||||
is AppScreen.Billing -> {
|
||||
BreadcrumbSeparator()
|
||||
Text(
|
||||
text = "Veranstaltung #${currentScreen.veranstaltungId}",
|
||||
color = TopBarTextColor.copy(alpha = 0.75f),
|
||||
fontSize = 14.sp,
|
||||
modifier = Modifier.clickable {
|
||||
onNavigate(AppScreen.VeranstaltungProfil(0, currentScreen.veranstaltungId))
|
||||
},
|
||||
)
|
||||
BreadcrumbSeparator()
|
||||
Text(
|
||||
text = "Turnier ${currentScreen.turnierId}",
|
||||
color = TopBarTextColor.copy(alpha = 0.75f),
|
||||
fontSize = 14.sp,
|
||||
modifier = Modifier.clickable {
|
||||
onNavigate(AppScreen.TurnierDetail(currentScreen.veranstaltungId, currentScreen.turnierId))
|
||||
},
|
||||
)
|
||||
BreadcrumbSeparator()
|
||||
Text(
|
||||
text = "Abrechnung",
|
||||
color = TopBarTextColor,
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
)
|
||||
}
|
||||
is AppScreen.TurnierNeu -> {
|
||||
BreadcrumbSeparator()
|
||||
Text(
|
||||
text = "Veranstaltung #${currentScreen.veranstaltungId}",
|
||||
color = TopBarTextColor.copy(alpha = 0.75f),
|
||||
fontSize = 14.sp,
|
||||
modifier = Modifier.clickable {
|
||||
onNavigate(AppScreen.VeranstaltungDetail(currentScreen.veranstaltungId))
|
||||
},
|
||||
)
|
||||
BreadcrumbSeparator()
|
||||
Text(
|
||||
text = "Neues Turnier",
|
||||
color = TopBarTextColor,
|
||||
fontSize = 14.sp,
|
||||
)
|
||||
}
|
||||
|
||||
is AppScreen.Ping -> {
|
||||
BreadcrumbSeparator()
|
||||
Text(
|
||||
text = "Ping Service",
|
||||
color = TopBarTextColor,
|
||||
fontSize = 14.sp,
|
||||
)
|
||||
}
|
||||
|
||||
is AppScreen.Vereine -> {
|
||||
BreadcrumbSeparator()
|
||||
Text(
|
||||
text = "Vereine",
|
||||
color = TopBarTextColor,
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
)
|
||||
}
|
||||
is AppScreen.Meisterschaften -> {
|
||||
BreadcrumbSeparator()
|
||||
Text(
|
||||
text = "Meisterschaften",
|
||||
color = TopBarTextColor,
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
)
|
||||
}
|
||||
is AppScreen.Cups -> {
|
||||
BreadcrumbSeparator()
|
||||
Text(
|
||||
text = "Cups",
|
||||
color = TopBarTextColor,
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Logout wurde auf Kundenwunsch entfernt
|
||||
@Composable
|
||||
private fun BreadcrumbContent(
|
||||
currentScreen: AppScreen,
|
||||
onNavigate: (AppScreen) -> Unit
|
||||
) {
|
||||
when (currentScreen) {
|
||||
is AppScreen.VeranstalterAuswahl -> {
|
||||
BreadcrumbSeparator()
|
||||
Text(
|
||||
text = "Veranstalter auswählen",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
}
|
||||
|
||||
is AppScreen.VeranstalterNeu -> {
|
||||
BreadcrumbSeparator()
|
||||
Text(
|
||||
text = "Veranstalter-Verwaltung",
|
||||
style = MaterialTheme.typography.bodyMedium.copy(color = MaterialTheme.colorScheme.onPrimary.copy(alpha = 0.75f)),
|
||||
modifier = Modifier.clickable { onNavigate(AppScreen.VeranstalterVerwaltung) },
|
||||
)
|
||||
BreadcrumbSeparator()
|
||||
Text(
|
||||
text = "Neuer Veranstalter",
|
||||
style = MaterialTheme.typography.bodyMedium.copy(fontWeight = FontWeight.SemiBold),
|
||||
)
|
||||
}
|
||||
is AppScreen.VeranstalterDetail -> {
|
||||
BreadcrumbSeparator()
|
||||
Text(
|
||||
text = "Veranstalter-Verwaltung",
|
||||
style = MaterialTheme.typography.bodyMedium.copy(color = MaterialTheme.colorScheme.onPrimary.copy(alpha = 0.75f)),
|
||||
modifier = Modifier.clickable { onNavigate(AppScreen.VeranstalterVerwaltung) },
|
||||
)
|
||||
BreadcrumbSeparator()
|
||||
Text(
|
||||
text = "Veranstalter #${currentScreen.veranstalterId}",
|
||||
style = MaterialTheme.typography.bodyMedium.copy(fontWeight = FontWeight.SemiBold),
|
||||
)
|
||||
}
|
||||
is AppScreen.VeranstaltungProfil -> {
|
||||
BreadcrumbSeparator()
|
||||
Text(
|
||||
text = "Veranstalter-Verwaltung",
|
||||
style = MaterialTheme.typography.bodyMedium.copy(color = MaterialTheme.colorScheme.onPrimary.copy(alpha = 0.75f)),
|
||||
modifier = Modifier.clickable { onNavigate(AppScreen.VeranstalterVerwaltung) },
|
||||
)
|
||||
BreadcrumbSeparator()
|
||||
Text(
|
||||
text = "Veranstalter #${currentScreen.veranstalterId}",
|
||||
style = MaterialTheme.typography.bodyMedium.copy(color = MaterialTheme.colorScheme.onPrimary.copy(alpha = 0.75f)),
|
||||
modifier = Modifier.clickable {
|
||||
onNavigate(AppScreen.VeranstalterDetail(currentScreen.veranstalterId))
|
||||
},
|
||||
)
|
||||
BreadcrumbSeparator()
|
||||
Text(
|
||||
text = "Veranstaltung #${currentScreen.veranstaltungId}",
|
||||
style = MaterialTheme.typography.bodyMedium.copy(fontWeight = FontWeight.SemiBold),
|
||||
)
|
||||
}
|
||||
is AppScreen.VeranstaltungDetail -> {
|
||||
BreadcrumbSeparator()
|
||||
Text(
|
||||
text = "Veranstaltung #${currentScreen.id}",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
}
|
||||
is AppScreen.VeranstaltungNeu -> {
|
||||
BreadcrumbSeparator()
|
||||
Text(
|
||||
text = "Neue Veranstaltung",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
}
|
||||
is AppScreen.TurnierDetail -> {
|
||||
BreadcrumbSeparator()
|
||||
Text(
|
||||
text = "Veranstaltung #${currentScreen.veranstaltungId}",
|
||||
style = MaterialTheme.typography.bodyMedium.copy(color = MaterialTheme.colorScheme.onPrimary.copy(alpha = 0.75f)),
|
||||
modifier = Modifier.clickable {
|
||||
onNavigate(AppScreen.VeranstaltungDetail(currentScreen.veranstaltungId))
|
||||
},
|
||||
)
|
||||
BreadcrumbSeparator()
|
||||
Text(
|
||||
text = "Turnier ${currentScreen.turnierId}",
|
||||
style = MaterialTheme.typography.bodyMedium.copy(fontWeight = FontWeight.SemiBold),
|
||||
)
|
||||
}
|
||||
is AppScreen.Billing -> {
|
||||
BreadcrumbSeparator()
|
||||
Text(
|
||||
text = "Veranstaltung #${currentScreen.veranstaltungId}",
|
||||
style = MaterialTheme.typography.bodyMedium.copy(color = MaterialTheme.colorScheme.onPrimary.copy(alpha = 0.75f)),
|
||||
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))
|
||||
},
|
||||
)
|
||||
BreadcrumbSeparator()
|
||||
Text(
|
||||
text = "Abrechnung",
|
||||
style = MaterialTheme.typography.bodyMedium.copy(fontWeight = FontWeight.SemiBold),
|
||||
)
|
||||
}
|
||||
is AppScreen.TurnierNeu -> {
|
||||
BreadcrumbSeparator()
|
||||
Text(
|
||||
text = "Veranstaltung #${currentScreen.veranstaltungId}",
|
||||
style = MaterialTheme.typography.bodyMedium.copy(color = MaterialTheme.colorScheme.onPrimary.copy(alpha = 0.75f)),
|
||||
modifier = Modifier.clickable {
|
||||
onNavigate(AppScreen.VeranstaltungDetail(currentScreen.veranstaltungId))
|
||||
},
|
||||
)
|
||||
BreadcrumbSeparator()
|
||||
Text(
|
||||
text = "Neues Turnier",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
}
|
||||
|
||||
is AppScreen.Ping -> {
|
||||
BreadcrumbSeparator()
|
||||
Text(
|
||||
text = "Ping Service",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
}
|
||||
|
||||
is AppScreen.Vereine -> {
|
||||
BreadcrumbSeparator()
|
||||
Text(
|
||||
text = "Vereine",
|
||||
style = MaterialTheme.typography.bodyMedium.copy(fontWeight = FontWeight.SemiBold),
|
||||
)
|
||||
}
|
||||
is AppScreen.Meisterschaften -> {
|
||||
BreadcrumbSeparator()
|
||||
Text(
|
||||
text = "Meisterschaften",
|
||||
style = MaterialTheme.typography.bodyMedium.copy(fontWeight = FontWeight.SemiBold),
|
||||
)
|
||||
}
|
||||
is AppScreen.Cups -> {
|
||||
BreadcrumbSeparator()
|
||||
Text(
|
||||
text = "Cups",
|
||||
style = MaterialTheme.typography.bodyMedium.copy(fontWeight = FontWeight.SemiBold),
|
||||
)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -733,45 +807,71 @@ private fun DesktopContentArea(
|
||||
|
||||
@Composable
|
||||
private fun DesktopFooterBar() {
|
||||
// Stub-Status für MVP
|
||||
// Echte Status-Logik vorbereitet
|
||||
val online = remember { mutableStateOf(true) }
|
||||
val deviceConnected = remember { mutableStateOf(true) }
|
||||
val deviceName = "Richter-Turm"
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(36.dp)
|
||||
.background(Color(0xFFF3F4F6))
|
||||
.padding(horizontal = 12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
Surface(
|
||||
color = MaterialTheme.colorScheme.surface,
|
||||
contentColor = MaterialTheme.colorScheme.onSurface,
|
||||
tonalElevation = 1.dp
|
||||
) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(
|
||||
imageVector = if (online.value) Icons.Filled.Wifi else Icons.Filled.WifiOff,
|
||||
contentDescription = null,
|
||||
tint = if (online.value) Color(0xFF059669) else Color(0xFFDC2626)
|
||||
)
|
||||
Spacer(Modifier.width(6.dp))
|
||||
Text(if (online.value) "Online" else "Offline", color = Color(0xFF374151), fontSize = 12.sp)
|
||||
Spacer(Modifier.width(16.dp))
|
||||
Icon(Icons.Filled.Devices, contentDescription = null, tint = if (deviceConnected.value) Color(0xFF2563EB) else Color(0xFF9CA3AF))
|
||||
Spacer(Modifier.width(6.dp))
|
||||
Text(
|
||||
if (deviceConnected.value) "Verbunden: $deviceName" else "Kein Gerät verbunden",
|
||||
color = Color(0xFF374151),
|
||||
fontSize = 12.sp
|
||||
)
|
||||
}
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
if (deviceConnected.value) {
|
||||
OutlinedButton(onClick = { /* öffne Chat-Panel */ }, contentPadding = PaddingValues(horizontal = 10.dp, vertical = 4.dp)) {
|
||||
Icon(Icons.AutoMirrored.Filled.Chat, contentDescription = null, tint = Color(0xFF2563EB))
|
||||
Spacer(Modifier.width(6.dp))
|
||||
Text("Chat", color = Color(0xFF2563EB), fontSize = 12.sp)
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(32.dp)
|
||||
.padding(horizontal = Dimens.SpacingS),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
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
|
||||
)
|
||||
|
||||
Spacer(Modifier.width(Dimens.SpacingM))
|
||||
|
||||
// Status: LAN Devices (mDNS)
|
||||
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
|
||||
)
|
||||
}
|
||||
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(
|
||||
text = "v2.4.0-rc1 | Desktop-Alpha",
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun StatusIndicator(
|
||||
icon: ImageVector,
|
||||
label: String,
|
||||
color: Color
|
||||
) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = null,
|
||||
tint = color,
|
||||
modifier = Modifier.size(Dimens.IconSizeS)
|
||||
)
|
||||
Spacer(Modifier.width(Dimens.SpacingXS))
|
||||
Text(
|
||||
text = label,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+20
-22
@@ -1,5 +1,6 @@
|
||||
package at.mocode.desktop.v2
|
||||
|
||||
import at.mocode.frontend.core.designsystem.components.MsTextField
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
@@ -51,10 +52,10 @@ fun OnboardingScreen(
|
||||
val frKey = remember { FocusRequester() }
|
||||
val frBtn = remember { FocusRequester() }
|
||||
|
||||
OutlinedTextField(
|
||||
MsTextField(
|
||||
value = geraetName,
|
||||
onValueChange = { onGeraetNameChange(it) },
|
||||
label = { Text("Gerätename (Pflicht)") },
|
||||
label = "Gerätename (Pflicht)",
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.focusRequester(frName)
|
||||
@@ -70,18 +71,15 @@ fun OnboardingScreen(
|
||||
} else false
|
||||
}
|
||||
,
|
||||
keyboardOptions = KeyboardOptions.Default.copy(imeAction = ImeAction.Next),
|
||||
imeAction = ImeAction.Next,
|
||||
keyboardActions = KeyboardActions(onNext = { focusManager.moveFocus(FocusDirection.Next) })
|
||||
)
|
||||
OutlinedTextField(
|
||||
MsTextField(
|
||||
value = secureKey,
|
||||
onValueChange = { onSecureKeyChange(it) },
|
||||
label = { Text("Sicherheitsschlüssel (Pflicht)") },
|
||||
trailingIcon = {
|
||||
IconButton(onClick = { showPw = !showPw }) {
|
||||
Icon(if (showPw) Icons.Default.VisibilityOff else Icons.Default.Visibility, contentDescription = null)
|
||||
}
|
||||
},
|
||||
label = "Sicherheitsschlüssel (Pflicht)",
|
||||
trailingIcon = if (showPw) Icons.Default.VisibilityOff else Icons.Default.Visibility,
|
||||
onTrailingIconClick = { showPw = !showPw },
|
||||
visualTransformation = if (showPw) VisualTransformation.None else PasswordVisualTransformation(),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
@@ -106,7 +104,7 @@ fun OnboardingScreen(
|
||||
} else false
|
||||
}
|
||||
,
|
||||
keyboardOptions = KeyboardOptions.Default.copy(imeAction = ImeAction.Done),
|
||||
imeAction = ImeAction.Done,
|
||||
keyboardActions = KeyboardActions(onDone = {
|
||||
if (geraetName.trim().length >= 3 && secureKey.trim().length >= 8) {
|
||||
onContinue(geraetName, secureKey)
|
||||
@@ -189,14 +187,14 @@ fun PferdProfilV2(id: Long, onBack: () -> Unit) {
|
||||
title = { Text("Pferd bearbeiten") },
|
||||
text = {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
OutlinedTextField(name, { name = it }, label = { Text("Name") }, modifier = Modifier.fillMaxWidth())
|
||||
MsTextField(name, { name = it }, label = "Name", modifier = Modifier.fillMaxWidth())
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
OutlinedTextField(oeps, { oeps = it }, label = { Text("ÖPS-Nr.") }, modifier = Modifier.weight(1f))
|
||||
OutlinedTextField(fei, { fei = it }, label = { Text("FEI-ID") }, modifier = Modifier.weight(1f))
|
||||
MsTextField(oeps, { oeps = it }, label = "ÖPS-Nr.", modifier = Modifier.weight(1f))
|
||||
MsTextField(fei, { fei = it }, label = "FEI-ID", modifier = Modifier.weight(1f))
|
||||
}
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
OutlinedTextField(geb, { geb = it }, label = { Text("Geburtsdatum") }, modifier = Modifier.weight(1f))
|
||||
OutlinedTextField(farbe, { farbe = it }, label = { Text("Farbe") }, modifier = Modifier.weight(1f))
|
||||
MsTextField(geb, { geb = it }, label = "Geburtsdatum", modifier = Modifier.weight(1f))
|
||||
MsTextField(farbe, { farbe = it }, label = "Farbe", modifier = Modifier.weight(1f))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -261,16 +259,16 @@ fun ReiterProfilV2(id: Long, onBack: () -> Unit) {
|
||||
text = {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
OutlinedTextField(vor, { vor = it }, label = { Text("Vorname") }, modifier = Modifier.weight(1f))
|
||||
OutlinedTextField(nach, { nach = it }, label = { Text("Nachname") }, modifier = Modifier.weight(1f))
|
||||
MsTextField(vor, { vor = it }, label = "Vorname", modifier = Modifier.weight(1f))
|
||||
MsTextField(nach, { nach = it }, label = "Nachname", modifier = Modifier.weight(1f))
|
||||
}
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
OutlinedTextField(oeps, { oeps = it }, label = { Text("ÖPS-Nr.") }, modifier = Modifier.weight(1f))
|
||||
OutlinedTextField(fei, { fei = it }, label = { Text("FEI-ID") }, modifier = Modifier.weight(1f))
|
||||
MsTextField(oeps, { oeps = it }, label = "ÖPS-Nr.", modifier = Modifier.weight(1f))
|
||||
MsTextField(fei, { fei = it }, label = "FEI-ID", modifier = Modifier.weight(1f))
|
||||
}
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
OutlinedTextField(liz, { liz = it }, label = { Text("Lizenzklasse") }, modifier = Modifier.weight(1f))
|
||||
OutlinedTextField(verein, { verein = it }, label = { Text("Verein") }, modifier = Modifier.weight(1f))
|
||||
MsTextField(liz, { liz = it }, label = "Lizenzklasse", modifier = Modifier.weight(1f))
|
||||
MsTextField(verein, { verein = it }, label = "Verein", modifier = Modifier.weight(1f))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user