TaskSchema module
This commit is contained in:
@@ -22,8 +22,10 @@ import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import androidx.navigation.navArgument
|
||||
import de.haushalt.app.ui.schema.SchemaAllScreen
|
||||
import de.haushalt.app.ui.schema.SchemaCreateScreen
|
||||
import de.haushalt.app.ui.schema.SchemaEditScreen
|
||||
import de.haushalt.app.ui.task.TaskAllScreen
|
||||
import de.haushalt.app.ui.task.TaskCreateScreen
|
||||
import de.haushalt.app.ui.task.TaskEditScreen
|
||||
import de.haushalt.app.ui.task.TaskScreen
|
||||
|
||||
@@ -35,16 +37,28 @@ private val trailMap: Map<String, List<Pair<String, String>>> = mapOf(
|
||||
"tasks" to "Tasks",
|
||||
"tasks/all" to "All",
|
||||
),
|
||||
"tasks/create" to listOf(
|
||||
"start" to "Haushalt",
|
||||
"tasks" to "Tasks",
|
||||
"tasks/create" to "Create",
|
||||
),
|
||||
"tasks/{id}" to listOf(
|
||||
"start" to "Haushalt",
|
||||
"tasks" to "Tasks",
|
||||
"tasks/{id}" to "Edit",
|
||||
),
|
||||
"schemas" to listOf(
|
||||
"start" to "Haushalt",
|
||||
"tasks" to "Tasks",
|
||||
"schemas" to "Schemas",
|
||||
),
|
||||
"schemas/create" to listOf(
|
||||
"start" to "Haushalt",
|
||||
"tasks" to "Tasks",
|
||||
"schemas" to "Schemas",
|
||||
"schemas/create" to "Create",
|
||||
),
|
||||
"schemas/{id}" to listOf(
|
||||
"start" to "Haushalt",
|
||||
"tasks" to "Tasks",
|
||||
"schemas" to "Schemas",
|
||||
"schemas/{id}" to "Edit",
|
||||
),
|
||||
)
|
||||
|
||||
@Composable
|
||||
@@ -70,9 +84,6 @@ fun MainScreen() {
|
||||
composable("tasks/all") {
|
||||
TaskAllScreen(navController = navController)
|
||||
}
|
||||
composable("tasks/create") {
|
||||
TaskCreateScreen(navController = navController)
|
||||
}
|
||||
composable(
|
||||
route = "tasks/{id}",
|
||||
arguments = listOf(navArgument("id") { type = NavType.IntType })
|
||||
@@ -80,6 +91,19 @@ fun MainScreen() {
|
||||
val id = entry.arguments?.getInt("id") ?: return@composable
|
||||
TaskEditScreen(navController = navController, taskId = id)
|
||||
}
|
||||
composable("schemas") {
|
||||
SchemaAllScreen(navController = navController)
|
||||
}
|
||||
composable("schemas/create") {
|
||||
SchemaCreateScreen(navController = navController)
|
||||
}
|
||||
composable(
|
||||
route = "schemas/{id}",
|
||||
arguments = listOf(navArgument("id") { type = NavType.IntType })
|
||||
) { entry ->
|
||||
val id = entry.arguments?.getInt("id") ?: return@composable
|
||||
SchemaEditScreen(navController = navController, schemaId = id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ object ApiClient {
|
||||
.build()
|
||||
|
||||
val taskApi: TaskApi = retrofit.create(TaskApi::class.java)
|
||||
val schemaApi: TaskSchemaApi = retrofit.create(TaskSchemaApi::class.java)
|
||||
|
||||
val appUpdateApi: AppUpdateApi = Retrofit.Builder()
|
||||
.baseUrl(BASE_URL)
|
||||
|
||||
@@ -16,9 +16,6 @@ interface TaskApi {
|
||||
@GET("tasks/{id}")
|
||||
suspend fun get(@Path("id") id: Int): Task
|
||||
|
||||
@POST("tasks")
|
||||
suspend fun create(@Body body: TaskRequest): Task
|
||||
|
||||
@PUT("tasks/{id}")
|
||||
suspend fun update(@Path("id") id: Int, @Body body: TaskRequest): Task
|
||||
|
||||
|
||||
32
app/app/src/main/java/de/haushalt/app/data/TaskSchema.kt
Normal file
32
app/app/src/main/java/de/haushalt/app/data/TaskSchema.kt
Normal file
@@ -0,0 +1,32 @@
|
||||
package de.haushalt.app.data
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
|
||||
@Serializable
|
||||
enum class TaskSchemaStatus {
|
||||
active, inactive,
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class TaskSchema(
|
||||
val id: Int? = null,
|
||||
val name: String,
|
||||
val status: TaskSchemaStatus = TaskSchemaStatus.active,
|
||||
val taskStatus: TaskStatus = TaskStatus.active,
|
||||
val date: String? = null,
|
||||
val repeat: JsonObject? = null,
|
||||
val start: String? = null,
|
||||
val end: String? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class TaskSchemaRequest(
|
||||
val name: String,
|
||||
val status: TaskSchemaStatus = TaskSchemaStatus.active,
|
||||
val taskStatus: TaskStatus = TaskStatus.active,
|
||||
val date: String? = null,
|
||||
val repeat: JsonObject? = null,
|
||||
val start: String? = null,
|
||||
val end: String? = null,
|
||||
)
|
||||
26
app/app/src/main/java/de/haushalt/app/data/TaskSchemaApi.kt
Normal file
26
app/app/src/main/java/de/haushalt/app/data/TaskSchemaApi.kt
Normal file
@@ -0,0 +1,26 @@
|
||||
package de.haushalt.app.data
|
||||
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.DELETE
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.PUT
|
||||
import retrofit2.http.POST
|
||||
import retrofit2.http.Path
|
||||
import retrofit2.http.Query
|
||||
|
||||
interface TaskSchemaApi {
|
||||
@GET("task-schemas")
|
||||
suspend fun list(): List<TaskSchema>
|
||||
|
||||
@GET("task-schemas/{id}")
|
||||
suspend fun get(@Path("id") id: Int): TaskSchema
|
||||
|
||||
@POST("task-schemas")
|
||||
suspend fun create(@Body body: TaskSchemaRequest): TaskSchema
|
||||
|
||||
@PUT("task-schemas/{id}")
|
||||
suspend fun update(@Path("id") id: Int, @Body body: TaskSchemaRequest): TaskSchema
|
||||
|
||||
@DELETE("task-schemas/{id}")
|
||||
suspend fun delete(@Path("id") id: Int)
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
package de.haushalt.app.ui.schema
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material.icons.filled.Edit
|
||||
import androidx.compose.material.icons.filled.Refresh
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.text.font.FontStyle
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleEventObserver
|
||||
import androidx.lifecycle.compose.LocalLifecycleOwner
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavController
|
||||
import de.haushalt.app.data.TaskSchema
|
||||
import de.haushalt.app.data.TaskSchemaStatus
|
||||
|
||||
@Composable
|
||||
fun SchemaAllScreen(
|
||||
navController: NavController,
|
||||
viewModel: SchemaAllViewModel = viewModel(),
|
||||
) {
|
||||
val lifecycleOwner = LocalLifecycleOwner.current
|
||||
DisposableEffect(lifecycleOwner) {
|
||||
val observer = LifecycleEventObserver { _, event ->
|
||||
if (event == Lifecycle.Event.ON_RESUME) {
|
||||
viewModel.refresh()
|
||||
}
|
||||
}
|
||||
lifecycleOwner.lifecycle.addObserver(observer)
|
||||
onDispose { lifecycleOwner.lifecycle.removeObserver(observer) }
|
||||
}
|
||||
|
||||
Column(modifier = Modifier.fillMaxSize().padding(16.dp)) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.End,
|
||||
) {
|
||||
IconButton(onClick = { navController.navigate("schemas/create") }) {
|
||||
Icon(Icons.Filled.Add, contentDescription = "Neues Schema")
|
||||
}
|
||||
IconButton(onClick = { viewModel.refresh() }) {
|
||||
Icon(Icons.Filled.Refresh, contentDescription = "Neu laden")
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(Modifier.padding(4.dp))
|
||||
|
||||
when {
|
||||
viewModel.isLoading && viewModel.schemas.isEmpty() -> Text("Lädt…")
|
||||
viewModel.error != null -> Text(viewModel.error ?: "", color = MaterialTheme.colorScheme.error)
|
||||
viewModel.schemas.isEmpty() -> Text("Keine Schemas.")
|
||||
else -> LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
items(viewModel.schemas, key = { it.id ?: 0 }) { schema ->
|
||||
SchemaRow(
|
||||
schema = schema,
|
||||
onEdit = { schema.id?.let { navController.navigate("schemas/$it") } },
|
||||
onDelete = { schema.id?.let(viewModel::delete) },
|
||||
)
|
||||
HorizontalDivider()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SchemaRow(
|
||||
schema: TaskSchema,
|
||||
onEdit: () -> Unit,
|
||||
onDelete: () -> Unit,
|
||||
) {
|
||||
val repeatLabel = when {
|
||||
schema.repeat == null -> "Einmalig"
|
||||
schema.repeat.containsKey("daily") -> "Täglich"
|
||||
schema.repeat.containsKey("weekly") -> "Wöchentlich"
|
||||
schema.repeat.containsKey("monthly") -> "Monatlich"
|
||||
else -> ""
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.alpha(if (schema.status == TaskSchemaStatus.inactive) 0.5f else 1f)
|
||||
.padding(vertical = 8.dp, horizontal = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.weight(1f),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
text = schema.name,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
fontStyle = if (schema.status == TaskSchemaStatus.inactive) FontStyle.Italic else null,
|
||||
)
|
||||
Text(
|
||||
text = repeatLabel,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
IconButton(onClick = onEdit) {
|
||||
Icon(Icons.Filled.Edit, contentDescription = "Bearbeiten")
|
||||
}
|
||||
IconButton(onClick = onDelete) {
|
||||
Icon(Icons.Filled.Delete, contentDescription = "Löschen")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package de.haushalt.app.ui.schema
|
||||
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import de.haushalt.app.data.ApiClient
|
||||
import de.haushalt.app.data.TaskSchema
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class SchemaAllViewModel : ViewModel() {
|
||||
var schemas by mutableStateOf<List<TaskSchema>>(emptyList())
|
||||
private set
|
||||
var isLoading by mutableStateOf(false)
|
||||
private set
|
||||
var error by mutableStateOf<String?>(null)
|
||||
private set
|
||||
|
||||
init {
|
||||
refresh()
|
||||
}
|
||||
|
||||
fun refresh() {
|
||||
viewModelScope.launch {
|
||||
isLoading = true
|
||||
error = null
|
||||
try {
|
||||
schemas = ApiClient.schemaApi.list()
|
||||
} catch (e: Exception) {
|
||||
error = e.message
|
||||
} finally {
|
||||
isLoading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun delete(id: Int) {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
ApiClient.schemaApi.delete(id)
|
||||
schemas = schemas.filter { it.id != id }
|
||||
} catch (e: Exception) {
|
||||
error = e.message
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
package de.haushalt.app.ui.schema
|
||||
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
import androidx.compose.foundation.layout.FlowRow
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ExposedDropdownMenuBox
|
||||
import androidx.compose.material3.ExposedDropdownMenuDefaults
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.MenuAnchorType
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import de.haushalt.app.data.TaskSchemaStatus
|
||||
import de.haushalt.app.data.TaskStatus
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun SchemaStatusDropdown(
|
||||
current: TaskSchemaStatus,
|
||||
onChange: (TaskSchemaStatus) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
ExposedDropdownMenuBox(expanded = expanded, onExpandedChange = { expanded = it }, modifier = modifier) {
|
||||
OutlinedTextField(
|
||||
value = current.name,
|
||||
onValueChange = {},
|
||||
readOnly = true,
|
||||
label = { Text("Schema Status") },
|
||||
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded) },
|
||||
modifier = Modifier.fillMaxWidth().menuAnchor(MenuAnchorType.PrimaryNotEditable),
|
||||
)
|
||||
ExposedDropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) {
|
||||
TaskSchemaStatus.entries.forEach { status ->
|
||||
DropdownMenuItem(
|
||||
text = { Text(status.name) },
|
||||
onClick = { onChange(status); expanded = false },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun TaskStatusDropdown(
|
||||
current: TaskStatus,
|
||||
onChange: (TaskStatus) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
val selectable = TaskStatus.entries.filter { it != TaskStatus.past }
|
||||
ExposedDropdownMenuBox(expanded = expanded, onExpandedChange = { expanded = it }, modifier = modifier) {
|
||||
OutlinedTextField(
|
||||
value = current.name,
|
||||
onValueChange = {},
|
||||
readOnly = true,
|
||||
label = { Text("Task Status") },
|
||||
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded) },
|
||||
modifier = Modifier.fillMaxWidth().menuAnchor(MenuAnchorType.PrimaryNotEditable),
|
||||
)
|
||||
ExposedDropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) {
|
||||
selectable.forEach { status ->
|
||||
DropdownMenuItem(
|
||||
text = { Text(status.name) },
|
||||
onClick = { onChange(status); expanded = false },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun RepeatTypeDropdown(
|
||||
current: String,
|
||||
onChange: (String) -> Unit,
|
||||
) {
|
||||
val options = listOf("none" to "Keine (Einmalig)", "daily" to "Täglich", "weekly" to "Wöchentlich", "monthly" to "Monatlich")
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
ExposedDropdownMenuBox(expanded = expanded, onExpandedChange = { expanded = it }) {
|
||||
OutlinedTextField(
|
||||
value = options.firstOrNull { it.first == current }?.second ?: "",
|
||||
onValueChange = {},
|
||||
readOnly = true,
|
||||
label = { Text("Wiederholung") },
|
||||
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded) },
|
||||
modifier = Modifier.fillMaxWidth().menuAnchor(MenuAnchorType.PrimaryNotEditable),
|
||||
)
|
||||
ExposedDropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) {
|
||||
options.forEach { (key, label) ->
|
||||
DropdownMenuItem(
|
||||
text = { Text(label) },
|
||||
onClick = { onChange(key); expanded = false },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
fun WeekdaySelector(
|
||||
selected: List<Boolean>,
|
||||
onChange: (List<Boolean>) -> Unit,
|
||||
) {
|
||||
val days = listOf("Mo", "Di", "Mi", "Do", "Fr", "Sa", "So")
|
||||
Text("Wochentage", style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||
FlowRow(horizontalArrangement = Arrangement.spacedBy(6.dp)) {
|
||||
days.forEachIndexed { i, day ->
|
||||
val isSelected = selected[i]
|
||||
Surface(
|
||||
modifier = Modifier.clickable {
|
||||
onChange(selected.toMutableList().also { it[i] = !it[i] })
|
||||
},
|
||||
shape = RoundedCornerShape(6.dp),
|
||||
border = BorderStroke(1.dp, if (isSelected) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.outline),
|
||||
color = if (isSelected) MaterialTheme.colorScheme.primaryContainer else MaterialTheme.colorScheme.surface,
|
||||
) {
|
||||
Text(
|
||||
text = day,
|
||||
modifier = Modifier.padding(horizontal = 12.dp, vertical = 8.dp),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
fun MonthdaySelector(
|
||||
selected: List<Boolean>,
|
||||
onChange: (List<Boolean>) -> Unit,
|
||||
) {
|
||||
Text("Monatstage", style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||
FlowRow(
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||
) {
|
||||
for (d in 1..31) {
|
||||
val isSelected = selected[d - 1]
|
||||
Surface(
|
||||
modifier = Modifier.size(40.dp).clickable {
|
||||
onChange(selected.toMutableList().also { it[d - 1] = !it[d - 1] })
|
||||
},
|
||||
shape = RoundedCornerShape(6.dp),
|
||||
border = BorderStroke(1.dp, if (isSelected) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.outline),
|
||||
color = if (isSelected) MaterialTheme.colorScheme.primaryContainer else MaterialTheme.colorScheme.surface,
|
||||
) {
|
||||
Text(
|
||||
text = "$d",
|
||||
modifier = Modifier.padding(4.dp),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
package de.haushalt.app.ui.schema
|
||||
|
||||
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.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavController
|
||||
import de.haushalt.app.data.TaskSchemaStatus
|
||||
import de.haushalt.app.data.TaskStatus
|
||||
import de.haushalt.app.ui.task.DatePickerField
|
||||
|
||||
@Composable
|
||||
fun SchemaCreateScreen(
|
||||
navController: NavController,
|
||||
viewModel: SchemaCreateViewModel = viewModel(),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(16.dp)
|
||||
.verticalScroll(rememberScrollState()),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||
) {
|
||||
OutlinedTextField(
|
||||
value = viewModel.name,
|
||||
onValueChange = { viewModel.name = it },
|
||||
label = { Text("Name") },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
singleLine = true,
|
||||
)
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
SchemaStatusDropdown(
|
||||
current = viewModel.status,
|
||||
onChange = { viewModel.status = it },
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
TaskStatusDropdown(
|
||||
current = viewModel.taskStatus,
|
||||
onChange = { viewModel.taskStatus = it },
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
}
|
||||
|
||||
RepeatTypeDropdown(
|
||||
current = viewModel.repeatType,
|
||||
onChange = { viewModel.repeatType = it },
|
||||
)
|
||||
|
||||
if (viewModel.repeatType == "none") {
|
||||
DatePickerField(
|
||||
value = viewModel.date,
|
||||
onChange = { viewModel.date = it },
|
||||
label = "Datum",
|
||||
)
|
||||
}
|
||||
|
||||
if (viewModel.repeatType == "weekly") {
|
||||
WeekdaySelector(
|
||||
selected = viewModel.weekly,
|
||||
onChange = { viewModel.weekly = it },
|
||||
)
|
||||
}
|
||||
|
||||
if (viewModel.repeatType == "monthly") {
|
||||
MonthdaySelector(
|
||||
selected = viewModel.monthly,
|
||||
onChange = { viewModel.monthly = it },
|
||||
)
|
||||
}
|
||||
|
||||
if (viewModel.repeatType != "none") {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
DatePickerField(
|
||||
value = viewModel.start,
|
||||
onChange = { viewModel.start = it },
|
||||
label = "Start",
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
DatePickerField(
|
||||
value = viewModel.end,
|
||||
onChange = { viewModel.end = it },
|
||||
label = "Ende",
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.error?.let {
|
||||
Text(it, color = MaterialTheme.colorScheme.error)
|
||||
}
|
||||
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Button(
|
||||
enabled = !viewModel.isSubmitting && viewModel.name.isNotBlank(),
|
||||
onClick = { viewModel.save { navController.popBackStack() } },
|
||||
) {
|
||||
Text("Erstellen")
|
||||
}
|
||||
OutlinedButton(onClick = { navController.popBackStack() }) {
|
||||
Text("Abbrechen")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package de.haushalt.app.ui.schema
|
||||
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import de.haushalt.app.data.ApiClient
|
||||
import de.haushalt.app.data.TaskSchemaRequest
|
||||
import de.haushalt.app.data.TaskSchemaStatus
|
||||
import de.haushalt.app.data.TaskStatus
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import kotlinx.serialization.json.JsonPrimitive
|
||||
import kotlinx.serialization.json.buildJsonArray
|
||||
|
||||
class SchemaCreateViewModel : ViewModel() {
|
||||
var name by mutableStateOf("")
|
||||
var status by mutableStateOf(TaskSchemaStatus.active)
|
||||
var taskStatus by mutableStateOf(TaskStatus.active)
|
||||
var repeatType by mutableStateOf("none")
|
||||
var date by mutableStateOf("")
|
||||
var start by mutableStateOf("")
|
||||
var end by mutableStateOf("")
|
||||
var weekly by mutableStateOf(List(7) { false })
|
||||
var monthly by mutableStateOf(List(31) { false })
|
||||
var isSubmitting by mutableStateOf(false)
|
||||
private set
|
||||
var error by mutableStateOf<String?>(null)
|
||||
private set
|
||||
|
||||
fun save(onSuccess: () -> Unit) {
|
||||
viewModelScope.launch {
|
||||
isSubmitting = true
|
||||
error = null
|
||||
try {
|
||||
ApiClient.schemaApi.create(buildRequest())
|
||||
onSuccess()
|
||||
} catch (e: Exception) {
|
||||
error = e.message
|
||||
} finally {
|
||||
isSubmitting = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildRequest(): TaskSchemaRequest {
|
||||
val repeat = when (repeatType) {
|
||||
"daily" -> JsonObject(mapOf("daily" to JsonPrimitive(true)))
|
||||
"weekly" -> JsonObject(mapOf("weekly" to buildJsonArray { weekly.forEach { add(JsonPrimitive(it)) } }))
|
||||
"monthly" -> JsonObject(mapOf("monthly" to buildJsonArray { monthly.forEach { add(JsonPrimitive(it)) } }))
|
||||
else -> null
|
||||
}
|
||||
|
||||
return TaskSchemaRequest(
|
||||
name = name,
|
||||
status = status,
|
||||
taskStatus = taskStatus,
|
||||
date = if (repeatType == "none" && date.isNotBlank()) date else null,
|
||||
repeat = repeat,
|
||||
start = if (repeatType != "none" && start.isNotBlank()) start else null,
|
||||
end = if (repeatType != "none" && end.isNotBlank()) end else null,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
package de.haushalt.app.ui.schema
|
||||
|
||||
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.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavController
|
||||
import de.haushalt.app.ui.task.DatePickerField
|
||||
|
||||
@Composable
|
||||
fun SchemaEditScreen(
|
||||
navController: NavController,
|
||||
schemaId: Int,
|
||||
viewModel: SchemaEditViewModel = viewModel(),
|
||||
) {
|
||||
LaunchedEffect(schemaId) {
|
||||
viewModel.load(schemaId)
|
||||
}
|
||||
|
||||
if (viewModel.isLoading) {
|
||||
Text("Lädt…", modifier = Modifier.padding(16.dp))
|
||||
return
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(16.dp)
|
||||
.verticalScroll(rememberScrollState()),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||
) {
|
||||
OutlinedTextField(
|
||||
value = viewModel.name,
|
||||
onValueChange = { viewModel.name = it },
|
||||
label = { Text("Name") },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
singleLine = true,
|
||||
)
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
SchemaStatusDropdown(
|
||||
current = viewModel.status,
|
||||
onChange = { viewModel.status = it },
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
TaskStatusDropdown(
|
||||
current = viewModel.taskStatus,
|
||||
onChange = { viewModel.taskStatus = it },
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
}
|
||||
|
||||
RepeatTypeDropdown(
|
||||
current = viewModel.repeatType,
|
||||
onChange = { viewModel.repeatType = it },
|
||||
)
|
||||
|
||||
if (viewModel.repeatType == "none") {
|
||||
DatePickerField(
|
||||
value = viewModel.date,
|
||||
onChange = { viewModel.date = it },
|
||||
label = "Datum",
|
||||
)
|
||||
}
|
||||
|
||||
if (viewModel.repeatType == "weekly") {
|
||||
WeekdaySelector(
|
||||
selected = viewModel.weekly,
|
||||
onChange = { viewModel.weekly = it },
|
||||
)
|
||||
}
|
||||
|
||||
if (viewModel.repeatType == "monthly") {
|
||||
MonthdaySelector(
|
||||
selected = viewModel.monthly,
|
||||
onChange = { viewModel.monthly = it },
|
||||
)
|
||||
}
|
||||
|
||||
if (viewModel.repeatType != "none") {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
DatePickerField(
|
||||
value = viewModel.start,
|
||||
onChange = { viewModel.start = it },
|
||||
label = "Start",
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
DatePickerField(
|
||||
value = viewModel.end,
|
||||
onChange = { viewModel.end = it },
|
||||
label = "Ende",
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.error?.let {
|
||||
Text(it, color = MaterialTheme.colorScheme.error)
|
||||
}
|
||||
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Button(
|
||||
enabled = !viewModel.isSubmitting && viewModel.name.isNotBlank(),
|
||||
onClick = { viewModel.update(schemaId) { navController.popBackStack() } },
|
||||
) {
|
||||
Text("Aktualisieren")
|
||||
}
|
||||
OutlinedButton(onClick = { viewModel.reset() }) {
|
||||
Text("Zurücksetzen")
|
||||
}
|
||||
OutlinedButton(onClick = { navController.popBackStack() }) {
|
||||
Text("Abbrechen")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
package de.haushalt.app.ui.schema
|
||||
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import de.haushalt.app.data.ApiClient
|
||||
import de.haushalt.app.data.TaskSchema
|
||||
import de.haushalt.app.data.TaskSchemaRequest
|
||||
import de.haushalt.app.data.TaskSchemaStatus
|
||||
import de.haushalt.app.data.TaskStatus
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import kotlinx.serialization.json.JsonPrimitive
|
||||
import kotlinx.serialization.json.boolean
|
||||
import kotlinx.serialization.json.buildJsonArray
|
||||
import kotlinx.serialization.json.jsonArray
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
|
||||
class SchemaEditViewModel : ViewModel() {
|
||||
var name by mutableStateOf("")
|
||||
var status by mutableStateOf(TaskSchemaStatus.active)
|
||||
var taskStatus by mutableStateOf(TaskStatus.active)
|
||||
var repeatType by mutableStateOf("none")
|
||||
var date by mutableStateOf("")
|
||||
var start by mutableStateOf("")
|
||||
var end by mutableStateOf("")
|
||||
var weekly by mutableStateOf(List(7) { false })
|
||||
var monthly by mutableStateOf(List(31) { false })
|
||||
var isLoading by mutableStateOf(false)
|
||||
private set
|
||||
var isSubmitting by mutableStateOf(false)
|
||||
private set
|
||||
var error by mutableStateOf<String?>(null)
|
||||
private set
|
||||
|
||||
private var original: TaskSchema? = null
|
||||
|
||||
fun load(id: Int) {
|
||||
viewModelScope.launch {
|
||||
isLoading = true
|
||||
error = null
|
||||
try {
|
||||
val schema = ApiClient.schemaApi.get(id)
|
||||
original = schema
|
||||
applySchema(schema)
|
||||
} catch (e: Exception) {
|
||||
error = e.message
|
||||
} finally {
|
||||
isLoading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun update(id: Int, onSuccess: () -> Unit) {
|
||||
viewModelScope.launch {
|
||||
isSubmitting = true
|
||||
error = null
|
||||
try {
|
||||
ApiClient.schemaApi.update(id, buildRequest())
|
||||
onSuccess()
|
||||
} catch (e: Exception) {
|
||||
error = e.message
|
||||
} finally {
|
||||
isSubmitting = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun reset() {
|
||||
original?.let { applySchema(it) }
|
||||
}
|
||||
|
||||
private fun applySchema(schema: TaskSchema) {
|
||||
name = schema.name
|
||||
status = schema.status
|
||||
taskStatus = schema.taskStatus
|
||||
date = schema.date?.take(10) ?: ""
|
||||
start = schema.start?.take(10) ?: ""
|
||||
end = schema.end?.take(10) ?: ""
|
||||
|
||||
when {
|
||||
schema.repeat == null -> {
|
||||
repeatType = "none"
|
||||
weekly = List(7) { false }
|
||||
monthly = List(31) { false }
|
||||
}
|
||||
schema.repeat.containsKey("daily") -> {
|
||||
repeatType = "daily"
|
||||
weekly = List(7) { false }
|
||||
monthly = List(31) { false }
|
||||
}
|
||||
schema.repeat.containsKey("weekly") -> {
|
||||
repeatType = "weekly"
|
||||
weekly = schema.repeat["weekly"]!!.jsonArray.map { it.jsonPrimitive.boolean }
|
||||
monthly = List(31) { false }
|
||||
}
|
||||
schema.repeat.containsKey("monthly") -> {
|
||||
repeatType = "monthly"
|
||||
weekly = List(7) { false }
|
||||
monthly = schema.repeat["monthly"]!!.jsonArray.map { it.jsonPrimitive.boolean }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildRequest(): TaskSchemaRequest {
|
||||
val repeat = when (repeatType) {
|
||||
"daily" -> JsonObject(mapOf("daily" to JsonPrimitive(true)))
|
||||
"weekly" -> JsonObject(mapOf("weekly" to buildJsonArray { weekly.forEach { add(JsonPrimitive(it)) } }))
|
||||
"monthly" -> JsonObject(mapOf("monthly" to buildJsonArray { monthly.forEach { add(JsonPrimitive(it)) } }))
|
||||
else -> null
|
||||
}
|
||||
|
||||
return TaskSchemaRequest(
|
||||
name = name,
|
||||
status = status,
|
||||
taskStatus = taskStatus,
|
||||
date = if (repeatType == "none" && date.isNotBlank()) date else null,
|
||||
repeat = repeat,
|
||||
start = if (repeatType != "none" && start.isNotBlank()) start else null,
|
||||
end = if (repeatType != "none" && end.isNotBlank()) end else null,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.DateRange
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material.icons.filled.Edit
|
||||
import androidx.compose.material.icons.filled.Refresh
|
||||
@@ -65,8 +66,11 @@ fun TaskAllScreen(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.End,
|
||||
) {
|
||||
IconButton(onClick = { navController.navigate("tasks/create") }) {
|
||||
Icon(Icons.Filled.Add, contentDescription = "Neuer Task")
|
||||
IconButton(onClick = { navController.navigate("schemas") }) {
|
||||
Icon(Icons.Filled.DateRange, contentDescription = "Schemas")
|
||||
}
|
||||
IconButton(onClick = { navController.navigate("schemas/create") }) {
|
||||
Icon(Icons.Filled.Add, contentDescription = "Neues Schema")
|
||||
}
|
||||
IconButton(onClick = { viewModel.refresh() }) {
|
||||
Icon(Icons.Filled.Refresh, contentDescription = "Neu laden")
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
package de.haushalt.app.ui.task
|
||||
|
||||
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.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavController
|
||||
|
||||
@Composable
|
||||
fun TaskCreateScreen(
|
||||
navController: NavController,
|
||||
viewModel: TaskCreateViewModel = viewModel(),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||
) {
|
||||
OutlinedTextField(
|
||||
value = viewModel.name,
|
||||
onValueChange = { viewModel.name = it },
|
||||
label = { Text("Name") },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
singleLine = true,
|
||||
)
|
||||
|
||||
DatePickerField(
|
||||
value = viewModel.date,
|
||||
onChange = { viewModel.date = it },
|
||||
)
|
||||
|
||||
StatusDropdown(
|
||||
current = viewModel.status,
|
||||
selectable = viewModel.availableStatuses,
|
||||
onChange = { viewModel.status = it },
|
||||
)
|
||||
|
||||
viewModel.error?.let {
|
||||
Text(it, color = MaterialTheme.colorScheme.error)
|
||||
}
|
||||
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Button(
|
||||
enabled = !viewModel.isSubmitting && viewModel.name.isNotBlank(),
|
||||
onClick = { viewModel.save { navController.popBackStack() } },
|
||||
) {
|
||||
Text("Speichern")
|
||||
}
|
||||
OutlinedButton(onClick = { navController.popBackStack() }) {
|
||||
Text("Abbrechen")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
package de.haushalt.app.ui.task
|
||||
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import de.haushalt.app.data.ApiClient
|
||||
import de.haushalt.app.data.TaskRequest
|
||||
import de.haushalt.app.data.TaskStatus
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class TaskCreateViewModel : ViewModel() {
|
||||
var name by mutableStateOf("")
|
||||
var date by mutableStateOf("")
|
||||
var status by mutableStateOf(TaskStatus.active)
|
||||
var availableStatuses by mutableStateOf<List<TaskStatus>>(emptyList())
|
||||
private set
|
||||
var isSubmitting by mutableStateOf(false)
|
||||
private set
|
||||
var error by mutableStateOf<String?>(null)
|
||||
private set
|
||||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
availableStatuses = ApiClient.taskApi.statuses()
|
||||
} catch (e: Exception) {
|
||||
availableStatuses = emptyList()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun save(onSuccess: () -> Unit) {
|
||||
viewModelScope.launch {
|
||||
isSubmitting = true
|
||||
error = null
|
||||
try {
|
||||
ApiClient.taskApi.create(
|
||||
TaskRequest(
|
||||
name = name,
|
||||
date = date.ifBlank { null },
|
||||
status = status,
|
||||
)
|
||||
)
|
||||
onSuccess()
|
||||
} catch (e: Exception) {
|
||||
error = e.message
|
||||
} finally {
|
||||
isSubmitting = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.List
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.DateRange
|
||||
import androidx.compose.material.icons.filled.Refresh
|
||||
import androidx.compose.material.icons.filled.Visibility
|
||||
import androidx.compose.material.icons.filled.VisibilityOff
|
||||
@@ -66,8 +67,11 @@ fun TaskScreen(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.End,
|
||||
) {
|
||||
IconButton(onClick = { navController.navigate("tasks/create") }) {
|
||||
Icon(Icons.Filled.Add, contentDescription = "Neuer Task")
|
||||
IconButton(onClick = { navController.navigate("schemas") }) {
|
||||
Icon(Icons.Filled.DateRange, contentDescription = "Schemas")
|
||||
}
|
||||
IconButton(onClick = { navController.navigate("schemas/create") }) {
|
||||
Icon(Icons.Filled.Add, contentDescription = "Neues Schema")
|
||||
}
|
||||
IconButton(onClick = { navController.navigate("tasks/all") }) {
|
||||
Icon(Icons.AutoMirrored.Filled.List, contentDescription = "Alle Tasks")
|
||||
|
||||
Reference in New Issue
Block a user