From 025e297153dc2da8c5cf3dde02be7306aaaca885 Mon Sep 17 00:00:00 2001 From: Xaver Date: Wed, 20 Nov 2024 17:24:36 +0100 Subject: [PATCH] idk why its not working --- .idea/misc.xml | 1 - app/build.gradle.kts | 6 + .../at/xaxa/demonstrator2/MainActivity.kt | 50 +---- .../at/xaxa/demonstrator2/TaskApplication.kt | 24 +++ .../java/at/xaxa/demonstrator2/data/Task.kt | 7 + .../xaxa/demonstrator2/data/TaskRepository.kt | 42 +++++ .../at/xaxa/demonstrator2/data/db/TaskDao.kt | 26 +++ .../demonstrator2/data/db/TaskDatabase.kt | 33 ++++ .../xaxa/demonstrator2/data/db/TaskEntity.kt | 10 + .../demonstrator2/ui/AppViewModelProvider.kt | 23 +++ .../demonstrator2/ui/DemoDetailsViewModel.kt | 37 ++++ .../java/at/xaxa/demonstrator2/ui/DemoUI.kt | 177 ++++++++++++++++++ .../at/xaxa/demonstrator2/ui/DemoUiState.kt | 7 + .../at/xaxa/demonstrator2/ui/DemoViewModel.kt | 33 ++++ .../demonstrator2/ui/edit/TaskEditScreen.kt | 78 ++++++++ .../ui/edit/TaskEditViewModel.kt | 47 +++++ gradle/libs.versions.toml | 9 + 17 files changed, 561 insertions(+), 49 deletions(-) create mode 100644 app/src/main/java/at/xaxa/demonstrator2/TaskApplication.kt create mode 100644 app/src/main/java/at/xaxa/demonstrator2/data/Task.kt create mode 100644 app/src/main/java/at/xaxa/demonstrator2/data/TaskRepository.kt create mode 100644 app/src/main/java/at/xaxa/demonstrator2/data/db/TaskDao.kt create mode 100644 app/src/main/java/at/xaxa/demonstrator2/data/db/TaskDatabase.kt create mode 100644 app/src/main/java/at/xaxa/demonstrator2/data/db/TaskEntity.kt create mode 100644 app/src/main/java/at/xaxa/demonstrator2/ui/AppViewModelProvider.kt create mode 100644 app/src/main/java/at/xaxa/demonstrator2/ui/DemoDetailsViewModel.kt create mode 100644 app/src/main/java/at/xaxa/demonstrator2/ui/DemoUI.kt create mode 100644 app/src/main/java/at/xaxa/demonstrator2/ui/DemoUiState.kt create mode 100644 app/src/main/java/at/xaxa/demonstrator2/ui/DemoViewModel.kt create mode 100644 app/src/main/java/at/xaxa/demonstrator2/ui/edit/TaskEditScreen.kt create mode 100644 app/src/main/java/at/xaxa/demonstrator2/ui/edit/TaskEditViewModel.kt diff --git a/.idea/misc.xml b/.idea/misc.xml index 0ad17cb..8978d23 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,3 @@ - diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 5078d26..b6f5a8d 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -53,6 +53,10 @@ dependencies { implementation(libs.androidx.navigation.compose) implementation(libs.androidx.material) + implementation(libs.converter.kotlinx.serialization) + implementation(libs.retrofit) + implementation(libs.kotlinx.serialization.json) + implementation(libs.androidx.core.ktx) implementation(libs.androidx.lifecycle.runtime.ktx) implementation(libs.androidx.activity.compose) @@ -62,6 +66,8 @@ dependencies { implementation(libs.androidx.ui.tooling.preview) implementation(libs.androidx.material3) implementation(libs.androidx.legacy.support.v4) + implementation(libs.androidx.room.common) + implementation(libs.androidx.room.ktx) testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) diff --git a/app/src/main/java/at/xaxa/demonstrator2/MainActivity.kt b/app/src/main/java/at/xaxa/demonstrator2/MainActivity.kt index ee19c4c..ba565fb 100644 --- a/app/src/main/java/at/xaxa/demonstrator2/MainActivity.kt +++ b/app/src/main/java/at/xaxa/demonstrator2/MainActivity.kt @@ -24,6 +24,7 @@ import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController +import at.xaxa.demonstrator2.ui.DemoApp import at.xaxa.demonstrator2.ui.theme.Demonstrator2Theme class MainActivity : ComponentActivity() { @@ -32,7 +33,7 @@ class MainActivity : ComponentActivity() { enableEdgeToEdge() setContent { Demonstrator2Theme { - NavigationBar() + DemoApp() } } } @@ -53,50 +54,3 @@ fun GreetingPreview() { Greeting("Android") } } - -@Composable -fun NavigationBar() { - val navController = rememberNavController() - - Scaffold( - bottomBar = { - NavigationBar { - NavigationBarItem( - icon = { Icon(Icons.Default.Home, contentDescription = "Home") }, - label = { Text("Home") }, - selected = navController.currentBackStackEntryAsState().value?.destination?.route == "home", - onClick = { navController.navigate("home") } - ) - NavigationBarItem( - icon = { Icon(Icons.Default.Favorite, contentDescription = "Favorites") }, - label = { Text("Favorites") }, - selected = navController.currentBackStackEntryAsState().value?.destination?.route == "favorites", - onClick = { navController.navigate("favorites") } - ) - } - } - ) { innerPadding -> - NavHost( - navController = navController, - startDestination = "home", - modifier = Modifier.padding(innerPadding) - ) { - composable("home") { HomeScreen() } - composable("favorites") { FavoritesScreen() } - } - } -} - -@Composable -fun HomeScreen() { - Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { - Text("Home Screen") - } -} - -@Composable -fun FavoritesScreen() { - Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { - Text("Favorites Screen") - } -} \ No newline at end of file diff --git a/app/src/main/java/at/xaxa/demonstrator2/TaskApplication.kt b/app/src/main/java/at/xaxa/demonstrator2/TaskApplication.kt new file mode 100644 index 0000000..99f11e8 --- /dev/null +++ b/app/src/main/java/at/xaxa/demonstrator2/TaskApplication.kt @@ -0,0 +1,24 @@ +package at.xaxa.demonstrator2 + +import android.app.Application +import at.xaxa.demonstrator2.data.TaskRepository +import at.xaxa.demonstrator2.data.db.TaskDatabase +import kotlinx.serialization.json.Json +import okhttp3.MediaType +import retrofit2.Retrofit +import retrofit2.converter.kotlinx.serialization.asConverterFactory + + +class TaskApplication : Application() { + + val taskRepository by lazy { + val retrofit = Retrofit.Builder() + .baseUrl("https://my-json-server.typicode.com/GithubGenericUsername/find-your-pet/") + .addConverterFactory(Json { ignoreUnknownKeys = true }.asConverterFactory(MediaType.get("application/json"))) + .build() + + TaskRepository( + TaskDatabase.getDatabase(this).TaskDao() + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/at/xaxa/demonstrator2/data/Task.kt b/app/src/main/java/at/xaxa/demonstrator2/data/Task.kt new file mode 100644 index 0000000..05f52b7 --- /dev/null +++ b/app/src/main/java/at/xaxa/demonstrator2/data/Task.kt @@ -0,0 +1,7 @@ +package at.xaxa.demonstrator2.data + +data class Task( + val id: Int, + val name :String, + val details: String +); diff --git a/app/src/main/java/at/xaxa/demonstrator2/data/TaskRepository.kt b/app/src/main/java/at/xaxa/demonstrator2/data/TaskRepository.kt new file mode 100644 index 0000000..21bb475 --- /dev/null +++ b/app/src/main/java/at/xaxa/demonstrator2/data/TaskRepository.kt @@ -0,0 +1,42 @@ +package at.xaxa.demonstrator2.data + +import android.util.Log +import at.xaxa.demonstrator2.data.db.TaskDao +import at.xaxa.demonstrator2.data.db.TaskEntity +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +class TaskRepository(private val taskDao: TaskDao) { + + fun getAllTasks(): Flow> { + return taskDao.getAllTasks().map { + it.map {item -> Task(item._id, item.name, item.details) } + } + } + + suspend fun getTaskById(id: Int): Task { + val item = taskDao.getTaskById(id) + return Task(item._id, item.name, item.details) + } + + suspend fun addTask(task: Task) { + taskDao.addTask(TaskEntity(_id=0, task.name, task.details)) + } + + suspend fun updateTask(task: Task) { + taskDao.updateTask(TaskEntity(task.id, task.name, task.details)) + } + + suspend fun addRandomTask() { + val randomTask:String = tasks.random() + addTask(Task(0, randomTask, "Wow I have to ${randomTask.lowercase()}")) + } + + val tasks = listOf( + "Buy Milk", + "Mow lawn", + "Chill", + "Drink Water" + ) + +} \ No newline at end of file diff --git a/app/src/main/java/at/xaxa/demonstrator2/data/db/TaskDao.kt b/app/src/main/java/at/xaxa/demonstrator2/data/db/TaskDao.kt new file mode 100644 index 0000000..34db331 --- /dev/null +++ b/app/src/main/java/at/xaxa/demonstrator2/data/db/TaskDao.kt @@ -0,0 +1,26 @@ +package at.xaxa.demonstrator2.data.db + +import androidx.room.Dao +import androidx.room.Delete +import androidx.room.Insert +import androidx.room.Query +import androidx.room.Update +import kotlinx.coroutines.flow.Flow + +@Dao +interface TaskDao { + @Insert + suspend fun addTask(taskEntity: TaskEntity) + + @Update + suspend fun updateTask(taskEntity: TaskEntity) + + @Delete + suspend fun deleteTask(taskEntity: TaskEntity) + + @Query("SELECT * from tasks") + fun getAllTasks(): Flow> + + @Query("SELECT * FROM tasks WHERE _id = :id") + suspend fun getTaskById(id: Int): TaskEntity +} \ No newline at end of file diff --git a/app/src/main/java/at/xaxa/demonstrator2/data/db/TaskDatabase.kt b/app/src/main/java/at/xaxa/demonstrator2/data/db/TaskDatabase.kt new file mode 100644 index 0000000..1b1eabd --- /dev/null +++ b/app/src/main/java/at/xaxa/demonstrator2/data/db/TaskDatabase.kt @@ -0,0 +1,33 @@ +package at.xaxa.demonstrator2.data.db + +import android.content.Context +import androidx.room.Database +import androidx.room.Room +import androidx.room.RoomDatabase + +@Database(entities = [TaskEntity::class], version = 1) +abstract class TaskDatabase : RoomDatabase() { + abstract fun TaskDao(): TaskDao + + companion object { + @Volatile + private var Instance: TaskDatabase? = null + + fun getDatabase(context: Context): TaskDatabase { + // if the Instance is not null, return it, otherwise create a new database instance. + return Instance ?: synchronized(this) { + val instance = Room.databaseBuilder(context, TaskDatabase::class.java, "task_database") + /** + * Setting this option in your app's database builder means that Room + * permanently deletes all data from the tables in your database when it + * attempts to perform a migration with no defined migration path. + */ + .fallbackToDestructiveMigration() + .build() + Instance = instance + return instance + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/at/xaxa/demonstrator2/data/db/TaskEntity.kt b/app/src/main/java/at/xaxa/demonstrator2/data/db/TaskEntity.kt new file mode 100644 index 0000000..16d2e0f --- /dev/null +++ b/app/src/main/java/at/xaxa/demonstrator2/data/db/TaskEntity.kt @@ -0,0 +1,10 @@ +package at.xaxa.demonstrator2.data.db + +import androidx.room.Entity + +@Entity(tableName = "tasks") +data class TaskEntity( + val _id: Int = 0, + val name :String, + val details: String = "empty" +) diff --git a/app/src/main/java/at/xaxa/demonstrator2/ui/AppViewModelProvider.kt b/app/src/main/java/at/xaxa/demonstrator2/ui/AppViewModelProvider.kt new file mode 100644 index 0000000..06faf4b --- /dev/null +++ b/app/src/main/java/at/xaxa/demonstrator2/ui/AppViewModelProvider.kt @@ -0,0 +1,23 @@ +package at.xaxa.demonstrator2.ui + +import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY +import androidx.lifecycle.createSavedStateHandle +import androidx.lifecycle.viewmodel.initializer +import androidx.lifecycle.viewmodel.viewModelFactory +import at.xaxa.demonstrator2.TaskApplication + +object AppViewModelProvider { + val Factory = viewModelFactory { + initializer { + DemoViewModel((this[APPLICATION_KEY] as TaskApplication).taskRepository) + } + + initializer { + DemoDetailsViewModel(this.createSavedStateHandle(), (this[APPLICATION_KEY] as TaskApplication).taskRepository) + } + + initializer { + DemoDetailsViewModel(this.createSavedStateHandle(), (this[APPLICATION_KEY] as TaskApplication).taskRepository) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/at/xaxa/demonstrator2/ui/DemoDetailsViewModel.kt b/app/src/main/java/at/xaxa/demonstrator2/ui/DemoDetailsViewModel.kt new file mode 100644 index 0000000..9a0d00f --- /dev/null +++ b/app/src/main/java/at/xaxa/demonstrator2/ui/DemoDetailsViewModel.kt @@ -0,0 +1,37 @@ +package at.xaxa.demonstrator2.ui + +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import at.xaxa.demonstrator2.data.Task +import at.xaxa.demonstrator2.data.TaskRepository +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +data class TaskDetailUi( + val task: Task = Task(0, "", "") +) + +class DemoDetailsViewModel(savedStateHandle: SavedStateHandle, private val taskRepository: TaskRepository): ViewModel() { + + private val taskId: Int = checkNotNull(savedStateHandle["taskId"]) + + private val _detailUiState = MutableStateFlow(TaskDetailUi()) + val detailUiState = _detailUiState.asStateFlow() + + init { + viewModelScope.launch { + val task = withContext(Dispatchers.IO) { + taskRepository.getTaskById(taskId) + } + _detailUiState.update { + TaskDetailUi(task) + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/at/xaxa/demonstrator2/ui/DemoUI.kt b/app/src/main/java/at/xaxa/demonstrator2/ui/DemoUI.kt new file mode 100644 index 0000000..13f2434 --- /dev/null +++ b/app/src/main/java/at/xaxa/demonstrator2/ui/DemoUI.kt @@ -0,0 +1,177 @@ +package at.xaxa.demonstrator2.ui + +import androidx.compose.foundation.layout.Box +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.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed +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.outlined.Edit +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.NavigationBarItem +import androidx.compose.material3.OutlinedCard +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavType +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.currentBackStackEntryAsState +import androidx.navigation.compose.rememberNavController +import androidx.navigation.navArgument +import at.xaxa.demonstrator2.data.Task +import at.xaxa.demonstrator2.ui.edit.TaskEditScreen +import at.xaxa.demonstrator2.ui.theme.Typography + +enum class DemoRoutes(val route: String) { + List("list"), + Add("add"), + Detail("task/{taskId}"), + Edit("task/{taskId}/edit") +} + +@Composable +fun DemoApp(modifier: Modifier = Modifier) { + val navController = rememberNavController() + + Scaffold( + bottomBar = { + androidx.compose.material3.NavigationBar { + NavigationBarItem( + icon = { Icon(Icons.AutoMirrored.Filled.List, contentDescription = "List") }, + label = { Text("List") }, + selected = navController.currentBackStackEntryAsState().value?.destination?.route == "list", + onClick = { navController.navigate(DemoRoutes.List.route) } + ) + NavigationBarItem( + icon = { Icon(Icons.Default.Add, contentDescription = "Add") }, + label = { Text("Add") }, + selected = navController.currentBackStackEntryAsState().value?.destination?.route == "add", + onClick = { navController.navigate(DemoRoutes.Add.route) } + ) + } + } + ) { innerPadding -> + NavHost( + navController = navController, + startDestination = DemoRoutes.List.route, + modifier = Modifier.padding(innerPadding) + ) { + composable(DemoRoutes.List.route){ + ListScreen( onEditClick = { + navController.navigate(DemoRoutes.Edit.route.replace("{taskId}", "$it")) + }){ + navController.navigate(DemoRoutes.Detail.route.replace("{taskId}", "$it")) + } + } + composable(DemoRoutes.Add.route) { AddScreen() } + composable( + route = DemoRoutes.Detail.route, + arguments = listOf(navArgument("taskId") { + type = NavType.IntType + }) + ) { + TaskDetailsScreen() + } + composable( + route = DemoRoutes.Edit.route, + arguments = listOf(navArgument("contactId") { + type = NavType.IntType + }) + ) { + TaskEditScreen() { + navController.navigateUp() + } + } + } + } +} + +@Composable +fun ListScreen( + modifier: Modifier = Modifier, + demoViewModel: DemoViewModel = viewModel(factory = AppViewModelProvider.Factory), + onEditClick: (Int) -> Unit, + onCardClick: (Int) -> Unit +) { + Box(Modifier.fillMaxSize()) { + val state by demoViewModel.taskUiState.collectAsStateWithLifecycle(); + + LazyColumn { + itemsIndexed(state.tasks) { index, task -> + TaskListItem(task, onCardClick = { + onCardClick(task.id) + }, onEditClick = { + onEditClick(task.id) + }) + } + } + } +} + +@Composable +fun TaskDetailsScreen(modifier: Modifier = Modifier, demoDetailsViewModel: DemoDetailsViewModel = viewModel(factory = AppViewModelProvider.Factory)) { + val detailUiState by demoDetailsViewModel.detailUiState.collectAsStateWithLifecycle() + + TaskDetails(detailUiState.task, modifier) +} + +@Composable +fun TaskDetails(task: Task, modifier: Modifier = Modifier) { + OutlinedCard( + modifier + .fillMaxWidth() + .padding(8.dp) + ) { + Column(Modifier.padding(16.dp)) { + Text(task.name, style = Typography.headlineMedium) + Row { + Text("Details: ${task.details}", style = Typography.headlineMedium) + } + } + } +} + +@Composable +fun AddScreen() { + Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + Text("Add Screen") + } +} + +@Composable +fun TaskListItem(task: Task, onCardClick: () -> Unit, onEditClick: ()->Unit, modifier: Modifier = Modifier) { + OutlinedCard( + onClick = { onCardClick() }, modifier = modifier + .fillMaxWidth() + .padding(8.dp) + ) { + Row(Modifier.padding(16.dp), verticalAlignment = Alignment.CenterVertically) { + Text(task.name, style = Typography.headlineMedium) + IconButton(onEditClick) { + Icon(Icons.Outlined.Edit, "Edit contact") + } + } + } +} + +@Preview +@Composable +fun PreviewTaskListItem(){ + TaskListItem(Task(0, "Buy Milk", "buy milk"), {}, {}) +} \ No newline at end of file diff --git a/app/src/main/java/at/xaxa/demonstrator2/ui/DemoUiState.kt b/app/src/main/java/at/xaxa/demonstrator2/ui/DemoUiState.kt new file mode 100644 index 0000000..a43c188 --- /dev/null +++ b/app/src/main/java/at/xaxa/demonstrator2/ui/DemoUiState.kt @@ -0,0 +1,7 @@ +package at.xaxa.demonstrator2.ui + +import at.xaxa.demonstrator2.data.Task + +data class DemoUiState( + val tasks : List +) \ No newline at end of file diff --git a/app/src/main/java/at/xaxa/demonstrator2/ui/DemoViewModel.kt b/app/src/main/java/at/xaxa/demonstrator2/ui/DemoViewModel.kt new file mode 100644 index 0000000..1fd1f21 --- /dev/null +++ b/app/src/main/java/at/xaxa/demonstrator2/ui/DemoViewModel.kt @@ -0,0 +1,33 @@ +package at.xaxa.demonstrator2.ui + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import at.xaxa.demonstrator2.data.TaskRepository +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch + +class DemoViewModel(val repository: TaskRepository) : ViewModel() { + + init { + viewModelScope.launch { + repository.getAllTasks() + } + } + + + val taskUiState = repository.getAllTasks() + .map { DemoUiState(it) } + .stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(5000), + initialValue = DemoUiState(emptyList()) + ) + + fun onAddButtonClicked() { + viewModelScope.launch { + repository.addRandomTask() + } + } +} diff --git a/app/src/main/java/at/xaxa/demonstrator2/ui/edit/TaskEditScreen.kt b/app/src/main/java/at/xaxa/demonstrator2/ui/edit/TaskEditScreen.kt new file mode 100644 index 0000000..a4c7131 --- /dev/null +++ b/app/src/main/java/at/xaxa/demonstrator2/ui/edit/TaskEditScreen.kt @@ -0,0 +1,78 @@ +package at.xaxa.demonstrator2.ui.edit + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Button +import androidx.compose.material3.OutlinedCard +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.lifecycle.viewmodel.compose.viewModel +import at.xaxa.demonstrator2.data.Task +import at.xaxa.demonstrator2.ui.AppViewModelProvider + + +@Composable +fun TaskEditScreen( + modifier: Modifier = Modifier, + viewModel: TaskEditViewModel = viewModel(factory = AppViewModelProvider.Factory), + onSave: () -> Unit +) { + val task = viewModel.editUiState.task + + + TaskEditForm(task, modifier, onValueChange = { taskChanged -> + viewModel.updateTask(taskChanged) + }) { + viewModel.saveTask() + onSave() + } +} + +@Composable +fun TaskEditForm( + task: Task, + modifier: Modifier = Modifier, + onValueChange: (Task) -> Unit = {}, + onSaveButtonClicked: () -> Unit = {} +) { + OutlinedCard( + modifier = modifier + .fillMaxWidth() + ) + { + Column(Modifier.padding(16.dp).fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) { + Row { + OutlinedTextField( + value = task.name, + label = { Text("Name") }, + onValueChange = { newText -> + onValueChange(task.copy(name = newText)) + }) + } + Row { + OutlinedTextField( + value = task.details.toString(), + label = { Text("Details") }, + onValueChange = { newText -> + onValueChange(task.copy(details = newText)) + }) + } + Button(onClick = { onSaveButtonClicked() }) { + Text("Save changes") + } + } + } +} + +@Preview +@Composable +private fun TaskEditPreview() { + TaskEditForm(Task(234, "Buy milk", "buy milk")) { } +} \ No newline at end of file diff --git a/app/src/main/java/at/xaxa/demonstrator2/ui/edit/TaskEditViewModel.kt b/app/src/main/java/at/xaxa/demonstrator2/ui/edit/TaskEditViewModel.kt new file mode 100644 index 0000000..b3d222c --- /dev/null +++ b/app/src/main/java/at/xaxa/demonstrator2/ui/edit/TaskEditViewModel.kt @@ -0,0 +1,47 @@ +package at.xaxa.demonstrator2.ui.edit + +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import at.xaxa.demonstrator2.data.Task +import at.xaxa.demonstrator2.data.TaskRepository +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + + +data class TaskEditUi( + val task: Task = Task(0, "", "") +) + +class TaskEditViewModel(private val savedStateHandle: SavedStateHandle, + private val taskRepository: TaskRepository) : ViewModel() { + + private val taskId: Int = checkNotNull(savedStateHandle["taskId"]) + + var editUiState by mutableStateOf(TaskEditUi()) + private set + + init { + viewModelScope.launch { + val task = withContext(Dispatchers.IO) { + taskRepository.getTaskById(taskId) + } + editUiState = TaskEditUi(task) + } + } + + fun updateTask(task: Task) { + editUiState = editUiState.copy(task=task) + } + + fun saveTask() { + viewModelScope.launch { + taskRepository.updateTask(editUiState.task) + } + } + +} \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a1944ad..9b0be02 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,10 +1,12 @@ [versions] agp = "8.6.0" +converterKotlinxSerialization = "2.11.0" kotlin = "1.9.0" coreKtx = "1.15.0" junit = "4.13.2" junitVersion = "1.2.1" espressoCore = "3.6.1" +kotlinxSerializationJson = "1.6.3" lifecycleRuntimeKtx = "2.8.7" activityCompose = "1.9.3" composeBom = "2024.04.01" @@ -13,12 +15,15 @@ material = "1.7.5" material3 = "1.2.0" navigationCompose = "2.8.4" navigationComposeVersion = "2.7.2" +roomCommon = "2.6.1" +roomKtx = "2.6.1" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } androidx-material = { module = "androidx.compose.material:material", version.ref = "material" } androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigationCompose" } androidx-navigation-compose-v272 = { module = "androidx.navigation:navigation-compose", version.ref = "navigationComposeVersion" } +converter-kotlinx-serialization = { module = "com.squareup.retrofit2:converter-kotlinx-serialization", version.ref = "converterKotlinxSerialization" } junit = { group = "junit", name = "junit", version.ref = "junit" } androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } @@ -33,7 +38,11 @@ androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-man androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } androidx-material3 = { group = "androidx.compose.material3", name = "material3" } androidx-legacy-support-v4 = { group = "androidx.legacy", name = "legacy-support-v4", version.ref = "legacySupportV4" } +kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" } material3 = { module = "androidx.compose.material3:material3", version.ref = "material3" } +androidx-room-common = { group = "androidx.room", name = "room-common", version.ref = "roomCommon" } +androidx-room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "roomKtx" } +retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "converterKotlinxSerialization" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" }