This commit is contained in:
2026-07-27 12:16:02 +02:00
parent 59f97682c1
commit e905ec5bb8
6 changed files with 197 additions and 304 deletions
@@ -1,5 +1,7 @@
package de.drazz.boehmitools.training.ui package de.drazz.boehmitools.training.ui
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
@@ -7,9 +9,7 @@ import androidx.compose.material.icons.filled.FitnessCenter
import androidx.compose.material.icons.filled.Settings import androidx.compose.material.icons.filled.Settings
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.NavigationBar import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarDefaults
import androidx.compose.material3.NavigationBarItem import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.NavigationBarItemDefaults import androidx.compose.material3.NavigationBarItemDefaults
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
@@ -42,24 +42,22 @@ fun AppRoot(
) { ) {
val navController = rememberNavController() val navController = rememberNavController()
val destinations = listOf( val destinations = listOf(
AppDestination("tracker", "Training") { Icon(Icons.Default.FitnessCenter, null) }, AppDestination("tracker", "Tracking") { Icon(Icons.Default.FitnessCenter, null) },
AppDestination("settings", "Mehr") { Icon(Icons.Default.Settings, null) }, AppDestination("settings", "Mehr") { Icon(Icons.Default.Settings, null) },
) )
val backStack by navController.currentBackStackEntryAsState() val backStack by navController.currentBackStackEntryAsState()
Scaffold( Scaffold(
containerColor = MaterialTheme.colorScheme.background, containerColor = androidx.compose.material3.MaterialTheme.colorScheme.background,
bottomBar = { bottomBar = {
Box(Modifier.fillMaxWidth().padding(horizontal = 14.dp, vertical = 10.dp)) {
Surface( Surface(
modifier = Modifier.padding(horizontal = 12.dp, vertical = 8.dp), modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(24.dp), shape = RoundedCornerShape(24.dp),
tonalElevation = 8.dp, tonalElevation = 10.dp,
shadowElevation = 10.dp, shadowElevation = 12.dp,
) {
NavigationBar(
containerColor = MaterialTheme.colorScheme.surface,
windowInsets = NavigationBarDefaults.windowInsets,
) { ) {
NavigationBar(containerColor = androidx.compose.material3.MaterialTheme.colorScheme.surface) {
destinations.forEach { destination -> destinations.forEach { destination ->
val selected = backStack?.destination?.hierarchy?.any { it.route == destination.route } == true val selected = backStack?.destination?.hierarchy?.any { it.route == destination.route } == true
NavigationBarItem( NavigationBarItem(
@@ -74,12 +72,13 @@ fun AppRoot(
icon = destination.icon, icon = destination.icon,
label = { Text(destination.label) }, label = { Text(destination.label) },
colors = NavigationBarItemDefaults.colors( colors = NavigationBarItemDefaults.colors(
indicatorColor = MaterialTheme.colorScheme.primaryContainer, indicatorColor = androidx.compose.material3.MaterialTheme.colorScheme.primaryContainer,
), ),
) )
} }
} }
} }
}
}, },
) { padding -> ) { padding ->
NavHost(navController = navController, startDestination = "tracker") { NavHost(navController = navController, startDestination = "tracker") {
@@ -103,7 +102,7 @@ private fun WhatsNewDialog(onDismiss: () -> Unit) {
AlertDialog( AlertDialog(
onDismissRequest = onDismiss, onDismissRequest = onDismiss,
title = { Text("Neu in RipMe ${BuildConfig.VERSION_NAME}") }, title = { Text("Neu in RipMe ${BuildConfig.VERSION_NAME}") },
text = { Text("Moderner Card-Look, verbesserte Hierarchie und kompaktere Trainingseingaben. Alle bestehenden Daten- und Synchronisationsfunktionen bleiben erhalten.") }, text = { Text("Modern Card Design, klarere Trainingseingaben und eine kompaktere Navigation.") },
confirmButton = { TextButton(onClick = onDismiss) { Text("Verstanden") } }, confirmButton = { TextButton(onClick = onDismiss) { Text("Verstanden") } },
) )
} }
@@ -1,8 +1,6 @@
package de.drazz.boehmitools.training.ui.setup package de.drazz.boehmitools.training.ui.setup
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
@@ -13,17 +11,13 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.CloudDone import androidx.compose.material.icons.filled.CloudDone
import androidx.compose.material.icons.filled.Info import androidx.compose.material.icons.filled.Info
import androidx.compose.material.icons.filled.Lock
import androidx.compose.material.icons.filled.Palette
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.CardDefaults import androidx.compose.material3.Card
import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ElevatedCard
import androidx.compose.material3.FilterChip import androidx.compose.material3.FilterChip
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
@@ -33,17 +27,12 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import de.drazz.boehmitools.training.BuildConfig import de.drazz.boehmitools.training.BuildConfig
import de.drazz.boehmitools.training.data.settings.SettingsRepository import de.drazz.boehmitools.training.data.settings.SettingsRepository
import de.drazz.boehmitools.training.ui.theme.BrandIndigo
import de.drazz.boehmitools.training.ui.theme.BrandPurple
import de.drazz.boehmitools.training.ui.theme.BrandViolet
@Composable @Composable
fun SettingsScreen( fun SettingsScreen(
@@ -59,63 +48,65 @@ fun SettingsScreen(
.fillMaxSize() .fillMaxSize()
.padding(contentPadding) .padding(contentPadding)
.verticalScroll(rememberScrollState()) .verticalScroll(rememberScrollState())
.padding(horizontal = 14.dp, vertical = 12.dp), .padding(16.dp),
verticalArrangement = Arrangement.spacedBy(14.dp), verticalArrangement = Arrangement.spacedBy(16.dp),
) { ) {
Box( Text("RipMe Einstellungen", style = MaterialTheme.typography.headlineMedium)
Modifier
.fillMaxWidth()
.background(Brush.linearGradient(listOf(BrandIndigo, BrandPurple, BrandViolet)), RoundedCornerShape(26.dp))
.padding(20.dp),
) {
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
Text("Einstellungen", color = Color.White, style = MaterialTheme.typography.headlineMedium)
Text("Verbindung, Darstellung und App-Informationen", color = Color.White.copy(alpha = 0.82f))
}
}
ElevatedCard( Card(Modifier.fillMaxWidth()) {
modifier = Modifier.fillMaxWidth(),
elevation = CardDefaults.elevatedCardElevation(defaultElevation = 3.dp),
) {
Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) {
Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) {
Icon(Icons.Default.Lock, null, tint = MaterialTheme.colorScheme.primary)
Text("Server & Anmeldung", style = MaterialTheme.typography.titleLarge) Text("Server & Anmeldung", style = MaterialTheme.typography.titleLarge)
}
Text( Text(
"Zugangsdaten werden lokal mit Android-Keystore-Unterstützung geschützt. Release-Builds akzeptieren ausschließlich HTTPS.", "Basic-Auth-Zugangsdaten werden mit einem Android-Keystore-Schlüssel verschlüsselt. Release-Builds akzeptieren ausschließlich HTTPS.",
color = MaterialTheme.colorScheme.onSurfaceVariant,
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
) )
OutlinedTextField(state.serverUrl, viewModel::updateServer, Modifier.fillMaxWidth(), label = { Text("Server-URL") }, singleLine = true)
OutlinedTextField(state.username, viewModel::updateUsername, Modifier.fillMaxWidth(), label = { Text("Benutzername") }, singleLine = true)
OutlinedTextField( OutlinedTextField(
state.password, value = state.serverUrl,
viewModel::updatePassword, onValueChange = viewModel::updateServer,
Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
label = { Text("Server-URL") },
singleLine = true,
)
OutlinedTextField(
value = state.username,
onValueChange = viewModel::updateUsername,
modifier = Modifier.fillMaxWidth(),
label = { Text("Benutzername") },
singleLine = true,
)
OutlinedTextField(
value = state.password,
onValueChange = viewModel::updatePassword,
modifier = Modifier.fillMaxWidth(),
label = { Text("Passwort") }, label = { Text("Passwort") },
visualTransformation = PasswordVisualTransformation(), visualTransformation = PasswordVisualTransformation(),
singleLine = true, singleLine = true,
) )
Button(onClick = viewModel::saveAndTest, enabled = !state.loading, modifier = Modifier.fillMaxWidth()) { Button(
if (state.loading) CircularProgressIndicator(modifier = Modifier.height(18.dp), strokeWidth = 2.dp) onClick = viewModel::saveAndTest,
else Icon(Icons.Default.CloudDone, null) enabled = !state.loading,
modifier = Modifier.fillMaxWidth(),
) {
if (state.loading) {
CircularProgressIndicator(modifier = Modifier.height(18.dp), strokeWidth = 2.dp)
} else {
Icon(Icons.Default.CloudDone, null)
}
Spacer(Modifier.width(8.dp)) Spacer(Modifier.width(8.dp))
Text("Speichern & Verbindung testen") Text("Speichern & Verbindung testen")
} }
if (state.message.isNotBlank()) Text(state.message, color = MaterialTheme.colorScheme.tertiary) if (state.message.isNotBlank()) Text(state.message, color = MaterialTheme.colorScheme.primary)
if (state.error.isNotBlank()) Text(state.error, color = MaterialTheme.colorScheme.error) if (state.error.isNotBlank()) Text(state.error, color = MaterialTheme.colorScheme.error)
} }
} }
ElevatedCard(Modifier.fillMaxWidth(), elevation = CardDefaults.elevatedCardElevation(3.dp)) { Card(Modifier.fillMaxWidth()) {
Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) {
Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) {
Icon(Icons.Default.Palette, null, tint = MaterialTheme.colorScheme.primary)
Text("Darstellung", style = MaterialTheme.typography.titleLarge) Text("Darstellung", style = MaterialTheme.typography.titleLarge)
} Row(
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(6.dp)) { modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(6.dp),
) {
listOf("system" to "System", "light" to "Hell", "dark" to "Dunkel").forEach { (value, label) -> listOf("system" to "System", "light" to "Hell", "dark" to "Dunkel").forEach { (value, label) ->
FilterChip( FilterChip(
selected = current.theme == value, selected = current.theme == value,
@@ -128,11 +119,14 @@ fun SettingsScreen(
} }
} }
ElevatedCard(Modifier.fillMaxWidth(), elevation = CardDefaults.elevatedCardElevation(3.dp)) { Card(Modifier.fillMaxWidth()) {
Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) { Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) {
Text("Über RipMe", style = MaterialTheme.typography.titleLarge) Text("Über RipMe", style = MaterialTheme.typography.titleLarge)
Text("Version ${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})") Text("Version ${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})")
Text("Native Android-App für deinen Trainingstracker.", color = MaterialTheme.colorScheme.onSurfaceVariant) Text(
"Native Android-App für deinen Trainingstracker.",
style = MaterialTheme.typography.bodyMedium,
)
OutlinedButton(onClick = onShowWhatsNew, modifier = Modifier.fillMaxWidth()) { OutlinedButton(onClick = onShowWhatsNew, modifier = Modifier.fillMaxWidth()) {
Icon(Icons.Default.Info, null) Icon(Icons.Default.Info, null)
Spacer(Modifier.width(8.dp)) Spacer(Modifier.width(8.dp))
@@ -1,75 +1,63 @@
package de.drazz.boehmitools.training.ui.theme package de.drazz.boehmitools.training.ui.theme
import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Shapes
import androidx.compose.material3.Typography import androidx.compose.material3.Typography
import androidx.compose.material3.darkColorScheme import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.lightColorScheme import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
val BrandPurple = Color(0xFF7C4DFF) val ModernPurple = Color(0xFF7C3AED)
val BrandViolet = Color(0xFF9C4DFF) val ModernViolet = Color(0xFF9B5CF6)
val BrandIndigo = Color(0xFF5B5FEF) val ModernIndigo = Color(0xFF4F46E5)
val SuccessGreen = Color(0xFF22C58B) val ModernGreen = Color(0xFF22C55E)
private val Light = lightColorScheme( private val Light = lightColorScheme(
primary = Color(0xFF6D3FE8), primary = ModernPurple,
onPrimary = Color.White, onPrimary = Color.White,
primaryContainer = Color(0xFFE9DDFF), primaryContainer = Color(0xFFEDE9FE),
onPrimaryContainer = Color(0xFF22005D), onPrimaryContainer = Color(0xFF32136B),
secondary = Color(0xFF64558A), secondary = ModernIndigo,
onSecondary = Color.White, onSecondary = Color.White,
secondaryContainer = Color(0xFFE9DEFF), secondaryContainer = Color(0xFFE0E7FF),
onSecondaryContainer = Color(0xFF201047), onSecondaryContainer = Color(0xFF1E1B4B),
tertiary = Color(0xFF006C63), tertiary = ModernGreen,
tertiaryContainer = Color(0xFF74F8E8), background = Color(0xFFF6F5FA),
background = Color(0xFFF8F7FC), onBackground = Color(0xFF17151D),
onBackground = Color(0xFF1C1B20), surface = Color(0xFFFFFFFF),
surface = Color(0xFFFFFBFF), onSurface = Color(0xFF17151D),
onSurface = Color(0xFF1C1B20), surfaceVariant = Color(0xFFF0EEF5),
surfaceVariant = Color(0xFFE8E5EE), onSurfaceVariant = Color(0xFF66616F),
outline = Color(0xFF7A757F), outline = Color(0xFFDDD8E7),
outlineVariant = Color(0xFFCBC5D0), outlineVariant = Color(0xFFEAE6F0),
) )
private val Dark = darkColorScheme( private val Dark = darkColorScheme(
primary = Color(0xFFCBB5FF), primary = Color(0xFFA78BFA),
onPrimary = Color(0xFF3B008F), onPrimary = Color(0xFF221047),
primaryContainer = Color(0xFF5427B6), primaryContainer = Color(0xFF4C1D95),
onPrimaryContainer = Color(0xFFEADDFF), onPrimaryContainer = Color(0xFFF3E8FF),
secondary = Color(0xFFCDC1E9), secondary = Color(0xFF818CF8),
onSecondary = Color(0xFF352A4F), onSecondary = Color(0xFF11133F),
secondaryContainer = Color(0xFF4C4167), secondaryContainer = Color(0xFF312E81),
onSecondaryContainer = Color(0xFFE9DEFF), onSecondaryContainer = Color(0xFFE0E7FF),
tertiary = Color(0xFF54DBC9), tertiary = Color(0xFF34D399),
tertiaryContainer = Color(0xFF005049), background = Color(0xFF0D111A),
background = Color(0xFF0F1118), onBackground = Color(0xFFF5F3FA),
onBackground = Color(0xFFE8E1EA), surface = Color(0xFF151A25),
surface = Color(0xFF151821), onSurface = Color(0xFFF5F3FA),
onSurface = Color(0xFFE8E1EA), surfaceVariant = Color(0xFF202634),
surfaceVariant = Color(0xFF2A2D38), onSurfaceVariant = Color(0xFFB7B2C3),
outline = Color(0xFF958F9A), outline = Color(0xFF383F50),
outlineVariant = Color(0xFF46434D), outlineVariant = Color(0xFF282E3B),
) )
private val AppShapes = Shapes( private val ModernTypography = Typography().copy(
extraSmall = RoundedCornerShape(10.dp), headlineSmall = Typography().headlineSmall.copy(fontWeight = FontWeight.Bold, letterSpacing = (-0.4).sp),
small = RoundedCornerShape(14.dp), titleLarge = Typography().titleLarge.copy(fontWeight = FontWeight.Bold),
medium = RoundedCornerShape(20.dp),
large = RoundedCornerShape(26.dp),
extraLarge = RoundedCornerShape(32.dp),
)
private val AppTypography = Typography(
headlineMedium = Typography().headlineMedium.copy(fontWeight = FontWeight.Bold, letterSpacing = (-0.4).sp),
headlineSmall = Typography().headlineSmall.copy(fontWeight = FontWeight.Bold, letterSpacing = (-0.3).sp),
titleLarge = Typography().titleLarge.copy(fontWeight = FontWeight.SemiBold),
titleMedium = Typography().titleMedium.copy(fontWeight = FontWeight.SemiBold), titleMedium = Typography().titleMedium.copy(fontWeight = FontWeight.SemiBold),
labelLarge = Typography().labelLarge.copy(fontWeight = FontWeight.SemiBold), labelLarge = Typography().labelLarge.copy(fontWeight = FontWeight.SemiBold),
) )
@@ -83,8 +71,7 @@ fun TrainingTheme(theme: String, content: @Composable () -> Unit) {
} }
MaterialTheme( MaterialTheme(
colorScheme = if (dark) Dark else Light, colorScheme = if (dark) Dark else Light,
typography = AppTypography, typography = ModernTypography,
shapes = AppShapes,
content = content, content = content,
) )
} }
@@ -11,11 +11,15 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.CheckCircle import androidx.compose.material.icons.filled.CheckCircle
import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material.icons.filled.MoreVert
@@ -27,7 +31,6 @@ import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.Card import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults import androidx.compose.material3.CardDefaults
import androidx.compose.material3.ElevatedCard
import androidx.compose.material3.Checkbox import androidx.compose.material3.Checkbox
import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.HorizontalDivider
@@ -42,8 +45,7 @@ import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.OutlinedCard import androidx.compose.material3.OutlinedCard
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface import androidx.compose.material3.PrimaryTabRow
import androidx.compose.material3.TabRow
import androidx.compose.material3.Tab import androidx.compose.material3.Tab
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
@@ -54,15 +56,13 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import de.drazz.boehmitools.training.ui.theme.BrandIndigo import de.drazz.boehmitools.training.ui.theme.ModernIndigo
import de.drazz.boehmitools.training.ui.theme.BrandPurple import de.drazz.boehmitools.training.ui.theme.ModernPurple
import de.drazz.boehmitools.training.ui.theme.BrandViolet import de.drazz.boehmitools.training.ui.theme.ModernViolet
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import de.drazz.boehmitools.training.data.local.PendingMutation import de.drazz.boehmitools.training.data.local.PendingMutation
@@ -103,11 +103,12 @@ fun TrackerScreen(
Scaffold( Scaffold(
modifier = Modifier.padding(contentPadding), modifier = Modifier.padding(contentPadding),
containerColor = MaterialTheme.colorScheme.background,
topBar = { topBar = {
TopAppBar( TopAppBar(
title = { title = {
Column { Column {
Text(state.plan?.title ?: "RipMe", maxLines = 1, style = MaterialTheme.typography.titleLarge) Text(state.plan?.title ?: "RipMe", maxLines = 2)
if (state.saveState.isNotBlank()) Text(state.saveState, style = MaterialTheme.typography.labelSmall) if (state.saveState.isNotBlank()) Text(state.saveState, style = MaterialTheme.typography.labelSmall)
} }
}, },
@@ -131,11 +132,9 @@ fun TrackerScreen(
) { inner -> ) { inner ->
Column(Modifier.fillMaxSize().padding(inner)) { Column(Modifier.fillMaxSize().padding(inner)) {
val tabs = listOf("session" to "Session", "progress" to "Fortschritt", "plan" to "Plan", "faq" to "FAQ") val tabs = listOf("session" to "Session", "progress" to "Fortschritt", "plan" to "Plan", "faq" to "FAQ")
TabRow( PrimaryTabRow(
selectedTabIndex = tabs.indexOfFirst { it.first == state.tab }.coerceAtLeast(0), selectedTabIndex = tabs.indexOfFirst { it.first == state.tab }.coerceAtLeast(0),
containerColor = MaterialTheme.colorScheme.background, containerColor = MaterialTheme.colorScheme.background,
contentColor = MaterialTheme.colorScheme.primary,
divider = {},
) { ) {
tabs.forEach { (id, label) -> tabs.forEach { (id, label) ->
Tab( Tab(
@@ -185,7 +184,7 @@ private fun SessionPane(state: TrackerUiState, pending: List<PendingMutation>, v
val session = trackerSession(state.tracker, de.drazz.boehmitools.training.domain.sessionKey(state.week, state.day), plan) val session = trackerSession(state.tracker, de.drazz.boehmitools.training.domain.sessionKey(state.week, state.day), plan)
Column( Column(
Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(horizontal = 14.dp, vertical = 12.dp), Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(horizontal = 14.dp, vertical = 12.dp),
verticalArrangement = Arrangement.spacedBy(12.dp), verticalArrangement = Arrangement.spacedBy(14.dp),
) { ) {
if (state.error.isNotBlank()) Text(state.error, color = MaterialTheme.colorScheme.error) if (state.error.isNotBlank()) Text(state.error, color = MaterialTheme.colorScheme.error)
if (pending.isNotEmpty()) PendingQueue(pending, vm) if (pending.isNotEmpty()) PendingQueue(pending, vm)
@@ -194,26 +193,20 @@ private fun SessionPane(state: TrackerUiState, pending: List<PendingMutation>, v
SequenceCard("Aufwärmen", "warmup", day.warmup, session, vm) SequenceCard("Aufwärmen", "warmup", day.warmup, session, vm)
day.rotations.forEach { rotation -> day.rotations.forEach { rotation ->
ElevatedCard( Card(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
elevation = CardDefaults.elevatedCardElevation(defaultElevation = 3.dp), shape = RoundedCornerShape(22.dp),
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface),
elevation = CardDefaults.cardElevation(defaultElevation = 5.dp),
) { ) {
Column(Modifier.padding(14.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(14.dp)) {
Text(rotation.label, style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold) Text(rotation.label, style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold)
rotation.exercises.forEach { exercise -> rotation.exercises.forEach { exercise ->
Surface(
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(18.dp),
color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.42f),
) {
Box(Modifier.padding(14.dp)) {
ExerciseCard(exercise, session, plan, vm) ExerciseCard(exercise, session, plan, vm)
} }
} }
} }
} }
}
}
SequenceCard("Cooldown", "cooldown", day.cooldown, session, vm) SequenceCard("Cooldown", "cooldown", day.cooldown, session, vm)
SequenceCard("Stretch", "stretch", day.stretch, session, vm) SequenceCard("Stretch", "stretch", day.stretch, session, vm)
Spacer(Modifier.height(24.dp)) Spacer(Modifier.height(24.dp))
@@ -222,10 +215,7 @@ private fun SessionPane(state: TrackerUiState, pending: List<PendingMutation>, v
@Composable @Composable
private fun PendingQueue(pending: List<PendingMutation>, vm: TrackerViewModel) { private fun PendingQueue(pending: List<PendingMutation>, vm: TrackerViewModel) {
OutlinedCard( OutlinedCard(Modifier.fillMaxWidth()) {
modifier = Modifier.fillMaxWidth(),
elevation = CardDefaults.elevatedCardElevation(defaultElevation = 3.dp),
) {
Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) { Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) {
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
Icon(Icons.Default.CloudOff, null) Icon(Icons.Default.CloudOff, null)
@@ -304,69 +294,34 @@ private fun SessionHeader(plan: PlanUi, day: DayUi, session: JsonObject, week: I
"completed" -> "Abgeschlossen" "completed" -> "Abgeschlossen"
else -> "Geplant" else -> "Geplant"
} }
ElevatedCard( Card(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
elevation = CardDefaults.elevatedCardElevation(defaultElevation = 6.dp), shape = RoundedCornerShape(24.dp),
elevation = CardDefaults.cardElevation(defaultElevation = 10.dp),
) { ) {
Column( Column(
Modifier Modifier
.background(Brush.linearGradient(listOf(BrandIndigo, BrandPurple, BrandViolet))) .background(Brush.linearGradient(listOf(ModernPurple, ModernIndigo)))
.padding(18.dp), .padding(20.dp),
verticalArrangement = Arrangement.spacedBy(10.dp), verticalArrangement = Arrangement.spacedBy(12.dp),
) { ) {
Text("Woche $week · Tag ${day.num}", color = Color.White.copy(alpha = 0.86f), style = MaterialTheme.typography.labelLarge) Text("Woche $week · Tag ${day.num}", color = Color.White.copy(alpha = .8f), style = MaterialTheme.typography.labelLarge)
Text(day.focus, color = Color.White, style = MaterialTheme.typography.headlineSmall) Text(day.focus, color = Color.White, style = MaterialTheme.typography.headlineSmall)
Text("Planrevision R${session.int("plan_revision", plan.publishedRevision)} · Status: $statusLabel", color = Color.White.copy(alpha = 0.78f), style = MaterialTheme.typography.bodySmall) Text("Planrevision R${session.int("plan_revision", plan.publishedRevision)} · $statusLabel", color = Color.White.copy(alpha = .78f), style = MaterialTheme.typography.bodySmall)
when (status) { when (status) {
"in_progress" -> Row( "in_progress" -> Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(10.dp)) {
modifier = Modifier.fillMaxWidth(), Button(onClick = { vm.setSessionStatus("completed") }, modifier = Modifier.weight(1f)) {
horizontalArrangement = Arrangement.spacedBy(8.dp), Icon(Icons.Default.CheckCircle, null); Spacer(Modifier.width(6.dp)); Text("Abschließen", maxLines = 1)
) {
Button(
onClick = { vm.setSessionStatus("completed") },
modifier = Modifier.weight(1f),
colors = androidx.compose.material3.ButtonDefaults.buttonColors(
containerColor = Color.White,
contentColor = BrandPurple,
),
) {
Icon(Icons.Default.CheckCircle, null)
Spacer(Modifier.width(6.dp))
Text("Abschließen", maxLines = 1)
} }
OutlinedButton( OutlinedButton(onClick = { vm.setSessionStatus("stopped") }, modifier = Modifier.weight(1f)) {
onClick = { vm.setSessionStatus("stopped") }, Icon(Icons.Default.Stop, null); Spacer(Modifier.width(6.dp)); Text("Stoppen", maxLines = 1)
modifier = Modifier.weight(1f),
) {
Icon(Icons.Default.Stop, null)
Spacer(Modifier.width(6.dp))
Text("Stoppen", maxLines = 1)
} }
} }
"stopped" -> Button( "stopped" -> Button(onClick = { vm.setSessionStatus("in_progress") }, modifier = Modifier.fillMaxWidth()) { Icon(Icons.Default.PlayArrow, null); Spacer(Modifier.width(6.dp)); Text("Session fortsetzen") }
onClick = { vm.setSessionStatus("in_progress") }, "completed" -> OutlinedButton(onClick = { vm.setSessionStatus("in_progress") }, modifier = Modifier.fillMaxWidth()) { Text("Session wieder öffnen") }
modifier = Modifier.fillMaxWidth(), else -> Button(onClick = { vm.setSessionStatus("in_progress") }, modifier = Modifier.fillMaxWidth()) { Icon(Icons.Default.PlayArrow, null); Spacer(Modifier.width(6.dp)); Text("Session starten") }
) {
Icon(Icons.Default.PlayArrow, null)
Spacer(Modifier.width(6.dp))
Text("Session fortsetzen")
}
"completed" -> OutlinedButton(
onClick = { vm.setSessionStatus("in_progress") },
modifier = Modifier.fillMaxWidth(),
) { Text("Session wieder öffnen") }
else -> Button(
onClick = { vm.setSessionStatus("in_progress") },
modifier = Modifier.fillMaxWidth(),
) {
Icon(Icons.Default.PlayArrow, null)
Spacer(Modifier.width(6.dp))
Text("Session starten")
}
}
TextButton(onClick = vm::resetSession, modifier = Modifier.fillMaxWidth()) {
Text("Session zurücksetzen")
} }
TextButton(onClick = vm::resetSession, modifier = Modifier.fillMaxWidth()) { Text("Session zurücksetzen", color = Color.White.copy(alpha = .82f)) }
} }
} }
} }
@@ -374,11 +329,12 @@ private fun SessionHeader(plan: PlanUi, day: DayUi, session: JsonObject, week: I
@Composable @Composable
private fun SequenceCard(title: String, prefix: String, items: List<String>, session: JsonObject, vm: TrackerViewModel) { private fun SequenceCard(title: String, prefix: String, items: List<String>, session: JsonObject, vm: TrackerViewModel) {
if (items.isEmpty()) return if (items.isEmpty()) return
ElevatedCard( Card(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
elevation = CardDefaults.elevatedCardElevation(defaultElevation = 3.dp), shape = RoundedCornerShape(20.dp),
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp),
) { ) {
Column(Modifier.padding(14.dp), verticalArrangement = Arrangement.spacedBy(4.dp)) { Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) {
Text(title, style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold) Text(title, style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold)
items.forEachIndexed { index, label -> items.forEachIndexed { index, label ->
val id = "$prefix-$index" val id = "$prefix-$index"
@@ -402,17 +358,28 @@ private fun ExerciseCard(exercise: ExerciseUi, session: JsonObject, plan: PlanUi
val data = normalizedResultData(item, schema) val data = normalizedResultData(item, schema)
var progressionMenu by remember(exercise.id, progression) { mutableStateOf(false) } var progressionMenu by remember(exercise.id, progression) { mutableStateOf(false) }
Column(Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(8.dp)) { Card(
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(18.dp),
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = .72f)),
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp),
) {
Column(Modifier.fillMaxWidth().padding(14.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) {
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
Column(Modifier.weight(1f)) {
Text(exercise.name, style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.SemiBold) Text(exercise.name, style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.SemiBold)
if (exercise.cue.isNotBlank()) Text(exercise.cue, style = MaterialTheme.typography.bodySmall) if (exercise.cue.isNotBlank()) Text(exercise.cue, style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant)
Text("Übungs-ID · ${exercise.id}", color = MaterialTheme.colorScheme.onSurfaceVariant, style = MaterialTheme.typography.labelSmall) }
Box(Modifier.size(32.dp).clip(RoundedCornerShape(10.dp)).background(MaterialTheme.colorScheme.primaryContainer))
}
Text("ID ${exercise.id}", style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.onSurfaceVariant)
Column(verticalArrangement = Arrangement.spacedBy(6.dp)) { Column(verticalArrangement = Arrangement.spacedBy(6.dp)) {
Text("Status", style = MaterialTheme.typography.labelMedium) Text("Status", style = MaterialTheme.typography.labelMedium)
Row( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(4.dp), horizontalArrangement = Arrangement.spacedBy(4.dp),
) { ) {
listOf("planned" to "Offen", "completed" to "Fertig", "partial" to "Teilw.", "skipped" to "Skip").forEach { (value, label) -> listOf("planned" to "Offen", "completed" to "Erledigt", "partial" to "Teilweise", "skipped" to "Übersprungen").forEach { (value, label) ->
FilterChip( FilterChip(
selected = status == value, selected = status == value,
onClick = { vm.setItemStatus(exercise.id, value) }, onClick = { vm.setItemStatus(exercise.id, value) },
@@ -469,6 +436,7 @@ private fun ExerciseCard(exercise: ExerciseUi, session: JsonObject, plan: PlanUi
) )
} }
} }
}
} }
@@ -502,10 +470,7 @@ private fun ManualResultFormatEditor(
return JsonObject(values) return JsonObject(values)
} }
OutlinedCard( OutlinedCard(Modifier.fillMaxWidth()) {
modifier = Modifier.fillMaxWidth(),
elevation = CardDefaults.elevatedCardElevation(defaultElevation = 3.dp),
) {
Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
Column(Modifier.weight(1f)) { Column(Modifier.weight(1f)) {
@@ -551,10 +516,7 @@ private fun ChoiceChips(label: String, choices: List<String>, selected: String,
@Composable @Composable
private fun ResultEditor(schema: ResultSchemaUi, data: JsonObject, onChange: (JsonObject) -> Unit) { private fun ResultEditor(schema: ResultSchemaUi, data: JsonObject, onChange: (JsonObject) -> Unit) {
OutlinedCard( OutlinedCard(Modifier.fillMaxWidth()) {
modifier = Modifier.fillMaxWidth(),
elevation = CardDefaults.elevatedCardElevation(defaultElevation = 3.dp),
) {
Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
Text("Ergebnis · ${schema.source}", style = MaterialTheme.typography.labelLarge) Text("Ergebnis · ${schema.source}", style = MaterialTheme.typography.labelLarge)
Text("${schema.mode}, ${schema.laterality}, Gewicht ${schema.weightMode}", style = MaterialTheme.typography.bodySmall) Text("${schema.mode}, ${schema.laterality}, Gewicht ${schema.weightMode}", style = MaterialTheme.typography.bodySmall)
@@ -651,15 +613,12 @@ private fun ProgressPane(state: TrackerUiState, pending: List<PendingMutation>,
} }
Column( Column(
Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(horizontal = 14.dp, vertical = 12.dp), Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(12.dp),
verticalArrangement = Arrangement.spacedBy(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp),
) { ) {
Text("Fortschritt", style = MaterialTheme.typography.headlineSmall) Text("Fortschritt", style = MaterialTheme.typography.headlineSmall)
ElevatedCard( Card(Modifier.fillMaxWidth()) {
modifier = Modifier.fillMaxWidth(),
elevation = CardDefaults.elevatedCardElevation(defaultElevation = 3.dp),
) {
Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) { Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) {
Text("Lokale Übersicht", style = MaterialTheme.typography.titleMedium) Text("Lokale Übersicht", style = MaterialTheme.typography.titleMedium)
Text("$completed abgeschlossene Sessions · $stopped gestoppte Sessions · ${sessions.size} angelegte Sessions") Text("$completed abgeschlossene Sessions · $stopped gestoppte Sessions · ${sessions.size} angelegte Sessions")
@@ -669,10 +628,7 @@ private fun ProgressPane(state: TrackerUiState, pending: List<PendingMutation>,
if (pending.isNotEmpty()) PendingQueue(pending, vm) if (pending.isNotEmpty()) PendingQueue(pending, vm)
ElevatedCard( Card(Modifier.fillMaxWidth()) {
modifier = Modifier.fillMaxWidth(),
elevation = CardDefaults.elevatedCardElevation(defaultElevation = 3.dp),
) {
Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) { Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) {
Text("Auswertung auswählen", style = MaterialTheme.typography.titleMedium) Text("Auswertung auswählen", style = MaterialTheme.typography.titleMedium)
Row( Row(
@@ -700,10 +656,7 @@ private fun ProgressPane(state: TrackerUiState, pending: List<PendingMutation>,
} }
if (state.analysisScope == "week") { if (state.analysisScope == "week") {
ElevatedCard( Card(Modifier.fillMaxWidth()) {
modifier = Modifier.fillMaxWidth(),
elevation = CardDefaults.elevatedCardElevation(defaultElevation = 3.dp),
) {
Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) { Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) {
Text("Wochenstatus", style = MaterialTheme.typography.titleMedium) Text("Wochenstatus", style = MaterialTheme.typography.titleMedium)
val closed = state.tracker.obj("week_statuses").obj(state.week.toString()).string("status") == "closed" val closed = state.tracker.obj("week_statuses").obj(state.week.toString()).string("status") == "closed"
@@ -721,10 +674,7 @@ private fun ProgressPane(state: TrackerUiState, pending: List<PendingMutation>,
} }
} }
ElevatedCard( Card(Modifier.fillMaxWidth()) {
modifier = Modifier.fillMaxWidth(),
elevation = CardDefaults.elevatedCardElevation(defaultElevation = 3.dp),
) {
Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) { Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) {
Text("Manuelle Analyse", style = MaterialTheme.typography.titleMedium) Text("Manuelle Analyse", style = MaterialTheme.typography.titleMedium)
Button( Button(
@@ -758,10 +708,7 @@ private fun ProgressPane(state: TrackerUiState, pending: List<PendingMutation>,
@Composable @Composable
private fun AnalysisCard(title: String, record: JsonObject?) { private fun AnalysisCard(title: String, record: JsonObject?) {
ElevatedCard( Card(Modifier.fillMaxWidth()) {
modifier = Modifier.fillMaxWidth(),
elevation = CardDefaults.elevatedCardElevation(defaultElevation = 3.dp),
) {
Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) {
Text(title, style = MaterialTheme.typography.titleMedium) Text(title, style = MaterialTheme.typography.titleMedium)
if (record == null) { if (record == null) {
@@ -791,10 +738,7 @@ private fun AnalysisCard(title: String, record: JsonObject?) {
val dataQuality = result.string("data_quality", result.obj("overview").string("data_quality")) val dataQuality = result.string("data_quality", result.obj("overview").string("data_quality"))
if (dataQuality.isNotBlank()) { if (dataQuality.isNotBlank()) {
OutlinedCard( OutlinedCard(Modifier.fillMaxWidth()) {
modifier = Modifier.fillMaxWidth(),
elevation = CardDefaults.elevatedCardElevation(defaultElevation = 3.dp),
) {
Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(4.dp)) { Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(4.dp)) {
Text("Datenbasis", fontWeight = FontWeight.Bold) Text("Datenbasis", fontWeight = FontWeight.Bold)
Text(dataQuality) Text(dataQuality)
@@ -806,10 +750,7 @@ private fun AnalysisCard(title: String, record: JsonObject?) {
if (metrics.isNotEmpty()) { if (metrics.isNotEmpty()) {
Text("Kennzahlen", style = MaterialTheme.typography.titleMedium) Text("Kennzahlen", style = MaterialTheme.typography.titleMedium)
metrics.forEach { metric -> metrics.forEach { metric ->
OutlinedCard( OutlinedCard(Modifier.fillMaxWidth()) {
modifier = Modifier.fillMaxWidth(),
elevation = CardDefaults.elevatedCardElevation(defaultElevation = 3.dp),
) {
Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(3.dp)) { Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(3.dp)) {
Text(metric.string("label"), style = MaterialTheme.typography.labelMedium) Text(metric.string("label"), style = MaterialTheme.typography.labelMedium)
Text(metric.string("value"), style = MaterialTheme.typography.titleMedium) Text(metric.string("value"), style = MaterialTheme.typography.titleMedium)
@@ -827,10 +768,7 @@ private fun AnalysisCard(title: String, record: JsonObject?) {
if (exercises.isNotEmpty()) { if (exercises.isNotEmpty()) {
Text("Übungsstand", style = MaterialTheme.typography.titleMedium) Text("Übungsstand", style = MaterialTheme.typography.titleMedium)
exercises.forEach { update -> exercises.forEach { update ->
OutlinedCard( OutlinedCard(Modifier.fillMaxWidth()) {
modifier = Modifier.fillMaxWidth(),
elevation = CardDefaults.elevatedCardElevation(defaultElevation = 3.dp),
) {
Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(5.dp)) { Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(5.dp)) {
Text(update.string("name", "Übung"), fontWeight = FontWeight.Bold) Text(update.string("name", "Übung"), fontWeight = FontWeight.Bold)
val trend = when (update.string("trend")) { val trend = when (update.string("trend")) {
@@ -858,10 +796,7 @@ private fun AnalysisCard(title: String, record: JsonObject?) {
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall,
) )
adjustments.forEach { item -> adjustments.forEach { item ->
OutlinedCard( OutlinedCard(Modifier.fillMaxWidth()) {
modifier = Modifier.fillMaxWidth(),
elevation = CardDefaults.elevatedCardElevation(defaultElevation = 3.dp),
) {
Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(5.dp)) { Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(5.dp)) {
Text(item.string("target", "Vorschlag"), fontWeight = FontWeight.Bold) Text(item.string("target", "Vorschlag"), fontWeight = FontWeight.Bold)
if (item.string("suggested_change").isNotBlank()) Text(item.string("suggested_change")) if (item.string("suggested_change").isNotBlank()) Text(item.string("suggested_change"))
@@ -875,10 +810,7 @@ private fun AnalysisCard(title: String, record: JsonObject?) {
val warnings = result.array("warnings") val warnings = result.array("warnings")
if (warnings.isNotEmpty()) { if (warnings.isNotEmpty()) {
OutlinedCard( OutlinedCard(Modifier.fillMaxWidth()) {
modifier = Modifier.fillMaxWidth(),
elevation = CardDefaults.elevatedCardElevation(defaultElevation = 3.dp),
) {
Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(5.dp)) { Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(5.dp)) {
Text("Hinweise", fontWeight = FontWeight.Bold) Text("Hinweise", fontWeight = FontWeight.Bold)
warnings.forEach { warning -> warnings.forEach { warning ->
@@ -904,10 +836,7 @@ private fun AnalysisVisuals(visuals: JsonObject) {
Text("Visualisierte Erkenntnisse", style = MaterialTheme.typography.titleMedium) Text("Visualisierte Erkenntnisse", style = MaterialTheme.typography.titleMedium)
charts.forEach { chart -> charts.forEach { chart ->
OutlinedCard( OutlinedCard(Modifier.fillMaxWidth()) {
modifier = Modifier.fillMaxWidth(),
elevation = CardDefaults.elevatedCardElevation(defaultElevation = 3.dp),
) {
Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(4.dp)) { Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(4.dp)) {
Text(chart.string("title", "Verlauf"), fontWeight = FontWeight.Bold) Text(chart.string("title", "Verlauf"), fontWeight = FontWeight.Bold)
val series = chart.array("series").mapNotNull { it as? JsonObject } val series = chart.array("series").mapNotNull { it as? JsonObject }
@@ -920,10 +849,7 @@ private fun AnalysisVisuals(visuals: JsonObject) {
} }
} }
flowcharts.forEach { flow -> flowcharts.forEach { flow ->
OutlinedCard( OutlinedCard(Modifier.fillMaxWidth()) {
modifier = Modifier.fillMaxWidth(),
elevation = CardDefaults.elevatedCardElevation(defaultElevation = 3.dp),
) {
Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(4.dp)) { Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(4.dp)) {
Text(flow.string("title", "Progressionspfad"), fontWeight = FontWeight.Bold) Text(flow.string("title", "Progressionspfad"), fontWeight = FontWeight.Bold)
val nodes = flow.array("nodes").mapNotNull { it as? JsonObject } val nodes = flow.array("nodes").mapNotNull { it as? JsonObject }
@@ -938,15 +864,12 @@ private fun AnalysisVisuals(visuals: JsonObject) {
@Composable @Composable
private fun PlanPane(plan: PlanUi) { private fun PlanPane(plan: PlanUi) {
Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(horizontal = 14.dp, vertical = 12.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) {
Text(plan.title, style = MaterialTheme.typography.headlineSmall) Text(plan.title, style = MaterialTheme.typography.headlineSmall)
Text(plan.subtitle) Text(plan.subtitle)
Text("${plan.weeks} Wochen · veröffentlichte Revision ${plan.publishedRevision}") Text("${plan.weeks} Wochen · veröffentlichte Revision ${plan.publishedRevision}")
plan.days.forEach { day -> plan.days.forEach { day ->
ElevatedCard( Card(Modifier.fillMaxWidth()) {
modifier = Modifier.fillMaxWidth(),
elevation = CardDefaults.elevatedCardElevation(defaultElevation = 3.dp),
) {
Column(Modifier.padding(14.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { Column(Modifier.padding(14.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
Text("Tag ${day.num}: ${day.focus}", style = MaterialTheme.typography.titleMedium) Text("Tag ${day.num}: ${day.focus}", style = MaterialTheme.typography.titleMedium)
day.rotations.forEach { rotation -> day.rotations.forEach { rotation ->
@@ -963,12 +886,9 @@ private fun PlanPane(plan: PlanUi) {
private fun FaqPane(state: TrackerUiState) { private fun FaqPane(state: TrackerUiState) {
val plan = state.plan ?: return val plan = state.plan ?: return
val guide = state.response.obj("equivalence_guide") val guide = state.response.obj("equivalence_guide")
Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(horizontal = 14.dp, vertical = 12.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) {
Text("FAQ & Variantencluster", style = MaterialTheme.typography.headlineSmall) Text("FAQ & Variantencluster", style = MaterialTheme.typography.headlineSmall)
ElevatedCard( Card(Modifier.fillMaxWidth()) {
modifier = Modifier.fillMaxWidth(),
elevation = CardDefaults.elevatedCardElevation(defaultElevation = 3.dp),
) {
Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) { Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) {
Text("Trainingsformat", style = MaterialTheme.typography.titleMedium) Text("Trainingsformat", style = MaterialTheme.typography.titleMedium)
Text("Modus: ${plan.trainingFormat.string("mode", "sets_reps")}") Text("Modus: ${plan.trainingFormat.string("mode", "sets_reps")}")
@@ -978,10 +898,7 @@ private fun FaqPane(state: TrackerUiState) {
} }
} }
} }
ElevatedCard( Card(Modifier.fillMaxWidth()) {
modifier = Modifier.fillMaxWidth(),
elevation = CardDefaults.elevatedCardElevation(defaultElevation = 3.dp),
) {
Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
Text("Planbezogene Übungsbibliothek", style = MaterialTheme.typography.titleMedium) Text("Planbezogene Übungsbibliothek", style = MaterialTheme.typography.titleMedium)
plan.exerciseCatalog.values.mapNotNull { it as? JsonObject }.take(100).forEach { entry -> plan.exerciseCatalog.values.mapNotNull { it as? JsonObject }.take(100).forEach { entry ->
@@ -990,10 +907,7 @@ private fun FaqPane(state: TrackerUiState) {
} }
} }
if (guide.isNotEmpty()) { if (guide.isNotEmpty()) {
ElevatedCard( Card(Modifier.fillMaxWidth()) {
modifier = Modifier.fillMaxWidth(),
elevation = CardDefaults.elevatedCardElevation(defaultElevation = 3.dp),
) {
Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) { Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) {
Text("Fallbackregeln des Servers", style = MaterialTheme.typography.titleMedium) Text("Fallbackregeln des Servers", style = MaterialTheme.typography.titleMedium)
Text(guide.toString(), style = MaterialTheme.typography.bodySmall) Text(guide.toString(), style = MaterialTheme.typography.bodySmall)
-1
View File
@@ -2,4 +2,3 @@ org.gradle.jvmargs=-Xmx4g -Dfile.encoding=UTF-8
android.useAndroidX=true android.useAndroidX=true
kotlin.code.style=official kotlin.code.style=official
android.nonTransitiveRClass=true android.nonTransitiveRClass=true
kapt.use.k2=false
Vendored Executable → Regular
View File