This commit is contained in:
team3
2026-10-09 22:27:49 +02:00
parent 1923567c62
commit 77634392ec
7 changed files with 102 additions and 65 deletions

View File

@@ -18,6 +18,8 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
@@ -27,6 +29,7 @@ import de.marha.planer.data.Sync
import de.marha.planer.ui.CalendarScreen
import de.marha.planer.ui.NotesScreen
import de.marha.planer.ui.PlanerTheme
import de.marha.planer.ui.Scheme
import de.marha.planer.ui.SettingsScreen
import de.marha.planer.ui.TodosScreen
import java.io.File
@@ -40,8 +43,12 @@ class MainActivity : ComponentActivity() {
val dir = File(filesDir, "data")
store = Store(dir)
sync = Sync(store, getSharedPreferences("sync", Context.MODE_PRIVATE), dir, lifecycleScope)
val prefs = getSharedPreferences("settings", Context.MODE_PRIVATE)
setContent {
PlanerTheme { App(store, sync) }
var scheme by remember { mutableStateOf(Scheme.load(prefs)) }
PlanerTheme(scheme) {
App(store, sync, scheme) { scheme = it; prefs.edit().putString("scheme", it.name).apply() }
}
}
}
@@ -65,7 +72,7 @@ private val tabs = listOf(
)
@Composable
fun App(store: Store, sync: Sync) {
fun App(store: Store, sync: Sync, scheme: Scheme, onScheme: (Scheme) -> Unit) {
// Ohne Server-Zugang zuerst die Einstellungen zeigen
var tab by rememberSaveable { mutableIntStateOf(if (sync.active) 0 else 3) }
Scaffold(
@@ -87,7 +94,7 @@ fun App(store: Store, sync: Sync) {
0 -> CalendarScreen(store, modifier)
1 -> TodosScreen(store, modifier)
2 -> NotesScreen(store, modifier)
else -> SettingsScreen(sync, modifier)
else -> SettingsScreen(sync, scheme, onScheme, modifier)
}
}
}

View File

@@ -209,7 +209,7 @@ private fun MonthView(
Column(Modifier.fillMaxSize().padding(horizontal = 4.dp)) {
Row(Modifier.fillMaxWidth()) {
for (name in Dates.WEEKDAYS) Text(name, Modifier.weight(1f), color = Muted, fontSize = 11.sp, textAlign = androidx.compose.ui.text.style.TextAlign.Center)
for (name in Dates.WEEKDAYS) Text(name, Modifier.weight(1f), color = colors.onSurfaceVariant, fontSize = 11.sp, textAlign = androidx.compose.ui.text.style.TextAlign.Center)
}
for (week in 0 until 6) {
Row(Modifier.fillMaxWidth().weight(1f)) {
@@ -219,25 +219,25 @@ private fun MonthView(
val todos = store.todosOn(day)
Box(
Modifier.weight(1f).fillMaxSize().padding(1.dp)
.background(Color(0x0AFFFFFF), RoundedCornerShape(6.dp))
.then(if (day == today) Modifier.border(1.dp, Orange, RoundedCornerShape(6.dp)) else Modifier)
.background(colors.onBackground.copy(alpha = 0.04f), RoundedCornerShape(6.dp))
.then(if (day == today) Modifier.border(1.dp, colors.primary, RoundedCornerShape(6.dp)) else Modifier)
.combinedClickable(onClick = { onDay(day) }, onLongClick = { menuDay = day })
.padding(3.dp),
) {
Column {
Text(day.substring(8).trimStart('0'), fontWeight = FontWeight.Bold, fontSize = 12.sp, color = if (otherMonth) Color(0xFF777777) else MaterialTheme.colorScheme.onBackground)
Text(day.substring(8).trimStart('0'), fontWeight = FontWeight.Bold, fontSize = 12.sp, color = if (otherMonth) colors.onSurfaceVariant else MaterialTheme.colorScheme.onBackground)
val entries = events.map { e -> (if (e.start != null && e.singleDay) "${e.start} ${e.title}" else e.title) to { onEvent(e) } } +
todos.map { t -> ((if (t.done) "☑ " else "☐ ") + (t.time?.let { "$it " } ?: "") + t.title) to { onTodo(t) } }
entries.take(2).forEachIndexed { index, (text, click) ->
val isTodo = index >= events.size
Text(
text, fontSize = 9.sp, maxLines = 1, overflow = TextOverflow.Ellipsis, color = Color.White,
text, fontSize = 9.sp, maxLines = 1, overflow = TextOverflow.Ellipsis, color = colors.onPrimary,
modifier = Modifier.padding(top = 2.dp).fillMaxWidth()
.background(if (isTodo) TodoBlue.copy(alpha = 0.35f) else Orange.copy(alpha = 0.5f), RoundedCornerShape(3.dp))
.background(if (isTodo) colors.secondary.copy(alpha = 0.35f) else colors.primary.copy(alpha = 0.5f), RoundedCornerShape(3.dp))
.clickable { click() }.padding(horizontal = 3.dp),
)
}
if (entries.size > 2) Text("+${entries.size - 2}", fontSize = 9.sp, color = Muted)
if (entries.size > 2) Text("+${entries.size - 2}", fontSize = 9.sp, color = colors.onSurfaceVariant)
}
DropdownMenu(expanded = menuDay == day, onDismissRequest = { menuDay = null }) {
DropdownMenuItem(text = { Text("Termin erstellen") }, onClick = { onNewEvent(day); menuDay = null })
@@ -272,21 +272,21 @@ private fun WeekView(
Column(Modifier.fillMaxSize()) {
Row(Modifier.fillMaxWidth().padding(top = 4.dp)) {
Text("KW ${Dates.isoWeek(days[0])}", Modifier.width(HOUR_LABEL_WIDTH), color = Muted, fontSize = 9.sp)
Text("KW ${Dates.isoWeek(days[0])}", Modifier.width(HOUR_LABEL_WIDTH), color = colors.onSurfaceVariant, fontSize = 9.sp)
for (day in days) {
val d = LocalDate.parse(day)
Text("${Dates.WEEKDAYS[d.dayOfWeek.value - 1]} ${d.dayOfMonth}.", Modifier.weight(1f), fontSize = 11.sp,
color = if (day == today) Orange else Color(0xFFCCCCCC), fontWeight = if (day == today) FontWeight.Bold else null,
color = if (day == today) colors.primary else colors.onBackground, fontWeight = if (day == today) FontWeight.Bold else null,
textAlign = androidx.compose.ui.text.style.TextAlign.Center)
}
}
// Ganztags-Zeile: Blöcke, darunter Todos ohne Uhrzeit. Tipp legt einen ganztägigen Termin an.
Row(Modifier.fillMaxWidth().padding(vertical = 2.dp)) {
Text("ganz", Modifier.width(HOUR_LABEL_WIDTH), color = Muted, fontSize = 9.sp)
Text("ganz", Modifier.width(HOUR_LABEL_WIDTH), color = colors.onSurfaceVariant, fontSize = 9.sp)
BoxWithConstraints(Modifier.weight(1f).height(maxOf(22.dp, ALLDAY_LANE * lanes + 4.dp))) {
val colWidth = maxWidth / 7
Row(Modifier.fillMaxSize()) {
for (day in days) Box(Modifier.weight(1f).fillMaxSize().padding(1.dp).background(Color(0x0AFFFFFF), RoundedCornerShape(3.dp)).pointerInput(day) {
for (day in days) Box(Modifier.weight(1f).fillMaxSize().padding(1.dp).background(colors.onBackground.copy(alpha = 0.04f), RoundedCornerShape(3.dp)).pointerInput(day) {
detectTapGestures(onTap = { onTap(day, null) }, onLongPress = { onLongPress(day, null, it) })
})
}
@@ -294,8 +294,8 @@ private fun WeekView(
Box(
Modifier.offset(x = colWidth * lane.from, y = ALLDAY_LANE * lane.lane)
.width(colWidth * (lane.to - lane.from + 1)).height(ALLDAY_LANE - 2.dp).padding(horizontal = 1.dp)
.background(Orange.copy(alpha = 0.8f), RoundedCornerShape(3.dp)).clickable { onEvent(lane.event) }.padding(horizontal = 4.dp),
) { Text(lane.event.title, fontSize = 10.sp, color = Color.White, maxLines = 1, overflow = TextOverflow.Ellipsis) }
.background(colors.primary.copy(alpha = 0.8f), RoundedCornerShape(3.dp)).clickable { onEvent(lane.event) }.padding(horizontal = 4.dp),
) { Text(lane.event.title, fontSize = 10.sp, color = colors.onPrimary, maxLines = 1, overflow = TextOverflow.Ellipsis) }
}
}
}
@@ -320,8 +320,8 @@ private fun WeekView(
}) {
for (hour in 0 until 24) {
Row(Modifier.fillMaxWidth().height(HOUR_HEIGHT)) {
Text("${pad(hour)}:00", Modifier.width(HOUR_LABEL_WIDTH).padding(top = 2.dp), color = Muted, fontSize = 9.sp)
for (i in 0 until 7) Box(Modifier.weight(1f).fillMaxSize().padding(1.dp).background(Color(0x0AFFFFFF), RoundedCornerShape(3.dp)))
Text("${pad(hour)}:00", Modifier.width(HOUR_LABEL_WIDTH).padding(top = 2.dp), color = colors.onSurfaceVariant, fontSize = 9.sp)
for (i in 0 until 7) Box(Modifier.weight(1f).fillMaxSize().padding(1.dp).background(colors.onBackground.copy(alpha = 0.04f), RoundedCornerShape(3.dp)))
}
}
}
@@ -334,13 +334,13 @@ private fun WeekView(
Box(
Modifier.offset(x = HOUR_LABEL_WIDTH + colWidth * dayIndex + width * block.col, y = HOUR_HEIGHT * block.from / 60f)
.width(width).height(HOUR_HEIGHT * (block.to - block.from) / 60f - 2.dp).padding(horizontal = 1.dp)
.background(Orange.copy(alpha = 0.8f), RoundedCornerShape(4.dp))
.border(1.dp, Color(0x40FFFFFF), RoundedCornerShape(4.dp))
.background(colors.primary.copy(alpha = 0.8f), RoundedCornerShape(4.dp))
.border(1.dp, colors.onPrimary.copy(alpha = 0.25f), RoundedCornerShape(4.dp))
.clickable { onEvent(block.event) }.padding(horizontal = 3.dp, vertical = 1.dp),
) {
Column {
Text(if (day == block.event.date) block.event.start!! else "…", fontSize = 8.sp, color = Color(0xDDFFFFFF))
Text(block.event.title, fontSize = 10.sp, color = Color.White, fontWeight = FontWeight.Bold, overflow = TextOverflow.Clip)
Text(if (day == block.event.date) block.event.start!! else "…", fontSize = 8.sp, color = colors.onPrimary.copy(alpha = 0.87f))
Text(block.event.title, fontSize = 10.sp, color = colors.onPrimary, fontWeight = FontWeight.Bold, overflow = TextOverflow.Clip)
}
}
}
@@ -348,14 +348,14 @@ private fun WeekView(
Box(
Modifier.offset(x = HOUR_LABEL_WIDTH + colWidth * dayIndex, y = HOUR_HEIGHT * Dates.timeToMinutes(todo.time!!) / 60f)
.width(colWidth).height(16.dp).padding(horizontal = 1.dp)
.background(TodoBlue.copy(alpha = 0.5f), RoundedCornerShape(3.dp)).clickable { onTodo(todo) }.padding(horizontal = 2.dp),
) { Text((if (todo.done) "☑ " else "☐ ") + todo.title, fontSize = 9.sp, color = Color.White, maxLines = 1, overflow = TextOverflow.Ellipsis) }
.background(colors.secondary.copy(alpha = 0.5f), RoundedCornerShape(3.dp)).clickable { onTodo(todo) }.padding(horizontal = 2.dp),
) { Text((if (todo.done) "☑ " else "☐ ") + todo.title, fontSize = 9.sp, color = colors.onPrimary, maxLines = 1, overflow = TextOverflow.Ellipsis) }
}
}
// Rote Linie an der aktuellen Uhrzeit in der Spalte von heute
if (todayIndex >= 0) {
val now = LocalTime.now()
Box(Modifier.offset(x = HOUR_LABEL_WIDTH + colWidth * todayIndex, y = HOUR_HEIGHT * (now.hour * 60 + now.minute) / 60f - 1.dp).width(colWidth).height(2.dp).background(NowRed))
Box(Modifier.offset(x = HOUR_LABEL_WIDTH + colWidth * todayIndex, y = HOUR_HEIGHT * (now.hour * 60 + now.minute) / 60f - 1.dp).width(colWidth).height(2.dp).background(colors.error))
}
}
}
@@ -380,18 +380,18 @@ private fun DayView(store: Store, day: String, onEvent: (Event) -> Unit, onTodo:
val todos = store.todosOn(day)
LazyColumn(Modifier.fillMaxSize().padding(horizontal = 8.dp)) {
item { SectionHeader("Termine", "+ Termin", onNewEvent) }
if (events.isEmpty()) item { Text("Keine Termine", color = Muted, modifier = Modifier.padding(8.dp)) }
if (events.isEmpty()) item { Text("Keine Termine", color = colors.onSurfaceVariant, modifier = Modifier.padding(8.dp)) }
items(events, key = { it.id }) { event ->
Row(
Modifier.fillMaxWidth().padding(vertical = 2.dp).background(Color(0x0FFFFFFF), RoundedCornerShape(6.dp)).clickable { onEvent(event) }.padding(10.dp),
Modifier.fillMaxWidth().padding(vertical = 2.dp).background(colors.onBackground.copy(alpha = 0.06f), RoundedCornerShape(6.dp)).clickable { onEvent(event) }.padding(10.dp),
horizontalArrangement = Arrangement.spacedBy(10.dp),
) {
Text(timeRange(event), color = Orange, modifier = Modifier.width(110.dp))
Text(timeRange(event), color = colors.primary, modifier = Modifier.width(110.dp))
Text(event.title, fontWeight = FontWeight.Bold)
}
}
item { SectionHeader("Todos", "+ Todo", onNewTodo) }
if (todos.isEmpty()) item { Text("Keine Todos", color = Muted, modifier = Modifier.padding(8.dp)) }
if (todos.isEmpty()) item { Text("Keine Todos", color = colors.onSurfaceVariant, modifier = Modifier.padding(8.dp)) }
items(todos, key = { it.id }) { todo -> TodoRow(todo, onToggle = { store.toggleTodo(todo.id) }, onEdit = { onTodo(todo) }) }
}
}
@@ -399,7 +399,7 @@ private fun DayView(store: Store, day: String, onEvent: (Event) -> Unit, onTodo:
@Composable
private fun SectionHeader(title: String, buttonLabel: String, onClick: () -> Unit) {
Row(Modifier.fillMaxWidth().padding(top = 8.dp), verticalAlignment = Alignment.CenterVertically) {
Text(title, color = Muted, fontWeight = FontWeight.Bold, modifier = Modifier.weight(1f))
Text(title, color = colors.onSurfaceVariant, fontWeight = FontWeight.Bold, modifier = Modifier.weight(1f))
TextButton(onClick = onClick) { Text(buttonLabel) }
}
}

View File

@@ -88,12 +88,12 @@ fun EventDialog(store: Store, event: Event, onDismiss: () -> Unit) {
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
TextInput("Titel", title) { title = it }
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(6.dp)) {
Text("Von", modifier = Modifier.width(28.dp), color = Muted)
Text("Von", modifier = Modifier.width(28.dp), color = colors.onSurfaceVariant)
DateField(date, allowNone = false) { date = it ?: date }
TimeField(start) { start = it }
}
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(6.dp)) {
Text("Bis", modifier = Modifier.width(28.dp), color = Muted)
Text("Bis", modifier = Modifier.width(28.dp), color = colors.onSurfaceVariant)
DateField(endDate ?: date, allowNone = false) { endDate = it }
TimeField(end) { end = it }
}

View File

@@ -97,7 +97,7 @@ fun NotesScreen(store: Store, modifier: Modifier = Modifier) {
collect(store.tree(), 0)
Box(modifier.fillMaxSize()) {
if (rows.isEmpty()) Text("Keine Notizen", color = Muted, modifier = Modifier.padding(16.dp))
if (rows.isEmpty()) Text("Keine Notizen", color = colors.onSurfaceVariant, modifier = Modifier.padding(16.dp))
LazyColumn(Modifier.fillMaxSize()) {
items(rows, key = { (if (it.isFolder) "f:" else "n:") + it.path }) { row ->
Box {
@@ -170,7 +170,7 @@ private fun TreeRow(row: Row, onClick: () -> Unit, onLongClick: () -> Unit) {
) {
if (row.isFolder) {
val icon = if (row.open) Icons.Filled.KeyboardArrowDown else Icons.AutoMirrored.Filled.KeyboardArrowRight
Icon(icon, contentDescription = if (row.open) "aufgeklappt" else "zugeklappt", tint = Muted, modifier = Modifier.padding(end = 10.dp))
Icon(icon, contentDescription = if (row.open) "aufgeklappt" else "zugeklappt", tint = colors.onSurfaceVariant, modifier = Modifier.padding(end = 10.dp))
} else {
Spacer(Modifier.width(34.dp))
}
@@ -211,11 +211,13 @@ fun NoteView(store: Store, id: String, modifier: Modifier = Modifier, onClose: (
}
}
val scheme = colors
val markdown = remember(scheme) { Markdown(scheme) }
Column(modifier.fillMaxSize()) {
Row(Modifier.fillMaxWidth().padding(4.dp), verticalAlignment = Alignment.CenterVertically) {
IconButton(onClick = { if (editing) stopEdit() else onClose() }) { Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Zurück") }
Text(note?.name ?: "", style = MaterialTheme.typography.titleLarge, modifier = Modifier.weight(1f))
if (editing) Text("Fertig", color = Orange, modifier = Modifier.clickable { stopEdit() }.padding(12.dp))
if (editing) Text("Fertig", color = colors.primary, modifier = Modifier.clickable { stopEdit() }.padding(12.dp))
}
if (editing) {
EditorBar { change -> value = change(value) }
@@ -223,12 +225,12 @@ fun NoteView(store: Store, id: String, modifier: Modifier = Modifier, onClose: (
value = value,
onValueChange = { value = Editor.afterInput(value, it) },
textStyle = TextStyle(color = MaterialTheme.colorScheme.onBackground, fontFamily = FontFamily.Monospace, fontSize = MaterialTheme.typography.bodyMedium.fontSize),
cursorBrush = SolidColor(Orange),
cursorBrush = SolidColor(colors.primary),
modifier = Modifier.fillMaxSize().padding(16.dp).focusRequester(focus),
)
} else {
Text(
Markdown.render(note?.content ?: ""),
markdown.render(note?.content ?: ""),
modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()).clickable { editing = true }.padding(16.dp),
)
}
@@ -241,7 +243,7 @@ private fun EditorBar(apply: ((TextFieldValue) -> TextFieldValue) -> Unit) {
Row(Modifier.fillMaxWidth().horizontalScroll(rememberScrollState()).padding(horizontal = 8.dp), verticalAlignment = Alignment.CenterVertically) {
for (level in 1..6) {
Text(
"H$level", color = Muted, fontWeight = FontWeight.Bold,
"H$level", color = colors.onSurfaceVariant, fontWeight = FontWeight.Bold,
modifier = Modifier.clickable { apply { Editor.heading(it, level) } }.padding(horizontal = 5.dp, vertical = 12.dp),
)
}
@@ -255,5 +257,5 @@ private fun EditorBar(apply: ((TextFieldValue) -> TextFieldValue) -> Unit) {
@Composable
private fun BarIcon(icon: ImageVector, label: String, onClick: () -> Unit) {
IconButton(onClick = onClick, modifier = Modifier.size(40.dp)) { Icon(icon, contentDescription = label, tint = Muted) }
IconButton(onClick = onClick, modifier = Modifier.size(40.dp)) { Icon(icon, contentDescription = label, tint = colors.onSurfaceVariant) }
}

View File

@@ -2,10 +2,12 @@ package de.marha.planer.ui
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.FilterChip
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
@@ -19,9 +21,9 @@ import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.unit.dp
import de.marha.planer.data.Sync
// Server-Zugang: URL und Token, wie sync.json auf dem Desktop.
// Server-Zugang (URL und Token, wie sync.json auf dem Desktop) und Farbschema.
@Composable
fun SettingsScreen(sync: Sync, modifier: Modifier = Modifier) {
fun SettingsScreen(sync: Sync, scheme: Scheme, onScheme: (Scheme) -> Unit, modifier: Modifier = Modifier) {
var url by remember { mutableStateOf(sync.url) }
var token by remember { mutableStateOf(sync.token) }
var saved by remember { mutableStateOf(false) }
@@ -34,11 +36,16 @@ fun SettingsScreen(sync: Sync, modifier: Modifier = Modifier) {
visualTransformation = PasswordVisualTransformation(), modifier = Modifier.fillMaxWidth(),
)
Button(onClick = { sync.configure(url, token); saved = true }) { Text("Speichern") }
if (saved) Text("Gespeichert. Abgleich läuft.", color = Muted)
if (saved) Text("Gespeichert. Abgleich läuft.", color = colors.onSurfaceVariant)
sync.status?.let { Text("Letzter Fehler: $it", color = MaterialTheme.colorScheme.error) }
Text(
"Ohne Zugang läuft die App rein lokal. Der Token steht auf dem Server in der .env und auf dem Desktop in sync.json.",
color = Muted, style = MaterialTheme.typography.bodySmall,
color = colors.onSurfaceVariant, style = MaterialTheme.typography.bodySmall,
)
Text("Farbschema", style = MaterialTheme.typography.titleLarge, modifier = Modifier.padding(top = 12.dp))
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
for (s in Scheme.entries)
FilterChip(selected = s == scheme, onClick = { onScheme(s) }, label = { Text(s.label) })
}
}
}

View File

@@ -1,31 +1,52 @@
package de.marha.planer.ui
import android.content.SharedPreferences
import androidx.compose.material3.ColorScheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
// Dunkel mit Orange, wie die Desktop-Extension
val Orange = Color(0xFFE9672E)
val Panel = Color(0xFF1C1C1C)
val Muted = Color(0xFF999999)
val NowRed = Color(0xFFFF3B30)
val TodoBlue = Color(0xFF8AB4F8)
// Farbschemata wie in Buchwerk: papier, dunkel, nacht, hell. Dunkel ist das bisherige Orange-Design.
// Nacht: reines Schwarz (OLED-Pixel aus) und dunkles Rot, das die Augen im Dunkeln kaum reizt.
enum class Scheme(val label: String) {
Papier("papier"), Dunkel("dunkel"), Nacht("nacht"), Hell("hell");
private val scheme = darkColorScheme(
primary = Orange,
onPrimary = Color.White,
secondary = TodoBlue,
background = Color(0xFF121212),
onBackground = Color(0xFFE6E6E6),
surface = Panel,
onSurface = Color(0xFFE6E6E6),
surfaceVariant = Color(0xFF2A2A2A),
onSurfaceVariant = Color(0xFFBBBBBB),
outline = Color(0xFF444444),
companion object {
fun load(prefs: SharedPreferences) = entries.firstOrNull { it.name == prefs.getString("scheme", null) } ?: Dunkel
}
}
private val Orange = Color(0xFFE9672E)
// Alle Farben stecken im Material-Schema: primary = Akzent, secondary = Todo-Blau, error = Rot,
// background = Hintergrund, surface = Panel, surfaceVariant = Karte, outline = Rand,
// onBackground = Text, onSurfaceVariant = gedämpfter Text.
private fun scheme(
dark: Boolean, bg: Long, panel: Long, card: Long, border: Long, text: Long, dim: Long,
accent: Color, onAccent: Color, red: Long, blue: Long,
): ColorScheme = (if (dark) darkColorScheme() else lightColorScheme()).copy(
primary = accent, onPrimary = onAccent, secondary = Color(blue), error = Color(red),
background = Color(bg), onBackground = Color(text), surface = Color(panel), onSurface = Color(text),
surfaceVariant = Color(card), onSurfaceVariant = Color(dim), outline = Color(border),
surfaceContainer = Color(panel), surfaceContainerHigh = Color(card),
// Auswahl in Tabs, Chips und Schaltergruppen: Karte statt Material-Lila
secondaryContainer = Color(card), onSecondaryContainer = Color(text),
)
private val schemes = mapOf(
Scheme.Dunkel to scheme(true, 0xFF121212, 0xFF1C1C1C, 0xFF2A2A2A, 0xFF444444, 0xFFE6E6E6, 0xFF999999, Orange, Color.White, 0xFFFF6B6B, 0xFF8AB4F8),
Scheme.Hell to scheme(false, 0xFFF4F6F9, 0xFFFFFFFF, 0xFFEEF1F5, 0xFFD5DBE3, 0xFF1D2531, 0xFF5F6B7C, Orange, Color.White, 0xFFC43D3D, 0xFF1B6FD6),
Scheme.Papier to scheme(false, 0xFFEFE6D2, 0xFFF8F1DE, 0xFFEEE3C8, 0xFFD8CAA6, 0xFF43392A, 0xFF6E6142, Orange, Color.White, 0xFFA6442E, 0xFF2B5AA6),
// Nacht: kein Weiß, auch nicht auf dem Akzent
Scheme.Nacht to scheme(true, 0xFF000000, 0xFF000000, 0xFF0D0303, 0xFF2A0A0A, 0xFFB03535, 0xFF6B2222, Color(0xFF7A1C1C), Color(0xFFB03535), 0xFFC0392B, 0xFF9A4A22),
)
@Composable
fun PlanerTheme(content: @Composable () -> Unit) {
MaterialTheme(colorScheme = scheme, content = content)
fun PlanerTheme(scheme: Scheme, content: @Composable () -> Unit) {
MaterialTheme(colorScheme = schemes.getValue(scheme), content = content)
}
// Kurzform in den Screens: colors.primary statt MaterialTheme.colorScheme.primary
val colors: ColorScheme @Composable get() = MaterialTheme.colorScheme

View File

@@ -38,7 +38,7 @@ fun TodosScreen(store: Store, modifier: Modifier = Modifier) {
Box(modifier.fillMaxSize()) {
if (todos.isEmpty()) {
Text("Keine Todos", color = Muted, modifier = Modifier.padding(16.dp))
Text("Keine Todos", color = colors.onSurfaceVariant, modifier = Modifier.padding(16.dp))
}
LazyColumn(Modifier.fillMaxSize()) {
items(todos, key = { it.id }) { todo ->
@@ -70,13 +70,13 @@ fun TodoRow(todo: Todo, onToggle: () -> Unit, onEdit: () -> Unit) {
Text(
todo.title.lineSequence().first(),
modifier = Modifier.weight(1f),
color = if (todo.done) Color(0xFF888888) else MaterialTheme.colorScheme.onBackground,
color = if (todo.done) colors.onSurfaceVariant else MaterialTheme.colorScheme.onBackground,
textDecoration = if (todo.done) TextDecoration.LineThrough else null,
)
val when_ = todoWhen(todo)
if (when_.isNotEmpty()) {
val overdue = !todo.done && (todo.date ?: "") < Dates.today()
Text(when_, color = if (overdue) Color(0xFFFF6B6B) else Muted, style = MaterialTheme.typography.bodySmall, modifier = Modifier.padding(end = 8.dp))
Text(when_, color = if (overdue) colors.error else colors.onSurfaceVariant, style = MaterialTheme.typography.bodySmall, modifier = Modifier.padding(end = 8.dp))
}
}
}