update
This commit is contained in:
@@ -18,6 +18,8 @@ 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.runtime.mutableIntStateOf
|
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.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
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.CalendarScreen
|
||||||
import de.marha.planer.ui.NotesScreen
|
import de.marha.planer.ui.NotesScreen
|
||||||
import de.marha.planer.ui.PlanerTheme
|
import de.marha.planer.ui.PlanerTheme
|
||||||
|
import de.marha.planer.ui.Scheme
|
||||||
import de.marha.planer.ui.SettingsScreen
|
import de.marha.planer.ui.SettingsScreen
|
||||||
import de.marha.planer.ui.TodosScreen
|
import de.marha.planer.ui.TodosScreen
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@@ -40,8 +43,12 @@ class MainActivity : ComponentActivity() {
|
|||||||
val dir = File(filesDir, "data")
|
val dir = File(filesDir, "data")
|
||||||
store = Store(dir)
|
store = Store(dir)
|
||||||
sync = Sync(store, getSharedPreferences("sync", Context.MODE_PRIVATE), dir, lifecycleScope)
|
sync = Sync(store, getSharedPreferences("sync", Context.MODE_PRIVATE), dir, lifecycleScope)
|
||||||
|
val prefs = getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||||
setContent {
|
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
|
@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
|
// Ohne Server-Zugang zuerst die Einstellungen zeigen
|
||||||
var tab by rememberSaveable { mutableIntStateOf(if (sync.active) 0 else 3) }
|
var tab by rememberSaveable { mutableIntStateOf(if (sync.active) 0 else 3) }
|
||||||
Scaffold(
|
Scaffold(
|
||||||
@@ -87,7 +94,7 @@ fun App(store: Store, sync: Sync) {
|
|||||||
0 -> CalendarScreen(store, modifier)
|
0 -> CalendarScreen(store, modifier)
|
||||||
1 -> TodosScreen(store, modifier)
|
1 -> TodosScreen(store, modifier)
|
||||||
2 -> NotesScreen(store, modifier)
|
2 -> NotesScreen(store, modifier)
|
||||||
else -> SettingsScreen(sync, modifier)
|
else -> SettingsScreen(sync, scheme, onScheme, modifier)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -209,7 +209,7 @@ private fun MonthView(
|
|||||||
|
|
||||||
Column(Modifier.fillMaxSize().padding(horizontal = 4.dp)) {
|
Column(Modifier.fillMaxSize().padding(horizontal = 4.dp)) {
|
||||||
Row(Modifier.fillMaxWidth()) {
|
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) {
|
for (week in 0 until 6) {
|
||||||
Row(Modifier.fillMaxWidth().weight(1f)) {
|
Row(Modifier.fillMaxWidth().weight(1f)) {
|
||||||
@@ -219,25 +219,25 @@ private fun MonthView(
|
|||||||
val todos = store.todosOn(day)
|
val todos = store.todosOn(day)
|
||||||
Box(
|
Box(
|
||||||
Modifier.weight(1f).fillMaxSize().padding(1.dp)
|
Modifier.weight(1f).fillMaxSize().padding(1.dp)
|
||||||
.background(Color(0x0AFFFFFF), RoundedCornerShape(6.dp))
|
.background(colors.onBackground.copy(alpha = 0.04f), RoundedCornerShape(6.dp))
|
||||||
.then(if (day == today) Modifier.border(1.dp, Orange, RoundedCornerShape(6.dp)) else Modifier)
|
.then(if (day == today) Modifier.border(1.dp, colors.primary, RoundedCornerShape(6.dp)) else Modifier)
|
||||||
.combinedClickable(onClick = { onDay(day) }, onLongClick = { menuDay = day })
|
.combinedClickable(onClick = { onDay(day) }, onLongClick = { menuDay = day })
|
||||||
.padding(3.dp),
|
.padding(3.dp),
|
||||||
) {
|
) {
|
||||||
Column {
|
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) } } +
|
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) } }
|
todos.map { t -> ((if (t.done) "☑ " else "☐ ") + (t.time?.let { "$it " } ?: "") + t.title) to { onTodo(t) } }
|
||||||
entries.take(2).forEachIndexed { index, (text, click) ->
|
entries.take(2).forEachIndexed { index, (text, click) ->
|
||||||
val isTodo = index >= events.size
|
val isTodo = index >= events.size
|
||||||
Text(
|
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()
|
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),
|
.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 }) {
|
DropdownMenu(expanded = menuDay == day, onDismissRequest = { menuDay = null }) {
|
||||||
DropdownMenuItem(text = { Text("Termin erstellen") }, onClick = { onNewEvent(day); menuDay = null })
|
DropdownMenuItem(text = { Text("Termin erstellen") }, onClick = { onNewEvent(day); menuDay = null })
|
||||||
@@ -272,21 +272,21 @@ private fun WeekView(
|
|||||||
|
|
||||||
Column(Modifier.fillMaxSize()) {
|
Column(Modifier.fillMaxSize()) {
|
||||||
Row(Modifier.fillMaxWidth().padding(top = 4.dp)) {
|
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) {
|
for (day in days) {
|
||||||
val d = LocalDate.parse(day)
|
val d = LocalDate.parse(day)
|
||||||
Text("${Dates.WEEKDAYS[d.dayOfWeek.value - 1]} ${d.dayOfMonth}.", Modifier.weight(1f), fontSize = 11.sp,
|
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)
|
textAlign = androidx.compose.ui.text.style.TextAlign.Center)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Ganztags-Zeile: Blöcke, darunter Todos ohne Uhrzeit. Tipp legt einen ganztägigen Termin an.
|
// Ganztags-Zeile: Blöcke, darunter Todos ohne Uhrzeit. Tipp legt einen ganztägigen Termin an.
|
||||||
Row(Modifier.fillMaxWidth().padding(vertical = 2.dp)) {
|
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))) {
|
BoxWithConstraints(Modifier.weight(1f).height(maxOf(22.dp, ALLDAY_LANE * lanes + 4.dp))) {
|
||||||
val colWidth = maxWidth / 7
|
val colWidth = maxWidth / 7
|
||||||
Row(Modifier.fillMaxSize()) {
|
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) })
|
detectTapGestures(onTap = { onTap(day, null) }, onLongPress = { onLongPress(day, null, it) })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -294,8 +294,8 @@ private fun WeekView(
|
|||||||
Box(
|
Box(
|
||||||
Modifier.offset(x = colWidth * lane.from, y = ALLDAY_LANE * lane.lane)
|
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)
|
.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),
|
.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 = Color.White, maxLines = 1, overflow = TextOverflow.Ellipsis) }
|
) { 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) {
|
for (hour in 0 until 24) {
|
||||||
Row(Modifier.fillMaxWidth().height(HOUR_HEIGHT)) {
|
Row(Modifier.fillMaxWidth().height(HOUR_HEIGHT)) {
|
||||||
Text("${pad(hour)}:00", Modifier.width(HOUR_LABEL_WIDTH).padding(top = 2.dp), color = Muted, fontSize = 9.sp)
|
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(Color(0x0AFFFFFF), RoundedCornerShape(3.dp)))
|
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(
|
Box(
|
||||||
Modifier.offset(x = HOUR_LABEL_WIDTH + colWidth * dayIndex + width * block.col, y = HOUR_HEIGHT * block.from / 60f)
|
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)
|
.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))
|
.background(colors.primary.copy(alpha = 0.8f), RoundedCornerShape(4.dp))
|
||||||
.border(1.dp, Color(0x40FFFFFF), 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),
|
.clickable { onEvent(block.event) }.padding(horizontal = 3.dp, vertical = 1.dp),
|
||||||
) {
|
) {
|
||||||
Column {
|
Column {
|
||||||
Text(if (day == block.event.date) block.event.start!! else "…", fontSize = 8.sp, color = Color(0xDDFFFFFF))
|
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 = Color.White, fontWeight = FontWeight.Bold, overflow = TextOverflow.Clip)
|
Text(block.event.title, fontSize = 10.sp, color = colors.onPrimary, fontWeight = FontWeight.Bold, overflow = TextOverflow.Clip)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -348,14 +348,14 @@ private fun WeekView(
|
|||||||
Box(
|
Box(
|
||||||
Modifier.offset(x = HOUR_LABEL_WIDTH + colWidth * dayIndex, y = HOUR_HEIGHT * Dates.timeToMinutes(todo.time!!) / 60f)
|
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)
|
.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),
|
.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 = Color.White, maxLines = 1, overflow = TextOverflow.Ellipsis) }
|
) { 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
|
// Rote Linie an der aktuellen Uhrzeit in der Spalte von heute
|
||||||
if (todayIndex >= 0) {
|
if (todayIndex >= 0) {
|
||||||
val now = LocalTime.now()
|
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)
|
val todos = store.todosOn(day)
|
||||||
LazyColumn(Modifier.fillMaxSize().padding(horizontal = 8.dp)) {
|
LazyColumn(Modifier.fillMaxSize().padding(horizontal = 8.dp)) {
|
||||||
item { SectionHeader("Termine", "+ Termin", onNewEvent) }
|
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 ->
|
items(events, key = { it.id }) { event ->
|
||||||
Row(
|
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),
|
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)
|
Text(event.title, fontWeight = FontWeight.Bold)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
item { SectionHeader("Todos", "+ Todo", onNewTodo) }
|
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) }) }
|
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
|
@Composable
|
||||||
private fun SectionHeader(title: String, buttonLabel: String, onClick: () -> Unit) {
|
private fun SectionHeader(title: String, buttonLabel: String, onClick: () -> Unit) {
|
||||||
Row(Modifier.fillMaxWidth().padding(top = 8.dp), verticalAlignment = Alignment.CenterVertically) {
|
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) }
|
TextButton(onClick = onClick) { Text(buttonLabel) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,12 +88,12 @@ fun EventDialog(store: Store, event: Event, onDismiss: () -> Unit) {
|
|||||||
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
TextInput("Titel", title) { title = it }
|
TextInput("Titel", title) { title = it }
|
||||||
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(6.dp)) {
|
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 }
|
DateField(date, allowNone = false) { date = it ?: date }
|
||||||
TimeField(start) { start = it }
|
TimeField(start) { start = it }
|
||||||
}
|
}
|
||||||
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(6.dp)) {
|
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 }
|
DateField(endDate ?: date, allowNone = false) { endDate = it }
|
||||||
TimeField(end) { end = it }
|
TimeField(end) { end = it }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ fun NotesScreen(store: Store, modifier: Modifier = Modifier) {
|
|||||||
collect(store.tree(), 0)
|
collect(store.tree(), 0)
|
||||||
|
|
||||||
Box(modifier.fillMaxSize()) {
|
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()) {
|
LazyColumn(Modifier.fillMaxSize()) {
|
||||||
items(rows, key = { (if (it.isFolder) "f:" else "n:") + it.path }) { row ->
|
items(rows, key = { (if (it.isFolder) "f:" else "n:") + it.path }) { row ->
|
||||||
Box {
|
Box {
|
||||||
@@ -170,7 +170,7 @@ private fun TreeRow(row: Row, onClick: () -> Unit, onLongClick: () -> Unit) {
|
|||||||
) {
|
) {
|
||||||
if (row.isFolder) {
|
if (row.isFolder) {
|
||||||
val icon = if (row.open) Icons.Filled.KeyboardArrowDown else Icons.AutoMirrored.Filled.KeyboardArrowRight
|
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 {
|
} else {
|
||||||
Spacer(Modifier.width(34.dp))
|
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()) {
|
Column(modifier.fillMaxSize()) {
|
||||||
Row(Modifier.fillMaxWidth().padding(4.dp), verticalAlignment = Alignment.CenterVertically) {
|
Row(Modifier.fillMaxWidth().padding(4.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||||
IconButton(onClick = { if (editing) stopEdit() else onClose() }) { Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Zurück") }
|
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))
|
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) {
|
if (editing) {
|
||||||
EditorBar { change -> value = change(value) }
|
EditorBar { change -> value = change(value) }
|
||||||
@@ -223,12 +225,12 @@ fun NoteView(store: Store, id: String, modifier: Modifier = Modifier, onClose: (
|
|||||||
value = value,
|
value = value,
|
||||||
onValueChange = { value = Editor.afterInput(value, it) },
|
onValueChange = { value = Editor.afterInput(value, it) },
|
||||||
textStyle = TextStyle(color = MaterialTheme.colorScheme.onBackground, fontFamily = FontFamily.Monospace, fontSize = MaterialTheme.typography.bodyMedium.fontSize),
|
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),
|
modifier = Modifier.fillMaxSize().padding(16.dp).focusRequester(focus),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Text(
|
Text(
|
||||||
Markdown.render(note?.content ?: ""),
|
markdown.render(note?.content ?: ""),
|
||||||
modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()).clickable { editing = true }.padding(16.dp),
|
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) {
|
Row(Modifier.fillMaxWidth().horizontalScroll(rememberScrollState()).padding(horizontal = 8.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||||
for (level in 1..6) {
|
for (level in 1..6) {
|
||||||
Text(
|
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),
|
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
|
@Composable
|
||||||
private fun BarIcon(icon: ImageVector, label: String, onClick: () -> Unit) {
|
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) }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,10 +2,12 @@ package de.marha.planer.ui
|
|||||||
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
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.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.FilterChip
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.OutlinedTextField
|
import androidx.compose.material3.OutlinedTextField
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
@@ -19,9 +21,9 @@ import androidx.compose.ui.text.input.PasswordVisualTransformation
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import de.marha.planer.data.Sync
|
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
|
@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 url by remember { mutableStateOf(sync.url) }
|
||||||
var token by remember { mutableStateOf(sync.token) }
|
var token by remember { mutableStateOf(sync.token) }
|
||||||
var saved by remember { mutableStateOf(false) }
|
var saved by remember { mutableStateOf(false) }
|
||||||
@@ -34,11 +36,16 @@ fun SettingsScreen(sync: Sync, modifier: Modifier = Modifier) {
|
|||||||
visualTransformation = PasswordVisualTransformation(), modifier = Modifier.fillMaxWidth(),
|
visualTransformation = PasswordVisualTransformation(), modifier = Modifier.fillMaxWidth(),
|
||||||
)
|
)
|
||||||
Button(onClick = { sync.configure(url, token); saved = true }) { Text("Speichern") }
|
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) }
|
sync.status?.let { Text("Letzter Fehler: $it", color = MaterialTheme.colorScheme.error) }
|
||||||
Text(
|
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.",
|
"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) })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,31 +1,52 @@
|
|||||||
package de.marha.planer.ui
|
package de.marha.planer.ui
|
||||||
|
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import androidx.compose.material3.ColorScheme
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.darkColorScheme
|
import androidx.compose.material3.darkColorScheme
|
||||||
|
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
|
||||||
|
|
||||||
// Dunkel mit Orange, wie die Desktop-Extension
|
// Farbschemata wie in Buchwerk: papier, dunkel, nacht, hell. Dunkel ist das bisherige Orange-Design.
|
||||||
val Orange = Color(0xFFE9672E)
|
// Nacht: reines Schwarz (OLED-Pixel aus) und dunkles Rot, das die Augen im Dunkeln kaum reizt.
|
||||||
val Panel = Color(0xFF1C1C1C)
|
enum class Scheme(val label: String) {
|
||||||
val Muted = Color(0xFF999999)
|
Papier("papier"), Dunkel("dunkel"), Nacht("nacht"), Hell("hell");
|
||||||
val NowRed = Color(0xFFFF3B30)
|
|
||||||
val TodoBlue = Color(0xFF8AB4F8)
|
|
||||||
|
|
||||||
private val scheme = darkColorScheme(
|
companion object {
|
||||||
primary = Orange,
|
fun load(prefs: SharedPreferences) = entries.firstOrNull { it.name == prefs.getString("scheme", null) } ?: Dunkel
|
||||||
onPrimary = Color.White,
|
}
|
||||||
secondary = TodoBlue,
|
}
|
||||||
background = Color(0xFF121212),
|
|
||||||
onBackground = Color(0xFFE6E6E6),
|
private val Orange = Color(0xFFE9672E)
|
||||||
surface = Panel,
|
|
||||||
onSurface = Color(0xFFE6E6E6),
|
// Alle Farben stecken im Material-Schema: primary = Akzent, secondary = Todo-Blau, error = Rot,
|
||||||
surfaceVariant = Color(0xFF2A2A2A),
|
// background = Hintergrund, surface = Panel, surfaceVariant = Karte, outline = Rand,
|
||||||
onSurfaceVariant = Color(0xFFBBBBBB),
|
// onBackground = Text, onSurfaceVariant = gedämpfter Text.
|
||||||
outline = Color(0xFF444444),
|
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
|
@Composable
|
||||||
fun PlanerTheme(content: @Composable () -> Unit) {
|
fun PlanerTheme(scheme: Scheme, content: @Composable () -> Unit) {
|
||||||
MaterialTheme(colorScheme = scheme, content = content)
|
MaterialTheme(colorScheme = schemes.getValue(scheme), content = content)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Kurzform in den Screens: colors.primary statt MaterialTheme.colorScheme.primary
|
||||||
|
val colors: ColorScheme @Composable get() = MaterialTheme.colorScheme
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ fun TodosScreen(store: Store, modifier: Modifier = Modifier) {
|
|||||||
|
|
||||||
Box(modifier.fillMaxSize()) {
|
Box(modifier.fillMaxSize()) {
|
||||||
if (todos.isEmpty()) {
|
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()) {
|
LazyColumn(Modifier.fillMaxSize()) {
|
||||||
items(todos, key = { it.id }) { todo ->
|
items(todos, key = { it.id }) { todo ->
|
||||||
@@ -70,13 +70,13 @@ fun TodoRow(todo: Todo, onToggle: () -> Unit, onEdit: () -> Unit) {
|
|||||||
Text(
|
Text(
|
||||||
todo.title.lineSequence().first(),
|
todo.title.lineSequence().first(),
|
||||||
modifier = Modifier.weight(1f),
|
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,
|
textDecoration = if (todo.done) TextDecoration.LineThrough else null,
|
||||||
)
|
)
|
||||||
val when_ = todoWhen(todo)
|
val when_ = todoWhen(todo)
|
||||||
if (when_.isNotEmpty()) {
|
if (when_.isNotEmpty()) {
|
||||||
val overdue = !todo.done && (todo.date ?: "") < Dates.today()
|
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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user