From d3b716527ee7bc5b8450b8184130e5e8a7201882 Mon Sep 17 00:00:00 2001 From: Max7414 <90671891+Max7414@users.noreply.github.com> Date: Mon, 12 May 2025 04:09:13 +0800 Subject: [PATCH 01/32] =?UTF-8?q?"Refactor:=20Entity/DAO=20from=20Item=20?= =?UTF-8?q?=E2=86=92=20Task,=20priority=20as=20String"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{ItemDaoTest.kt => TaskDaoTest.kt} | 50 +++++++++---------- .../example/inventory/data/AppContainer.kt | 10 ++-- .../inventory/data/InventoryDatabase.kt | 4 +- ...epository.kt => OfflineTasksRepository.kt} | 12 ++--- .../inventory/data/{Item.kt => Task.kt} | 7 ++- .../inventory/data/{ItemDao.kt => TaskDao.kt} | 16 +++--- ...{ItemsRepository.kt => TasksRepository.kt} | 14 +++--- .../inventory/ui/AppViewModelProvider.kt | 8 +-- .../example/inventory/ui/home/HomeScreen.kt | 30 +++++------ .../inventory/ui/home/HomeViewModel.kt | 12 ++--- .../inventory/ui/item/ItemDetailsScreen.kt | 12 ++--- .../inventory/ui/item/ItemDetailsViewModel.kt | 18 +++---- .../inventory/ui/item/ItemEditViewModel.kt | 12 ++--- .../inventory/ui/item/ItemEntryViewModel.kt | 24 ++++----- 14 files changed, 114 insertions(+), 115 deletions(-) rename app/src/androidTest/java/com/example/inventory/{ItemDaoTest.kt => TaskDaoTest.kt} (70%) rename app/src/main/java/com/example/inventory/data/{OfflineItemsRepository.kt => OfflineTasksRepository.kt} (63%) rename app/src/main/java/com/example/inventory/data/{Item.kt => Task.kt} (90%) rename app/src/main/java/com/example/inventory/data/{ItemDao.kt => TaskDao.kt} (79%) rename app/src/main/java/com/example/inventory/data/{ItemsRepository.kt => TasksRepository.kt} (80%) diff --git a/app/src/androidTest/java/com/example/inventory/ItemDaoTest.kt b/app/src/androidTest/java/com/example/inventory/TaskDaoTest.kt similarity index 70% rename from app/src/androidTest/java/com/example/inventory/ItemDaoTest.kt rename to app/src/androidTest/java/com/example/inventory/TaskDaoTest.kt index da73558e..15330f4b 100644 --- a/app/src/androidTest/java/com/example/inventory/ItemDaoTest.kt +++ b/app/src/androidTest/java/com/example/inventory/TaskDaoTest.kt @@ -21,8 +21,8 @@ import androidx.room.Room import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.example.inventory.data.InventoryDatabase -import com.example.inventory.data.Item -import com.example.inventory.data.ItemDao +import com.example.inventory.data.Task +import com.example.inventory.data.TaskDao import kotlinx.coroutines.flow.first import kotlinx.coroutines.runBlocking import org.junit.After @@ -34,12 +34,12 @@ import org.junit.runner.RunWith import java.io.IOException @RunWith(AndroidJUnit4::class) -class ItemDaoTest { +class TaskDaoTest { - private lateinit var itemDao: ItemDao + private lateinit var taskDao: TaskDao private lateinit var inventoryDatabase: InventoryDatabase - private val item1 = Item(1, "Apples", 10.0, 20) - private val item2 = Item(2, "Bananas", 15.0, 97) + private val task1 = Task(1, "Apples", 10.0, 20) + private val task2 = Task(2, "Bananas", 15.0, 97) @Before fun createDb() { @@ -50,7 +50,7 @@ class ItemDaoTest { // Allowing main thread queries, just for testing. .allowMainThreadQueries() .build() - itemDao = inventoryDatabase.itemDao() + taskDao = inventoryDatabase.taskDao() } @After @@ -63,17 +63,17 @@ class ItemDaoTest { @Throws(Exception::class) fun daoInsert_insertsItemIntoDB() = runBlocking { addOneItemToDb() - val allItems = itemDao.getAllItems().first() - assertEquals(allItems[0], item1) + val allItems = taskDao.getAllItems().first() + assertEquals(allItems[0], task1) } @Test @Throws(Exception::class) fun daoGetAllItems_returnsAllItemsFromDB() = runBlocking { addTwoItemsToDb() - val allItems = itemDao.getAllItems().first() - assertEquals(allItems[0], item1) - assertEquals(allItems[1], item2) + val allItems = taskDao.getAllItems().first() + assertEquals(allItems[0], task1) + assertEquals(allItems[1], task2) } @@ -81,17 +81,17 @@ class ItemDaoTest { @Throws(Exception::class) fun daoGetItem_returnsItemFromDB() = runBlocking { addOneItemToDb() - val item = itemDao.getItem(1) - assertEquals(item.first(), item1) + val item = taskDao.getItem(1) + assertEquals(item.first(), task1) } @Test @Throws(Exception::class) fun daoDeleteItems_deletesAllItemsFromDB() = runBlocking { addTwoItemsToDb() - itemDao.delete(item1) - itemDao.delete(item2) - val allItems = itemDao.getAllItems().first() + taskDao.delete(task1) + taskDao.delete(task2) + val allItems = taskDao.getAllItems().first() assertTrue(allItems.isEmpty()) } @@ -99,20 +99,20 @@ class ItemDaoTest { @Throws(Exception::class) fun daoUpdateItems_updatesItemsInDB() = runBlocking { addTwoItemsToDb() - itemDao.update(Item(1, "Apples", 15.0, 25)) - itemDao.update(Item(2, "Bananas", 5.0, 50)) + taskDao.update(Task(1, "Apples", 15.0, 25)) + taskDao.update(Task(2, "Bananas", 5.0, 50)) - val allItems = itemDao.getAllItems().first() - assertEquals(allItems[0], Item(1, "Apples", 15.0, 25)) - assertEquals(allItems[1], Item(2, "Bananas", 5.0, 50)) + val allItems = taskDao.getAllItems().first() + assertEquals(allItems[0], Task(1, "Apples", 15.0, 25)) + assertEquals(allItems[1], Task(2, "Bananas", 5.0, 50)) } private suspend fun addOneItemToDb() { - itemDao.insert(item1) + taskDao.insert(task1) } private suspend fun addTwoItemsToDb() { - itemDao.insert(item1) - itemDao.insert(item2) + taskDao.insert(task1) + taskDao.insert(task2) } } diff --git a/app/src/main/java/com/example/inventory/data/AppContainer.kt b/app/src/main/java/com/example/inventory/data/AppContainer.kt index fc352312..08a924ef 100644 --- a/app/src/main/java/com/example/inventory/data/AppContainer.kt +++ b/app/src/main/java/com/example/inventory/data/AppContainer.kt @@ -22,17 +22,17 @@ import android.content.Context * App container for Dependency injection. */ interface AppContainer { - val itemsRepository: ItemsRepository + val tasksRepository: TasksRepository } /** - * [AppContainer] implementation that provides instance of [OfflineItemsRepository] + * [AppContainer] implementation that provides instance of [OfflineTasksRepository] */ class AppDataContainer(private val context: Context) : AppContainer { /** - * Implementation for [ItemsRepository] + * Implementation for [TasksRepository] */ - override val itemsRepository: ItemsRepository by lazy { - OfflineItemsRepository(InventoryDatabase.getDatabase(context).itemDao()) + override val tasksRepository: TasksRepository by lazy { + OfflineTasksRepository(InventoryDatabase.getDatabase(context).taskDao()) } } diff --git a/app/src/main/java/com/example/inventory/data/InventoryDatabase.kt b/app/src/main/java/com/example/inventory/data/InventoryDatabase.kt index d9977d9c..d9598176 100644 --- a/app/src/main/java/com/example/inventory/data/InventoryDatabase.kt +++ b/app/src/main/java/com/example/inventory/data/InventoryDatabase.kt @@ -24,10 +24,10 @@ import androidx.room.RoomDatabase /** * Database class with a singleton Instance object. */ -@Database(entities = [Item::class], version = 1, exportSchema = false) +@Database(entities = [Task::class], version = 1, exportSchema = false) abstract class InventoryDatabase : RoomDatabase() { - abstract fun itemDao(): ItemDao + abstract fun taskDao(): TaskDao companion object { @Volatile diff --git a/app/src/main/java/com/example/inventory/data/OfflineItemsRepository.kt b/app/src/main/java/com/example/inventory/data/OfflineTasksRepository.kt similarity index 63% rename from app/src/main/java/com/example/inventory/data/OfflineItemsRepository.kt rename to app/src/main/java/com/example/inventory/data/OfflineTasksRepository.kt index ed4c03b7..1a4c909e 100644 --- a/app/src/main/java/com/example/inventory/data/OfflineItemsRepository.kt +++ b/app/src/main/java/com/example/inventory/data/OfflineTasksRepository.kt @@ -18,14 +18,14 @@ package com.example.inventory.data import kotlinx.coroutines.flow.Flow -class OfflineItemsRepository(private val itemDao: ItemDao) : ItemsRepository { - override fun getAllItemsStream(): Flow> = itemDao.getAllItems() +class OfflineTasksRepository(private val taskDao: TaskDao) : TasksRepository { + override fun getAllItemsStream(): Flow> = taskDao.getAllItems() - override fun getItemStream(id: Int): Flow = itemDao.getItem(id) + override fun getTaskStream(id: Int): Flow = taskDao.getItem(id) - override suspend fun insertItem(item: Item) = itemDao.insert(item) + override suspend fun insertTask(task: Task) = taskDao.insert(task) - override suspend fun deleteItem(item: Item) = itemDao.delete(item) + override suspend fun deleteTask(task: Task) = taskDao.delete(task) - override suspend fun updateItem(item: Item) = itemDao.update(item) + override suspend fun updateTask(task: Task) = taskDao.update(task) } diff --git a/app/src/main/java/com/example/inventory/data/Item.kt b/app/src/main/java/com/example/inventory/data/Task.kt similarity index 90% rename from app/src/main/java/com/example/inventory/data/Item.kt rename to app/src/main/java/com/example/inventory/data/Task.kt index edad7cae..cafb9ac4 100644 --- a/app/src/main/java/com/example/inventory/data/Item.kt +++ b/app/src/main/java/com/example/inventory/data/Task.kt @@ -22,11 +22,10 @@ import androidx.room.PrimaryKey /** * Entity data class represents a single row in the database. */ -@Entity(tableName = "items") -data class Item( +@Entity(tableName = "tasks") +data class Task( @PrimaryKey(autoGenerate = true) val id: Int = 0, val name: String, - val price: Double, - val quantity: Int + val priority: String ) diff --git a/app/src/main/java/com/example/inventory/data/ItemDao.kt b/app/src/main/java/com/example/inventory/data/TaskDao.kt similarity index 79% rename from app/src/main/java/com/example/inventory/data/ItemDao.kt rename to app/src/main/java/com/example/inventory/data/TaskDao.kt index 22b14c72..e058f569 100644 --- a/app/src/main/java/com/example/inventory/data/ItemDao.kt +++ b/app/src/main/java/com/example/inventory/data/TaskDao.kt @@ -28,22 +28,22 @@ import kotlinx.coroutines.flow.Flow * Database access object to access the Inventory database */ @Dao -interface ItemDao { +interface TaskDao { - @Query("SELECT * from items ORDER BY name ASC") - fun getAllItems(): Flow> + @Query("SELECT * from tasks ORDER BY name ASC") + fun getAllItems(): Flow> - @Query("SELECT * from items WHERE id = :id") - fun getItem(id: Int): Flow + @Query("SELECT * from tasks WHERE id = :id") + fun getItem(id: Int): Flow // Specify the conflict strategy as IGNORE, when the user tries to add an // existing Item into the database Room ignores the conflict. @Insert(onConflict = OnConflictStrategy.IGNORE) - suspend fun insert(item: Item) + suspend fun insert(task: Task) @Update - suspend fun update(item: Item) + suspend fun update(task: Task) @Delete - suspend fun delete(item: Item) + suspend fun delete(task: Task) } diff --git a/app/src/main/java/com/example/inventory/data/ItemsRepository.kt b/app/src/main/java/com/example/inventory/data/TasksRepository.kt similarity index 80% rename from app/src/main/java/com/example/inventory/data/ItemsRepository.kt rename to app/src/main/java/com/example/inventory/data/TasksRepository.kt index 57029541..873cee79 100644 --- a/app/src/main/java/com/example/inventory/data/ItemsRepository.kt +++ b/app/src/main/java/com/example/inventory/data/TasksRepository.kt @@ -19,31 +19,31 @@ package com.example.inventory.data import kotlinx.coroutines.flow.Flow /** - * Repository that provides insert, update, delete, and retrieve of [Item] from a given data source. + * Repository that provides insert, update, delete, and retrieve of [Task] from a given data source. */ -interface ItemsRepository { +interface TasksRepository { /** * Retrieve all the items from the the given data source. */ - fun getAllItemsStream(): Flow> + fun getAllTasksStream(): Flow> /** * Retrieve an item from the given data source that matches with the [id]. */ - fun getItemStream(id: Int): Flow + fun getTaskStream(id: Int): Flow /** * Insert item in the data source */ - suspend fun insertItem(item: Item) + suspend fun insertTask(task: Task) /** * Delete item from the data source */ - suspend fun deleteItem(item: Item) + suspend fun deleteTask(task: Task) /** * Update item in the data source */ - suspend fun updateItem(item: Item) + suspend fun updateTask(task: Task) } diff --git a/app/src/main/java/com/example/inventory/ui/AppViewModelProvider.kt b/app/src/main/java/com/example/inventory/ui/AppViewModelProvider.kt index be50e541..1a61bd93 100644 --- a/app/src/main/java/com/example/inventory/ui/AppViewModelProvider.kt +++ b/app/src/main/java/com/example/inventory/ui/AppViewModelProvider.kt @@ -37,25 +37,25 @@ object AppViewModelProvider { initializer { ItemEditViewModel( this.createSavedStateHandle(), - inventoryApplication().container.itemsRepository + inventoryApplication().container.tasksRepository ) } // Initializer for ItemEntryViewModel initializer { - ItemEntryViewModel(inventoryApplication().container.itemsRepository) + ItemEntryViewModel(inventoryApplication().container.tasksRepository) } // Initializer for ItemDetailsViewModel initializer { ItemDetailsViewModel( this.createSavedStateHandle(), - inventoryApplication().container.itemsRepository + inventoryApplication().container.tasksRepository ) } // Initializer for HomeViewModel initializer { - HomeViewModel(inventoryApplication().container.itemsRepository) + HomeViewModel(inventoryApplication().container.tasksRepository) } } } diff --git a/app/src/main/java/com/example/inventory/ui/home/HomeScreen.kt b/app/src/main/java/com/example/inventory/ui/home/HomeScreen.kt index 910552ce..8097307d 100644 --- a/app/src/main/java/com/example/inventory/ui/home/HomeScreen.kt +++ b/app/src/main/java/com/example/inventory/ui/home/HomeScreen.kt @@ -57,7 +57,7 @@ import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel import com.example.inventory.InventoryTopAppBar import com.example.inventory.R -import com.example.inventory.data.Item +import com.example.inventory.data.Task import com.example.inventory.ui.AppViewModelProvider import com.example.inventory.ui.item.formatedPrice import com.example.inventory.ui.navigation.NavigationDestination @@ -109,7 +109,7 @@ fun HomeScreen( }, ) { innerPadding -> HomeBody( - itemList = homeUiState.itemList, + taskList = homeUiState.taskList, onItemClick = navigateToItemUpdate, modifier = modifier.fillMaxSize(), contentPadding = innerPadding, @@ -119,7 +119,7 @@ fun HomeScreen( @Composable private fun HomeBody( - itemList: List, + taskList: List, onItemClick: (Int) -> Unit, modifier: Modifier = Modifier, contentPadding: PaddingValues = PaddingValues(0.dp), @@ -128,7 +128,7 @@ private fun HomeBody( horizontalAlignment = Alignment.CenterHorizontally, modifier = modifier, ) { - if (itemList.isEmpty()) { + if (taskList.isEmpty()) { Text( text = stringResource(R.string.no_item_description), textAlign = TextAlign.Center, @@ -137,7 +137,7 @@ private fun HomeBody( ) } else { InventoryList( - itemList = itemList, + taskList = taskList, onItemClick = { onItemClick(it.id) }, contentPadding = contentPadding, modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.padding_small)) @@ -148,8 +148,8 @@ private fun HomeBody( @Composable private fun InventoryList( - itemList: List, - onItemClick: (Item) -> Unit, + taskList: List, + onItemClick: (Task) -> Unit, contentPadding: PaddingValues, modifier: Modifier = Modifier ) { @@ -157,8 +157,8 @@ private fun InventoryList( modifier = modifier, contentPadding = contentPadding ) { - items(items = itemList, key = { it.id }) { item -> - InventoryItem(item = item, + items(items = taskList, key = { it.id }) { item -> + InventoryItem(task = item, modifier = Modifier .padding(dimensionResource(id = R.dimen.padding_small)) .clickable { onItemClick(item) }) @@ -168,7 +168,7 @@ private fun InventoryList( @Composable private fun InventoryItem( - item: Item, modifier: Modifier = Modifier + task: Task, modifier: Modifier = Modifier ) { Card( modifier = modifier, elevation = CardDefaults.cardElevation(defaultElevation = 2.dp) @@ -181,17 +181,17 @@ private fun InventoryItem( modifier = Modifier.fillMaxWidth() ) { Text( - text = item.name, + text = task.name, style = MaterialTheme.typography.titleLarge, ) Spacer(Modifier.weight(1f)) Text( - text = item.formatedPrice(), + text = task.formatedPrice(), style = MaterialTheme.typography.titleMedium ) } Text( - text = stringResource(R.string.in_stock, item.quantity), + text = stringResource(R.string.in_stock, task.quantity), style = MaterialTheme.typography.titleMedium ) } @@ -203,7 +203,7 @@ private fun InventoryItem( fun HomeBodyPreview() { InventoryTheme { HomeBody(listOf( - Item(1, "Game", 100.0, 20), Item(2, "Pen", 200.0, 30), Item(3, "TV", 300.0, 50) + Task(1, "Game", 100.0, 20), Task(2, "Pen", 200.0, 30), Task(3, "TV", 300.0, 50) ), onItemClick = {}) } } @@ -221,7 +221,7 @@ fun HomeBodyEmptyListPreview() { fun InventoryItemPreview() { InventoryTheme { InventoryItem( - Item(1, "Game", 100.0, 20), + Task(1, "Game", 100.0, 20), ) } } diff --git a/app/src/main/java/com/example/inventory/ui/home/HomeViewModel.kt b/app/src/main/java/com/example/inventory/ui/home/HomeViewModel.kt index 3ec7087f..44694f72 100644 --- a/app/src/main/java/com/example/inventory/ui/home/HomeViewModel.kt +++ b/app/src/main/java/com/example/inventory/ui/home/HomeViewModel.kt @@ -18,8 +18,8 @@ package com.example.inventory.ui.home import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.example.inventory.data.Item -import com.example.inventory.data.ItemsRepository +import com.example.inventory.data.Task +import com.example.inventory.data.TasksRepository import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.map @@ -28,14 +28,14 @@ import kotlinx.coroutines.flow.stateIn /** * ViewModel to retrieve all items in the Room database. */ -class HomeViewModel(itemsRepository: ItemsRepository) : ViewModel() { +class HomeViewModel(tasksRepository: TasksRepository) : ViewModel() { /** - * Holds home ui state. The list of items are retrieved from [ItemsRepository] and mapped to + * Holds home ui state. The list of items are retrieved from [TasksRepository] and mapped to * [HomeUiState] */ val homeUiState: StateFlow = - itemsRepository.getAllItemsStream().map { HomeUiState(it) } + tasksRepository.getAllItemsStream().map { HomeUiState(it) } .stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(TIMEOUT_MILLIS), @@ -50,4 +50,4 @@ class HomeViewModel(itemsRepository: ItemsRepository) : ViewModel() { /** * Ui State for HomeScreen */ -data class HomeUiState(val itemList: List = listOf()) +data class HomeUiState(val taskList: List = listOf()) diff --git a/app/src/main/java/com/example/inventory/ui/item/ItemDetailsScreen.kt b/app/src/main/java/com/example/inventory/ui/item/ItemDetailsScreen.kt index caab4181..65fc96c6 100644 --- a/app/src/main/java/com/example/inventory/ui/item/ItemDetailsScreen.kt +++ b/app/src/main/java/com/example/inventory/ui/item/ItemDetailsScreen.kt @@ -60,7 +60,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.lifecycle.viewmodel.compose.viewModel import com.example.inventory.InventoryTopAppBar import com.example.inventory.R -import com.example.inventory.data.Item +import com.example.inventory.data.Task import com.example.inventory.ui.AppViewModelProvider import com.example.inventory.ui.navigation.NavigationDestination import com.example.inventory.ui.theme.InventoryTheme @@ -146,7 +146,7 @@ private fun ItemDetailsBody( ) { var deleteConfirmationRequired by rememberSaveable { mutableStateOf(false) } ItemDetails( - item = itemDetailsUiState.itemDetails.toItem(), modifier = Modifier.fillMaxWidth() + task = itemDetailsUiState.itemDetails.toItem(), modifier = Modifier.fillMaxWidth() ) Button( onClick = onSellItem, @@ -179,7 +179,7 @@ private fun ItemDetailsBody( @Composable fun ItemDetails( - item: Item, modifier: Modifier = Modifier + task: Task, modifier: Modifier = Modifier ) { Card( modifier = modifier, colors = CardDefaults.cardColors( @@ -195,7 +195,7 @@ fun ItemDetails( ) { ItemDetailsRow( labelResID = R.string.item, - itemDetail = item.name, + itemDetail = task.name, modifier = Modifier.padding( horizontal = dimensionResource( id = R.dimen @@ -205,7 +205,7 @@ fun ItemDetails( ) ItemDetailsRow( labelResID = R.string.quantity_in_stock, - itemDetail = item.quantity.toString(), + itemDetail = task.quantity.toString(), modifier = Modifier.padding( horizontal = dimensionResource( id = R.dimen @@ -215,7 +215,7 @@ fun ItemDetails( ) ItemDetailsRow( labelResID = R.string.price, - itemDetail = item.formatedPrice(), + itemDetail = task.formatedPrice(), modifier = Modifier.padding( horizontal = dimensionResource( id = R.dimen diff --git a/app/src/main/java/com/example/inventory/ui/item/ItemDetailsViewModel.kt b/app/src/main/java/com/example/inventory/ui/item/ItemDetailsViewModel.kt index c37b0f58..0c290ec4 100644 --- a/app/src/main/java/com/example/inventory/ui/item/ItemDetailsViewModel.kt +++ b/app/src/main/java/com/example/inventory/ui/item/ItemDetailsViewModel.kt @@ -19,7 +19,7 @@ package com.example.inventory.ui.item import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.example.inventory.data.ItemsRepository +import com.example.inventory.data.TasksRepository import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.filterNotNull @@ -28,21 +28,21 @@ import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch /** - * ViewModel to retrieve, update and delete an item from the [ItemsRepository]'s data source. + * ViewModel to retrieve, update and delete an item from the [TasksRepository]'s data source. */ class ItemDetailsViewModel( savedStateHandle: SavedStateHandle, - private val itemsRepository: ItemsRepository, + private val tasksRepository: TasksRepository, ) : ViewModel() { private val itemId: Int = checkNotNull(savedStateHandle[ItemDetailsDestination.itemIdArg]) /** - * Holds the item details ui state. The data is retrieved from [ItemsRepository] and mapped to + * Holds the item details ui state. The data is retrieved from [TasksRepository] and mapped to * the UI state. */ val uiState: StateFlow = - itemsRepository.getItemStream(itemId) + tasksRepository.getTaskStream(itemId) .filterNotNull() .map { ItemDetailsUiState(outOfStock = it.quantity <= 0, itemDetails = it.toItemDetails()) @@ -53,22 +53,22 @@ class ItemDetailsViewModel( ) /** - * Reduces the item quantity by one and update the [ItemsRepository]'s data source. + * Reduces the item quantity by one and update the [TasksRepository]'s data source. */ fun reduceQuantityByOne() { viewModelScope.launch { val currentItem = uiState.value.itemDetails.toItem() if (currentItem.quantity > 0) { - itemsRepository.updateItem(currentItem.copy(quantity = currentItem.quantity - 1)) + tasksRepository.updateTask(currentItem.copy(quantity = currentItem.quantity - 1)) } } } /** - * Deletes the item from the [ItemsRepository]'s data source. + * Deletes the item from the [TasksRepository]'s data source. */ suspend fun deleteItem() { - itemsRepository.deleteItem(uiState.value.itemDetails.toItem()) + tasksRepository.deleteTask(uiState.value.itemDetails.toItem()) } companion object { diff --git a/app/src/main/java/com/example/inventory/ui/item/ItemEditViewModel.kt b/app/src/main/java/com/example/inventory/ui/item/ItemEditViewModel.kt index bdd64918..244bc2a7 100644 --- a/app/src/main/java/com/example/inventory/ui/item/ItemEditViewModel.kt +++ b/app/src/main/java/com/example/inventory/ui/item/ItemEditViewModel.kt @@ -22,17 +22,17 @@ import androidx.compose.runtime.setValue import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.example.inventory.data.ItemsRepository +import com.example.inventory.data.TasksRepository import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch /** - * ViewModel to retrieve and update an item from the [ItemsRepository]'s data source. + * ViewModel to retrieve and update an item from the [TasksRepository]'s data source. */ class ItemEditViewModel( savedStateHandle: SavedStateHandle, - private val itemsRepository: ItemsRepository + private val tasksRepository: TasksRepository ) : ViewModel() { /** @@ -45,7 +45,7 @@ class ItemEditViewModel( init { viewModelScope.launch { - itemUiState = itemsRepository.getItemStream(itemId) + itemUiState = tasksRepository.getTaskStream(itemId) .filterNotNull() .first() .toItemUiState(true) @@ -53,11 +53,11 @@ class ItemEditViewModel( } /** - * Update the item in the [ItemsRepository]'s data source + * Update the item in the [TasksRepository]'s data source */ suspend fun updateItem() { if (validateInput(itemUiState.itemDetails)) { - itemsRepository.updateItem(itemUiState.itemDetails.toItem()) + tasksRepository.updateTask(itemUiState.itemDetails.toItem()) } } diff --git a/app/src/main/java/com/example/inventory/ui/item/ItemEntryViewModel.kt b/app/src/main/java/com/example/inventory/ui/item/ItemEntryViewModel.kt index 9729464c..7056e8f5 100644 --- a/app/src/main/java/com/example/inventory/ui/item/ItemEntryViewModel.kt +++ b/app/src/main/java/com/example/inventory/ui/item/ItemEntryViewModel.kt @@ -20,14 +20,14 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel -import com.example.inventory.data.Item -import com.example.inventory.data.ItemsRepository +import com.example.inventory.data.Task +import com.example.inventory.data.TasksRepository import java.text.NumberFormat /** * ViewModel to validate and insert items in the Room database. */ -class ItemEntryViewModel(private val itemsRepository: ItemsRepository) : ViewModel() { +class ItemEntryViewModel(private val tasksRepository: TasksRepository) : ViewModel() { /** * Holds current item ui state @@ -45,11 +45,11 @@ class ItemEntryViewModel(private val itemsRepository: ItemsRepository) : ViewMod } /** - * Inserts an [Item] in the Room database + * Inserts an [Task] in the Room database */ suspend fun saveItem() { if (validateInput()) { - itemsRepository.insertItem(itemUiState.itemDetails.toItem()) + tasksRepository.insertTask(itemUiState.itemDetails.toItem()) } } @@ -76,33 +76,33 @@ data class ItemDetails( ) /** - * Extension function to convert [ItemUiState] to [Item]. If the value of [ItemDetails.price] is + * Extension function to convert [ItemUiState] to [Task]. If the value of [ItemDetails.price] is * not a valid [Double], then the price will be set to 0.0. Similarly if the value of * [ItemUiState] is not a valid [Int], then the quantity will be set to 0 */ -fun ItemDetails.toItem(): Item = Item( +fun ItemDetails.toItem(): Task = Task( id = id, name = name, price = price.toDoubleOrNull() ?: 0.0, quantity = quantity.toIntOrNull() ?: 0 ) -fun Item.formatedPrice(): String { +fun Task.formatedPrice(): String { return NumberFormat.getCurrencyInstance().format(price) } /** - * Extension function to convert [Item] to [ItemUiState] + * Extension function to convert [Task] to [ItemUiState] */ -fun Item.toItemUiState(isEntryValid: Boolean = false): ItemUiState = ItemUiState( +fun Task.toItemUiState(isEntryValid: Boolean = false): ItemUiState = ItemUiState( itemDetails = this.toItemDetails(), isEntryValid = isEntryValid ) /** - * Extension function to convert [Item] to [ItemDetails] + * Extension function to convert [Task] to [ItemDetails] */ -fun Item.toItemDetails(): ItemDetails = ItemDetails( +fun Task.toItemDetails(): ItemDetails = ItemDetails( id = id, name = name, price = price.toString(), From 39f8ef6a559010036133d3725bdbc510d0829bd2 Mon Sep 17 00:00:00 2001 From: Max7414 <90671891+Max7414@users.noreply.github.com> Date: Mon, 12 May 2025 05:07:43 +0800 Subject: [PATCH 02/32] Fix override fun getAllTasksStream() --- .../java/com/example/inventory/data/OfflineTasksRepository.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/example/inventory/data/OfflineTasksRepository.kt b/app/src/main/java/com/example/inventory/data/OfflineTasksRepository.kt index 1a4c909e..ac76fdf1 100644 --- a/app/src/main/java/com/example/inventory/data/OfflineTasksRepository.kt +++ b/app/src/main/java/com/example/inventory/data/OfflineTasksRepository.kt @@ -19,7 +19,7 @@ package com.example.inventory.data import kotlinx.coroutines.flow.Flow class OfflineTasksRepository(private val taskDao: TaskDao) : TasksRepository { - override fun getAllItemsStream(): Flow> = taskDao.getAllItems() + override fun getAllTasksStream(): Flow> = taskDao.getAllItems() override fun getTaskStream(id: Int): Flow = taskDao.getItem(id) From 5661c0d8323a71c25f67b74f1604feed18662b1e Mon Sep 17 00:00:00 2001 From: Max7414 <90671891+Max7414@users.noreply.github.com> Date: Mon, 12 May 2025 05:12:18 +0800 Subject: [PATCH 03/32] app_name -> To Do List --- app/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 66262ff3..63d2d985 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -14,7 +14,7 @@ ~ limitations under the License. --> - Inventory + To Do List Attention Back Delete From 4513626e802da92734d01578378886a75a6a3ce2 Mon Sep 17 00:00:00 2001 From: Max7414 <90671891+Max7414@users.noreply.github.com> Date: Mon, 12 May 2025 05:15:23 +0800 Subject: [PATCH 04/32] item->task_entry_title -> Add Task --- app/src/main/java/com/example/inventory/ui/home/HomeScreen.kt | 2 +- .../main/java/com/example/inventory/ui/item/ItemEntryScreen.kt | 2 +- app/src/main/res/values/strings.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/example/inventory/ui/home/HomeScreen.kt b/app/src/main/java/com/example/inventory/ui/home/HomeScreen.kt index 8097307d..156fae11 100644 --- a/app/src/main/java/com/example/inventory/ui/home/HomeScreen.kt +++ b/app/src/main/java/com/example/inventory/ui/home/HomeScreen.kt @@ -103,7 +103,7 @@ fun HomeScreen( ) { Icon( imageVector = Icons.Default.Add, - contentDescription = stringResource(R.string.item_entry_title) + contentDescription = stringResource(R.string.task_entry_title) ) } }, diff --git a/app/src/main/java/com/example/inventory/ui/item/ItemEntryScreen.kt b/app/src/main/java/com/example/inventory/ui/item/ItemEntryScreen.kt index e542de8c..36918c81 100644 --- a/app/src/main/java/com/example/inventory/ui/item/ItemEntryScreen.kt +++ b/app/src/main/java/com/example/inventory/ui/item/ItemEntryScreen.kt @@ -52,7 +52,7 @@ import java.util.Locale object ItemEntryDestination : NavigationDestination { override val route = "item_entry" - override val titleRes = R.string.item_entry_title + override val titleRes = R.string.task_entry_title } @OptIn(ExperimentalMaterial3Api::class) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 63d2d985..eccd65de 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -23,7 +23,7 @@ Edit Item Item Item Details - Add Item + Add Task Item Name* Item Price* No From 5b710054c86019077d0b249b48983ba7676ae926 Mon Sep 17 00:00:00 2001 From: Max7414 <90671891+Max7414@users.noreply.github.com> Date: Mon, 12 May 2025 05:16:51 +0800 Subject: [PATCH 05/32] item-> no_task_description -> Oops!\nNo tasks in the lists.\nTap + to add --- app/src/main/java/com/example/inventory/ui/home/HomeScreen.kt | 2 +- app/src/main/res/values/strings.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/example/inventory/ui/home/HomeScreen.kt b/app/src/main/java/com/example/inventory/ui/home/HomeScreen.kt index 156fae11..7e838994 100644 --- a/app/src/main/java/com/example/inventory/ui/home/HomeScreen.kt +++ b/app/src/main/java/com/example/inventory/ui/home/HomeScreen.kt @@ -130,7 +130,7 @@ private fun HomeBody( ) { if (taskList.isEmpty()) { Text( - text = stringResource(R.string.no_item_description), + text = stringResource(R.string.no_task_description), textAlign = TextAlign.Center, style = MaterialTheme.typography.titleLarge, modifier = Modifier.padding(contentPadding), diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index eccd65de..5b0306ef 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -19,7 +19,7 @@ Back Delete Are you sure you want to delete? - Oops!\nNo items in the inventory.\nTap + to add. + Oops!\nNo tasks in the lists.\nTap + to add. Edit Item Item Item Details From 26f4b6f2a315b6f9878c2940fd8b3c2fd7f44c29 Mon Sep 17 00:00:00 2001 From: Max7414 <90671891+Max7414@users.noreply.github.com> Date: Mon, 12 May 2025 05:26:27 +0800 Subject: [PATCH 06/32] HomeScreen.kt: onItemClick change to onTaskClick task.q to task.priority Preview data edited HomeViewModel.kt: getAllTasksStream() --- .../com/example/inventory/ui/home/HomeScreen.kt | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/example/inventory/ui/home/HomeScreen.kt b/app/src/main/java/com/example/inventory/ui/home/HomeScreen.kt index 7e838994..c9284e3d 100644 --- a/app/src/main/java/com/example/inventory/ui/home/HomeScreen.kt +++ b/app/src/main/java/com/example/inventory/ui/home/HomeScreen.kt @@ -110,7 +110,7 @@ fun HomeScreen( ) { innerPadding -> HomeBody( taskList = homeUiState.taskList, - onItemClick = navigateToItemUpdate, + onTaskClick = navigateToItemUpdate, modifier = modifier.fillMaxSize(), contentPadding = innerPadding, ) @@ -120,7 +120,7 @@ fun HomeScreen( @Composable private fun HomeBody( taskList: List, - onItemClick: (Int) -> Unit, + onTaskClick: (Int) -> Unit, modifier: Modifier = Modifier, contentPadding: PaddingValues = PaddingValues(0.dp), ) { @@ -138,7 +138,7 @@ private fun HomeBody( } else { InventoryList( taskList = taskList, - onItemClick = { onItemClick(it.id) }, + onItemClick = { onTaskClick(it.id) }, contentPadding = contentPadding, modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.padding_small)) ) @@ -191,7 +191,7 @@ private fun InventoryItem( ) } Text( - text = stringResource(R.string.in_stock, task.quantity), + text = stringResource(R.string.in_stock, task.priority), style = MaterialTheme.typography.titleMedium ) } @@ -203,8 +203,8 @@ private fun InventoryItem( fun HomeBodyPreview() { InventoryTheme { HomeBody(listOf( - Task(1, "Game", 100.0, 20), Task(2, "Pen", 200.0, 30), Task(3, "TV", 300.0, 50) - ), onItemClick = {}) + Task(1, "Task1", "High"), Task(2, "Task2", "High"), Task(3, "Task3", "Low") + ), onTaskClick = {}) } } @@ -212,7 +212,7 @@ fun HomeBodyPreview() { @Composable fun HomeBodyEmptyListPreview() { InventoryTheme { - HomeBody(listOf(), onItemClick = {}) + HomeBody(listOf(), onTaskClick = {}) } } @@ -221,7 +221,7 @@ fun HomeBodyEmptyListPreview() { fun InventoryItemPreview() { InventoryTheme { InventoryItem( - Task(1, "Game", 100.0, 20), + Task(1, "Task1", "High"), ) } } From 855389a1fc6e61525039dbd3bae7667dfb29e8d7 Mon Sep 17 00:00:00 2001 From: Max7414 <90671891+Max7414@users.noreply.github.com> Date: Mon, 12 May 2025 05:26:32 +0800 Subject: [PATCH 07/32] HomeScreen.kt: onItemClick change to onTaskClick task.q to task.priority Preview data edited HomeViewModel.kt: getAllTasksStream() --- .../main/java/com/example/inventory/ui/home/HomeViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/example/inventory/ui/home/HomeViewModel.kt b/app/src/main/java/com/example/inventory/ui/home/HomeViewModel.kt index 44694f72..9ed7f17a 100644 --- a/app/src/main/java/com/example/inventory/ui/home/HomeViewModel.kt +++ b/app/src/main/java/com/example/inventory/ui/home/HomeViewModel.kt @@ -35,7 +35,7 @@ class HomeViewModel(tasksRepository: TasksRepository) : ViewModel() { * [HomeUiState] */ val homeUiState: StateFlow = - tasksRepository.getAllItemsStream().map { HomeUiState(it) } + tasksRepository.getAllTasksStream().map { HomeUiState(it) } .stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(TIMEOUT_MILLIS), From 9549b7de2acff7c9635a36ade84557f86060bfcd Mon Sep 17 00:00:00 2001 From: Max7414 <90671891+Max7414@users.noreply.github.com> Date: Mon, 12 May 2025 05:34:41 +0800 Subject: [PATCH 08/32] strings.xml change all item to task. Files under Item all change to task --- .../inventory/ui/AppViewModelProvider.kt | 8 ++++---- .../example/inventory/ui/home/HomeScreen.kt | 2 +- .../ui/navigation/InventoryNavGraph.kt | 18 +++++++++--------- .../TaskDetailsScreen.kt} | 16 ++++++++-------- .../TaskDetailsViewModel.kt} | 4 ++-- .../TaskEditScreen.kt} | 6 +++--- .../TaskEditViewModel.kt} | 4 ++-- .../TaskEntryScreen.kt} | 6 +++--- .../TaskEntryViewModel.kt} | 2 +- app/src/main/res/values/strings.xml | 10 +++++----- 10 files changed, 38 insertions(+), 38 deletions(-) rename app/src/main/java/com/example/inventory/ui/{item/ItemDetailsScreen.kt => task/TaskDetailsScreen.kt} (96%) rename app/src/main/java/com/example/inventory/ui/{item/ItemDetailsViewModel.kt => task/TaskDetailsViewModel.kt} (95%) rename app/src/main/java/com/example/inventory/ui/{item/ItemEditScreen.kt => task/TaskEditScreen.kt} (95%) rename app/src/main/java/com/example/inventory/ui/{item/ItemEditViewModel.kt => task/TaskEditViewModel.kt} (97%) rename app/src/main/java/com/example/inventory/ui/{item/ItemEntryScreen.kt => task/TaskEntryScreen.kt} (97%) rename app/src/main/java/com/example/inventory/ui/{item/ItemEntryViewModel.kt => task/TaskEntryViewModel.kt} (98%) diff --git a/app/src/main/java/com/example/inventory/ui/AppViewModelProvider.kt b/app/src/main/java/com/example/inventory/ui/AppViewModelProvider.kt index 1a61bd93..2db9830d 100644 --- a/app/src/main/java/com/example/inventory/ui/AppViewModelProvider.kt +++ b/app/src/main/java/com/example/inventory/ui/AppViewModelProvider.kt @@ -24,9 +24,9 @@ import androidx.lifecycle.viewmodel.initializer import androidx.lifecycle.viewmodel.viewModelFactory import com.example.inventory.InventoryApplication import com.example.inventory.ui.home.HomeViewModel -import com.example.inventory.ui.item.ItemDetailsViewModel -import com.example.inventory.ui.item.ItemEditViewModel -import com.example.inventory.ui.item.ItemEntryViewModel +import com.example.inventory.ui.task.ItemDetailsViewModel +import com.example.inventory.ui.task.TaskEditViewModel +import com.example.inventory.ui.task.ItemEntryViewModel /** * Provides Factory to create instance of ViewModel for the entire Inventory app @@ -35,7 +35,7 @@ object AppViewModelProvider { val Factory = viewModelFactory { // Initializer for ItemEditViewModel initializer { - ItemEditViewModel( + TaskEditViewModel( this.createSavedStateHandle(), inventoryApplication().container.tasksRepository ) diff --git a/app/src/main/java/com/example/inventory/ui/home/HomeScreen.kt b/app/src/main/java/com/example/inventory/ui/home/HomeScreen.kt index c9284e3d..b7f0bf94 100644 --- a/app/src/main/java/com/example/inventory/ui/home/HomeScreen.kt +++ b/app/src/main/java/com/example/inventory/ui/home/HomeScreen.kt @@ -59,7 +59,7 @@ import com.example.inventory.InventoryTopAppBar import com.example.inventory.R import com.example.inventory.data.Task import com.example.inventory.ui.AppViewModelProvider -import com.example.inventory.ui.item.formatedPrice +import com.example.inventory.ui.task.formatedPrice import com.example.inventory.ui.navigation.NavigationDestination import com.example.inventory.ui.theme.InventoryTheme diff --git a/app/src/main/java/com/example/inventory/ui/navigation/InventoryNavGraph.kt b/app/src/main/java/com/example/inventory/ui/navigation/InventoryNavGraph.kt index 1f56c32d..eb1e2e13 100644 --- a/app/src/main/java/com/example/inventory/ui/navigation/InventoryNavGraph.kt +++ b/app/src/main/java/com/example/inventory/ui/navigation/InventoryNavGraph.kt @@ -25,12 +25,12 @@ import androidx.navigation.compose.composable import androidx.navigation.navArgument import com.example.inventory.ui.home.HomeDestination import com.example.inventory.ui.home.HomeScreen -import com.example.inventory.ui.item.ItemDetailsDestination -import com.example.inventory.ui.item.ItemDetailsScreen -import com.example.inventory.ui.item.ItemEditDestination -import com.example.inventory.ui.item.ItemEditScreen -import com.example.inventory.ui.item.ItemEntryDestination -import com.example.inventory.ui.item.ItemEntryScreen +import com.example.inventory.ui.task.TaskDetailsDestination +import com.example.inventory.ui.task.ItemDetailsScreen +import com.example.inventory.ui.task.ItemEditDestination +import com.example.inventory.ui.task.ItemEditScreen +import com.example.inventory.ui.task.ItemEntryDestination +import com.example.inventory.ui.task.ItemEntryScreen /** * Provides Navigation graph for the application. @@ -49,7 +49,7 @@ fun InventoryNavHost( HomeScreen( navigateToItemEntry = { navController.navigate(ItemEntryDestination.route) }, navigateToItemUpdate = { - navController.navigate("${ItemDetailsDestination.route}/${it}") + navController.navigate("${TaskDetailsDestination.route}/${it}") } ) } @@ -60,8 +60,8 @@ fun InventoryNavHost( ) } composable( - route = ItemDetailsDestination.routeWithArgs, - arguments = listOf(navArgument(ItemDetailsDestination.itemIdArg) { + route = TaskDetailsDestination.routeWithArgs, + arguments = listOf(navArgument(TaskDetailsDestination.taskIdArg) { type = NavType.IntType }) ) { diff --git a/app/src/main/java/com/example/inventory/ui/item/ItemDetailsScreen.kt b/app/src/main/java/com/example/inventory/ui/task/TaskDetailsScreen.kt similarity index 96% rename from app/src/main/java/com/example/inventory/ui/item/ItemDetailsScreen.kt rename to app/src/main/java/com/example/inventory/ui/task/TaskDetailsScreen.kt index 65fc96c6..ed1e5aef 100644 --- a/app/src/main/java/com/example/inventory/ui/item/ItemDetailsScreen.kt +++ b/app/src/main/java/com/example/inventory/ui/task/TaskDetailsScreen.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.example.inventory.ui.item +package com.example.inventory.ui.task import androidx.annotation.StringRes import androidx.compose.foundation.layout.Arrangement @@ -66,11 +66,11 @@ import com.example.inventory.ui.navigation.NavigationDestination import com.example.inventory.ui.theme.InventoryTheme import kotlinx.coroutines.launch -object ItemDetailsDestination : NavigationDestination { +object TaskDetailsDestination : NavigationDestination { override val route = "item_details" - override val titleRes = R.string.item_detail_title - const val itemIdArg = "itemId" - val routeWithArgs = "$route/{$itemIdArg}" + override val titleRes = R.string.task_detail_title + const val taskIdArg = "itemId" + val routeWithArgs = "$route/{$taskIdArg}" } @OptIn(ExperimentalMaterial3Api::class) @@ -86,7 +86,7 @@ fun ItemDetailsScreen( Scaffold( topBar = { InventoryTopAppBar( - title = stringResource(ItemDetailsDestination.titleRes), + title = stringResource(TaskDetailsDestination.titleRes), canNavigateBack = true, navigateUp = navigateBack ) @@ -103,7 +103,7 @@ fun ItemDetailsScreen( ) { Icon( imageVector = Icons.Default.Edit, - contentDescription = stringResource(R.string.edit_item_title), + contentDescription = stringResource(R.string.edit_task_title), ) } }, @@ -194,7 +194,7 @@ fun ItemDetails( verticalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.padding_medium)) ) { ItemDetailsRow( - labelResID = R.string.item, + labelResID = R.string.task, itemDetail = task.name, modifier = Modifier.padding( horizontal = dimensionResource( diff --git a/app/src/main/java/com/example/inventory/ui/item/ItemDetailsViewModel.kt b/app/src/main/java/com/example/inventory/ui/task/TaskDetailsViewModel.kt similarity index 95% rename from app/src/main/java/com/example/inventory/ui/item/ItemDetailsViewModel.kt rename to app/src/main/java/com/example/inventory/ui/task/TaskDetailsViewModel.kt index 0c290ec4..74c1056a 100644 --- a/app/src/main/java/com/example/inventory/ui/item/ItemDetailsViewModel.kt +++ b/app/src/main/java/com/example/inventory/ui/task/TaskDetailsViewModel.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.example.inventory.ui.item +package com.example.inventory.ui.task import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel @@ -35,7 +35,7 @@ class ItemDetailsViewModel( private val tasksRepository: TasksRepository, ) : ViewModel() { - private val itemId: Int = checkNotNull(savedStateHandle[ItemDetailsDestination.itemIdArg]) + private val itemId: Int = checkNotNull(savedStateHandle[TaskDetailsDestination.taskIdArg]) /** * Holds the item details ui state. The data is retrieved from [TasksRepository] and mapped to diff --git a/app/src/main/java/com/example/inventory/ui/item/ItemEditScreen.kt b/app/src/main/java/com/example/inventory/ui/task/TaskEditScreen.kt similarity index 95% rename from app/src/main/java/com/example/inventory/ui/item/ItemEditScreen.kt rename to app/src/main/java/com/example/inventory/ui/task/TaskEditScreen.kt index e290a3c2..2da2d9a8 100644 --- a/app/src/main/java/com/example/inventory/ui/item/ItemEditScreen.kt +++ b/app/src/main/java/com/example/inventory/ui/task/TaskEditScreen.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.example.inventory.ui.item +package com.example.inventory.ui.task import androidx.compose.foundation.layout.calculateEndPadding import androidx.compose.foundation.layout.calculateStartPadding @@ -39,7 +39,7 @@ import kotlinx.coroutines.launch object ItemEditDestination : NavigationDestination { override val route = "item_edit" - override val titleRes = R.string.edit_item_title + override val titleRes = R.string.edit_task_title const val itemIdArg = "itemId" val routeWithArgs = "$route/{$itemIdArg}" } @@ -50,7 +50,7 @@ fun ItemEditScreen( navigateBack: () -> Unit, onNavigateUp: () -> Unit, modifier: Modifier = Modifier, - viewModel: ItemEditViewModel = viewModel(factory = AppViewModelProvider.Factory) + viewModel: TaskEditViewModel = viewModel(factory = AppViewModelProvider.Factory) ) { val coroutineScope = rememberCoroutineScope() Scaffold( diff --git a/app/src/main/java/com/example/inventory/ui/item/ItemEditViewModel.kt b/app/src/main/java/com/example/inventory/ui/task/TaskEditViewModel.kt similarity index 97% rename from app/src/main/java/com/example/inventory/ui/item/ItemEditViewModel.kt rename to app/src/main/java/com/example/inventory/ui/task/TaskEditViewModel.kt index 244bc2a7..bfaa099a 100644 --- a/app/src/main/java/com/example/inventory/ui/item/ItemEditViewModel.kt +++ b/app/src/main/java/com/example/inventory/ui/task/TaskEditViewModel.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.example.inventory.ui.item +package com.example.inventory.ui.task import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -30,7 +30,7 @@ import kotlinx.coroutines.launch /** * ViewModel to retrieve and update an item from the [TasksRepository]'s data source. */ -class ItemEditViewModel( +class TaskEditViewModel( savedStateHandle: SavedStateHandle, private val tasksRepository: TasksRepository ) : ViewModel() { diff --git a/app/src/main/java/com/example/inventory/ui/item/ItemEntryScreen.kt b/app/src/main/java/com/example/inventory/ui/task/TaskEntryScreen.kt similarity index 97% rename from app/src/main/java/com/example/inventory/ui/item/ItemEntryScreen.kt rename to app/src/main/java/com/example/inventory/ui/task/TaskEntryScreen.kt index 36918c81..59d862f6 100644 --- a/app/src/main/java/com/example/inventory/ui/item/ItemEntryScreen.kt +++ b/app/src/main/java/com/example/inventory/ui/task/TaskEntryScreen.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.example.inventory.ui.item +package com.example.inventory.ui.task import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -139,7 +139,7 @@ fun ItemInputForm( OutlinedTextField( value = itemDetails.name, onValueChange = { onValueChange(itemDetails.copy(name = it)) }, - label = { Text(stringResource(R.string.item_name_req)) }, + label = { Text(stringResource(R.string.task_name_req)) }, colors = OutlinedTextFieldDefaults.colors( focusedContainerColor = MaterialTheme.colorScheme.secondaryContainer, unfocusedContainerColor = MaterialTheme.colorScheme.secondaryContainer, @@ -153,7 +153,7 @@ fun ItemInputForm( value = itemDetails.price, onValueChange = { onValueChange(itemDetails.copy(price = it)) }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal), - label = { Text(stringResource(R.string.item_price_req)) }, + label = { Text(stringResource(R.string.task_price_req)) }, colors = OutlinedTextFieldDefaults.colors( focusedContainerColor = MaterialTheme.colorScheme.secondaryContainer, unfocusedContainerColor = MaterialTheme.colorScheme.secondaryContainer, diff --git a/app/src/main/java/com/example/inventory/ui/item/ItemEntryViewModel.kt b/app/src/main/java/com/example/inventory/ui/task/TaskEntryViewModel.kt similarity index 98% rename from app/src/main/java/com/example/inventory/ui/item/ItemEntryViewModel.kt rename to app/src/main/java/com/example/inventory/ui/task/TaskEntryViewModel.kt index 7056e8f5..4fdc9fb3 100644 --- a/app/src/main/java/com/example/inventory/ui/item/ItemEntryViewModel.kt +++ b/app/src/main/java/com/example/inventory/ui/task/TaskEntryViewModel.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.example.inventory.ui.item +package com.example.inventory.ui.task import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5b0306ef..4024d166 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -20,12 +20,12 @@ Delete Are you sure you want to delete? Oops!\nNo tasks in the lists.\nTap + to add. - Edit Item - Item - Item Details + Edit Task + Task + Task Details Add Task - Item Name* - Item Price* + Task Name* + Task Price* No Price Quantity in stock From 6c9599791020f34fdbec5693f684a7eb4a68b309 Mon Sep 17 00:00:00 2001 From: Max7414 <90671891+Max7414@users.noreply.github.com> Date: Mon, 12 May 2025 05:56:34 +0800 Subject: [PATCH 09/32] more item to task refactor --- .../inventory/ui/AppViewModelProvider.kt | 4 +- .../ui/navigation/InventoryNavGraph.kt | 6 +- .../inventory/ui/task/TaskDetailsScreen.kt | 64 +++++++++---------- .../inventory/ui/task/TaskDetailsViewModel.kt | 18 +++--- .../inventory/ui/task/TaskEditViewModel.kt | 10 +-- .../inventory/ui/task/TaskEntryScreen.kt | 22 +++---- .../inventory/ui/task/TaskEntryViewModel.kt | 22 +++---- 7 files changed, 73 insertions(+), 73 deletions(-) diff --git a/app/src/main/java/com/example/inventory/ui/AppViewModelProvider.kt b/app/src/main/java/com/example/inventory/ui/AppViewModelProvider.kt index 2db9830d..bc1c8157 100644 --- a/app/src/main/java/com/example/inventory/ui/AppViewModelProvider.kt +++ b/app/src/main/java/com/example/inventory/ui/AppViewModelProvider.kt @@ -24,7 +24,7 @@ import androidx.lifecycle.viewmodel.initializer import androidx.lifecycle.viewmodel.viewModelFactory import com.example.inventory.InventoryApplication import com.example.inventory.ui.home.HomeViewModel -import com.example.inventory.ui.task.ItemDetailsViewModel +import com.example.inventory.ui.task.TaskDetailsViewModel import com.example.inventory.ui.task.TaskEditViewModel import com.example.inventory.ui.task.ItemEntryViewModel @@ -47,7 +47,7 @@ object AppViewModelProvider { // Initializer for ItemDetailsViewModel initializer { - ItemDetailsViewModel( + TaskDetailsViewModel( this.createSavedStateHandle(), inventoryApplication().container.tasksRepository ) diff --git a/app/src/main/java/com/example/inventory/ui/navigation/InventoryNavGraph.kt b/app/src/main/java/com/example/inventory/ui/navigation/InventoryNavGraph.kt index eb1e2e13..a499264d 100644 --- a/app/src/main/java/com/example/inventory/ui/navigation/InventoryNavGraph.kt +++ b/app/src/main/java/com/example/inventory/ui/navigation/InventoryNavGraph.kt @@ -26,7 +26,7 @@ import androidx.navigation.navArgument import com.example.inventory.ui.home.HomeDestination import com.example.inventory.ui.home.HomeScreen import com.example.inventory.ui.task.TaskDetailsDestination -import com.example.inventory.ui.task.ItemDetailsScreen +import com.example.inventory.ui.task.TaskDetailsScreen import com.example.inventory.ui.task.ItemEditDestination import com.example.inventory.ui.task.ItemEditScreen import com.example.inventory.ui.task.ItemEntryDestination @@ -65,8 +65,8 @@ fun InventoryNavHost( type = NavType.IntType }) ) { - ItemDetailsScreen( - navigateToEditItem = { navController.navigate("${ItemEditDestination.route}/$it") }, + TaskDetailsScreen( + navigateToEditTask = { navController.navigate("${ItemEditDestination.route}/$it") }, navigateBack = { navController.navigateUp() } ) } diff --git a/app/src/main/java/com/example/inventory/ui/task/TaskDetailsScreen.kt b/app/src/main/java/com/example/inventory/ui/task/TaskDetailsScreen.kt index ed1e5aef..984801bf 100644 --- a/app/src/main/java/com/example/inventory/ui/task/TaskDetailsScreen.kt +++ b/app/src/main/java/com/example/inventory/ui/task/TaskDetailsScreen.kt @@ -67,19 +67,19 @@ import com.example.inventory.ui.theme.InventoryTheme import kotlinx.coroutines.launch object TaskDetailsDestination : NavigationDestination { - override val route = "item_details" + override val route = "task_details" override val titleRes = R.string.task_detail_title - const val taskIdArg = "itemId" + const val taskIdArg = "taskId" val routeWithArgs = "$route/{$taskIdArg}" } @OptIn(ExperimentalMaterial3Api::class) @Composable -fun ItemDetailsScreen( - navigateToEditItem: (Int) -> Unit, +fun TaskDetailsScreen( + navigateToEditTask: (Int) -> Unit, navigateBack: () -> Unit, modifier: Modifier = Modifier, - viewModel: ItemDetailsViewModel = viewModel(factory = AppViewModelProvider.Factory) + viewModel: TaskDetailsViewModel = viewModel(factory = AppViewModelProvider.Factory) ) { val uiState = viewModel.uiState.collectAsState() val coroutineScope = rememberCoroutineScope() @@ -93,7 +93,7 @@ fun ItemDetailsScreen( }, floatingActionButton = { FloatingActionButton( - onClick = { navigateToEditItem(uiState.value.itemDetails.id) }, + onClick = { navigateToEditTask(uiState.value.taskDetails.id) }, shape = MaterialTheme.shapes.medium, modifier = Modifier .padding( @@ -109,16 +109,16 @@ fun ItemDetailsScreen( }, modifier = modifier, ) { innerPadding -> - ItemDetailsBody( - itemDetailsUiState = uiState.value, - onSellItem = { viewModel.reduceQuantityByOne() }, + TaskDetailsBody( + taskDetailsUiState = uiState.value, + onSellTask = { viewModel.reduceQuantityByOne() }, onDelete = { // Note: If the user rotates the screen very fast, the operation may get cancelled - // and the item may not be deleted from the Database. This is because when config + // and the task may not be deleted from the Database. This is because when config // change occurs, the Activity will be recreated and the rememberCoroutineScope will // be cancelled - since the scope is bound to composition. coroutineScope.launch { - viewModel.deleteItem() + viewModel.deleteTask() navigateBack() } }, @@ -134,9 +134,9 @@ fun ItemDetailsScreen( } @Composable -private fun ItemDetailsBody( - itemDetailsUiState: ItemDetailsUiState, - onSellItem: () -> Unit, +private fun TaskDetailsBody( + taskDetailsUiState: TaskDetailsUiState, + onSellTask: () -> Unit, onDelete: () -> Unit, modifier: Modifier = Modifier ) { @@ -145,14 +145,14 @@ private fun ItemDetailsBody( verticalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.padding_medium)) ) { var deleteConfirmationRequired by rememberSaveable { mutableStateOf(false) } - ItemDetails( - task = itemDetailsUiState.itemDetails.toItem(), modifier = Modifier.fillMaxWidth() + TaskDetails( + task = taskDetailsUiState.taskDetails.toTask(), modifier = Modifier.fillMaxWidth() ) Button( - onClick = onSellItem, + onClick = onSellTask, modifier = Modifier.fillMaxWidth(), shape = MaterialTheme.shapes.small, - enabled = !itemDetailsUiState.outOfStock + enabled = !taskDetailsUiState.outOfStock ) { Text(stringResource(R.string.sell)) } @@ -178,7 +178,7 @@ private fun ItemDetailsBody( @Composable -fun ItemDetails( +fun TaskDetails( task: Task, modifier: Modifier = Modifier ) { Card( @@ -193,9 +193,9 @@ fun ItemDetails( .padding(dimensionResource(id = R.dimen.padding_medium)), verticalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.padding_medium)) ) { - ItemDetailsRow( + TaskDetailsRow( labelResID = R.string.task, - itemDetail = task.name, + taskDetail = task.name, modifier = Modifier.padding( horizontal = dimensionResource( id = R.dimen @@ -203,9 +203,9 @@ fun ItemDetails( ) ) ) - ItemDetailsRow( + TaskDetailsRow( labelResID = R.string.quantity_in_stock, - itemDetail = task.quantity.toString(), + taskDetail = task.quantity.toString(), modifier = Modifier.padding( horizontal = dimensionResource( id = R.dimen @@ -213,9 +213,9 @@ fun ItemDetails( ) ) ) - ItemDetailsRow( + TaskDetailsRow( labelResID = R.string.price, - itemDetail = task.formatedPrice(), + taskDetail = task.formatedPrice(), modifier = Modifier.padding( horizontal = dimensionResource( id = R.dimen @@ -229,13 +229,13 @@ fun ItemDetails( } @Composable -private fun ItemDetailsRow( - @StringRes labelResID: Int, itemDetail: String, modifier: Modifier = Modifier +private fun TaskDetailsRow( + @StringRes labelResID: Int, taskDetail: String, modifier: Modifier = Modifier ) { Row(modifier = modifier) { Text(text = stringResource(labelResID)) Spacer(modifier = Modifier.weight(1f)) - Text(text = itemDetail, fontWeight = FontWeight.Bold) + Text(text = taskDetail, fontWeight = FontWeight.Bold) } } @@ -261,10 +261,10 @@ private fun DeleteConfirmationDialog( @Preview(showBackground = true) @Composable -fun ItemDetailsScreenPreview() { +fun TaskDetailsScreenPreview() { InventoryTheme { - ItemDetailsBody(ItemDetailsUiState( - outOfStock = true, itemDetails = ItemDetails(1, "Pen", "$100", "10") - ), onSellItem = {}, onDelete = {}) + TaskDetailsBody(TaskDetailsUiState( + outOfStock = true, taskDetails = TaskDetails(1, "Pen", "$100", "10") + ), onSellTask = {}, onDelete = {}) } } diff --git a/app/src/main/java/com/example/inventory/ui/task/TaskDetailsViewModel.kt b/app/src/main/java/com/example/inventory/ui/task/TaskDetailsViewModel.kt index 74c1056a..230d4cf7 100644 --- a/app/src/main/java/com/example/inventory/ui/task/TaskDetailsViewModel.kt +++ b/app/src/main/java/com/example/inventory/ui/task/TaskDetailsViewModel.kt @@ -30,7 +30,7 @@ import kotlinx.coroutines.launch /** * ViewModel to retrieve, update and delete an item from the [TasksRepository]'s data source. */ -class ItemDetailsViewModel( +class TaskDetailsViewModel( savedStateHandle: SavedStateHandle, private val tasksRepository: TasksRepository, ) : ViewModel() { @@ -41,15 +41,15 @@ class ItemDetailsViewModel( * Holds the item details ui state. The data is retrieved from [TasksRepository] and mapped to * the UI state. */ - val uiState: StateFlow = + val uiState: StateFlow = tasksRepository.getTaskStream(itemId) .filterNotNull() .map { - ItemDetailsUiState(outOfStock = it.quantity <= 0, itemDetails = it.toItemDetails()) + TaskDetailsUiState(outOfStock = it.quantity <= 0, taskDetails = it.toItemDetails()) }.stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(TIMEOUT_MILLIS), - initialValue = ItemDetailsUiState() + initialValue = TaskDetailsUiState() ) /** @@ -57,7 +57,7 @@ class ItemDetailsViewModel( */ fun reduceQuantityByOne() { viewModelScope.launch { - val currentItem = uiState.value.itemDetails.toItem() + val currentItem = uiState.value.taskDetails.toTask() if (currentItem.quantity > 0) { tasksRepository.updateTask(currentItem.copy(quantity = currentItem.quantity - 1)) } @@ -67,8 +67,8 @@ class ItemDetailsViewModel( /** * Deletes the item from the [TasksRepository]'s data source. */ - suspend fun deleteItem() { - tasksRepository.deleteTask(uiState.value.itemDetails.toItem()) + suspend fun deleteTask() { + tasksRepository.deleteTask(uiState.value.taskDetails.toTask()) } companion object { @@ -79,7 +79,7 @@ class ItemDetailsViewModel( /** * UI state for ItemDetailsScreen */ -data class ItemDetailsUiState( +data class TaskDetailsUiState( val outOfStock: Boolean = true, - val itemDetails: ItemDetails = ItemDetails() + val taskDetails: TaskDetails = TaskDetails() ) diff --git a/app/src/main/java/com/example/inventory/ui/task/TaskEditViewModel.kt b/app/src/main/java/com/example/inventory/ui/task/TaskEditViewModel.kt index bfaa099a..757e3ceb 100644 --- a/app/src/main/java/com/example/inventory/ui/task/TaskEditViewModel.kt +++ b/app/src/main/java/com/example/inventory/ui/task/TaskEditViewModel.kt @@ -56,8 +56,8 @@ class TaskEditViewModel( * Update the item in the [TasksRepository]'s data source */ suspend fun updateItem() { - if (validateInput(itemUiState.itemDetails)) { - tasksRepository.updateTask(itemUiState.itemDetails.toItem()) + if (validateInput(itemUiState.taskDetails)) { + tasksRepository.updateTask(itemUiState.taskDetails.toTask()) } } @@ -65,12 +65,12 @@ class TaskEditViewModel( * Updates the [itemUiState] with the value provided in the argument. This method also triggers * a validation for input values. */ - fun updateUiState(itemDetails: ItemDetails) { + fun updateUiState(taskDetails: TaskDetails) { itemUiState = - ItemUiState(itemDetails = itemDetails, isEntryValid = validateInput(itemDetails)) + ItemUiState(taskDetails = taskDetails, isEntryValid = validateInput(taskDetails)) } - private fun validateInput(uiState: ItemDetails = itemUiState.itemDetails): Boolean { + private fun validateInput(uiState: TaskDetails = itemUiState.taskDetails): Boolean { return with(uiState) { name.isNotBlank() && price.isNotBlank() && quantity.isNotBlank() } diff --git a/app/src/main/java/com/example/inventory/ui/task/TaskEntryScreen.kt b/app/src/main/java/com/example/inventory/ui/task/TaskEntryScreen.kt index 59d862f6..d7295f21 100644 --- a/app/src/main/java/com/example/inventory/ui/task/TaskEntryScreen.kt +++ b/app/src/main/java/com/example/inventory/ui/task/TaskEntryScreen.kt @@ -101,7 +101,7 @@ fun ItemEntryScreen( @Composable fun ItemEntryBody( itemUiState: ItemUiState, - onItemValueChange: (ItemDetails) -> Unit, + onItemValueChange: (TaskDetails) -> Unit, onSaveClick: () -> Unit, modifier: Modifier = Modifier ) { @@ -110,7 +110,7 @@ fun ItemEntryBody( verticalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.padding_large)) ) { ItemInputForm( - itemDetails = itemUiState.itemDetails, + taskDetails = itemUiState.taskDetails, onValueChange = onItemValueChange, modifier = Modifier.fillMaxWidth() ) @@ -127,9 +127,9 @@ fun ItemEntryBody( @Composable fun ItemInputForm( - itemDetails: ItemDetails, + taskDetails: TaskDetails, modifier: Modifier = Modifier, - onValueChange: (ItemDetails) -> Unit = {}, + onValueChange: (TaskDetails) -> Unit = {}, enabled: Boolean = true ) { Column( @@ -137,8 +137,8 @@ fun ItemInputForm( verticalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.padding_medium)) ) { OutlinedTextField( - value = itemDetails.name, - onValueChange = { onValueChange(itemDetails.copy(name = it)) }, + value = taskDetails.name, + onValueChange = { onValueChange(taskDetails.copy(name = it)) }, label = { Text(stringResource(R.string.task_name_req)) }, colors = OutlinedTextFieldDefaults.colors( focusedContainerColor = MaterialTheme.colorScheme.secondaryContainer, @@ -150,8 +150,8 @@ fun ItemInputForm( singleLine = true ) OutlinedTextField( - value = itemDetails.price, - onValueChange = { onValueChange(itemDetails.copy(price = it)) }, + value = taskDetails.price, + onValueChange = { onValueChange(taskDetails.copy(price = it)) }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal), label = { Text(stringResource(R.string.task_price_req)) }, colors = OutlinedTextFieldDefaults.colors( @@ -165,8 +165,8 @@ fun ItemInputForm( singleLine = true ) OutlinedTextField( - value = itemDetails.quantity, - onValueChange = { onValueChange(itemDetails.copy(quantity = it)) }, + value = taskDetails.quantity, + onValueChange = { onValueChange(taskDetails.copy(quantity = it)) }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), label = { Text(stringResource(R.string.quantity_req)) }, colors = OutlinedTextFieldDefaults.colors( @@ -192,7 +192,7 @@ fun ItemInputForm( private fun ItemEntryScreenPreview() { InventoryTheme { ItemEntryBody(itemUiState = ItemUiState( - ItemDetails( + TaskDetails( name = "Item name", price = "10.00", quantity = "5" ) ), onItemValueChange = {}, onSaveClick = {}) diff --git a/app/src/main/java/com/example/inventory/ui/task/TaskEntryViewModel.kt b/app/src/main/java/com/example/inventory/ui/task/TaskEntryViewModel.kt index 4fdc9fb3..4bf5e9c8 100644 --- a/app/src/main/java/com/example/inventory/ui/task/TaskEntryViewModel.kt +++ b/app/src/main/java/com/example/inventory/ui/task/TaskEntryViewModel.kt @@ -39,9 +39,9 @@ class ItemEntryViewModel(private val tasksRepository: TasksRepository) : ViewMod * Updates the [itemUiState] with the value provided in the argument. This method also triggers * a validation for input values. */ - fun updateUiState(itemDetails: ItemDetails) { + fun updateUiState(taskDetails: TaskDetails) { itemUiState = - ItemUiState(itemDetails = itemDetails, isEntryValid = validateInput(itemDetails)) + ItemUiState(taskDetails = taskDetails, isEntryValid = validateInput(taskDetails)) } /** @@ -49,11 +49,11 @@ class ItemEntryViewModel(private val tasksRepository: TasksRepository) : ViewMod */ suspend fun saveItem() { if (validateInput()) { - tasksRepository.insertTask(itemUiState.itemDetails.toItem()) + tasksRepository.insertTask(itemUiState.taskDetails.toTask()) } } - private fun validateInput(uiState: ItemDetails = itemUiState.itemDetails): Boolean { + private fun validateInput(uiState: TaskDetails = itemUiState.taskDetails): Boolean { return with(uiState) { name.isNotBlank() && price.isNotBlank() && quantity.isNotBlank() } @@ -64,11 +64,11 @@ class ItemEntryViewModel(private val tasksRepository: TasksRepository) : ViewMod * Represents Ui State for an Item. */ data class ItemUiState( - val itemDetails: ItemDetails = ItemDetails(), + val taskDetails: TaskDetails = TaskDetails(), val isEntryValid: Boolean = false ) -data class ItemDetails( +data class TaskDetails( val id: Int = 0, val name: String = "", val price: String = "", @@ -76,11 +76,11 @@ data class ItemDetails( ) /** - * Extension function to convert [ItemUiState] to [Task]. If the value of [ItemDetails.price] is + * Extension function to convert [ItemUiState] to [Task]. If the value of [TaskDetails.price] is * not a valid [Double], then the price will be set to 0.0. Similarly if the value of * [ItemUiState] is not a valid [Int], then the quantity will be set to 0 */ -fun ItemDetails.toItem(): Task = Task( +fun TaskDetails.toTask(): Task = Task( id = id, name = name, price = price.toDoubleOrNull() ?: 0.0, @@ -95,14 +95,14 @@ fun Task.formatedPrice(): String { * Extension function to convert [Task] to [ItemUiState] */ fun Task.toItemUiState(isEntryValid: Boolean = false): ItemUiState = ItemUiState( - itemDetails = this.toItemDetails(), + taskDetails = this.toItemDetails(), isEntryValid = isEntryValid ) /** - * Extension function to convert [Task] to [ItemDetails] + * Extension function to convert [Task] to [TaskDetails] */ -fun Task.toItemDetails(): ItemDetails = ItemDetails( +fun Task.toItemDetails(): TaskDetails = TaskDetails( id = id, name = name, price = price.toString(), From f9dac3bbc84b1ff668fa289655807aaa4a070b0f Mon Sep 17 00:00:00 2001 From: Max7414 <90671891+Max7414@users.noreply.github.com> Date: Mon, 12 May 2025 05:58:36 +0800 Subject: [PATCH 10/32] task.q <0 remove --- .../java/com/example/inventory/ui/task/TaskDetailsScreen.kt | 2 +- .../com/example/inventory/ui/task/TaskDetailsViewModel.kt | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/example/inventory/ui/task/TaskDetailsScreen.kt b/app/src/main/java/com/example/inventory/ui/task/TaskDetailsScreen.kt index 984801bf..e5df8270 100644 --- a/app/src/main/java/com/example/inventory/ui/task/TaskDetailsScreen.kt +++ b/app/src/main/java/com/example/inventory/ui/task/TaskDetailsScreen.kt @@ -205,7 +205,7 @@ fun TaskDetails( ) TaskDetailsRow( labelResID = R.string.quantity_in_stock, - taskDetail = task.quantity.toString(), + taskDetail = task.priority.toString(), modifier = Modifier.padding( horizontal = dimensionResource( id = R.dimen diff --git a/app/src/main/java/com/example/inventory/ui/task/TaskDetailsViewModel.kt b/app/src/main/java/com/example/inventory/ui/task/TaskDetailsViewModel.kt index 230d4cf7..575b2ace 100644 --- a/app/src/main/java/com/example/inventory/ui/task/TaskDetailsViewModel.kt +++ b/app/src/main/java/com/example/inventory/ui/task/TaskDetailsViewModel.kt @@ -45,7 +45,7 @@ class TaskDetailsViewModel( tasksRepository.getTaskStream(itemId) .filterNotNull() .map { - TaskDetailsUiState(outOfStock = it.quantity <= 0, taskDetails = it.toItemDetails()) + TaskDetailsUiState(taskDetails = it.toItemDetails()) }.stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(TIMEOUT_MILLIS), @@ -58,9 +58,6 @@ class TaskDetailsViewModel( fun reduceQuantityByOne() { viewModelScope.launch { val currentItem = uiState.value.taskDetails.toTask() - if (currentItem.quantity > 0) { - tasksRepository.updateTask(currentItem.copy(quantity = currentItem.quantity - 1)) - } } } @@ -80,6 +77,5 @@ class TaskDetailsViewModel( * UI state for ItemDetailsScreen */ data class TaskDetailsUiState( - val outOfStock: Boolean = true, val taskDetails: TaskDetails = TaskDetails() ) From 7516cc2eac83fd364bdbc484a89f1b61f7a1ad69 Mon Sep 17 00:00:00 2001 From: Max7414 <90671891+Max7414@users.noreply.github.com> Date: Mon, 12 May 2025 06:18:48 +0800 Subject: [PATCH 11/32] Quinity to priority price remove --- .../example/inventory/ui/task/TaskDetailsScreen.kt | 6 +++--- .../example/inventory/ui/task/TaskEditViewModel.kt | 2 +- .../example/inventory/ui/task/TaskEntryScreen.kt | 6 +++--- .../inventory/ui/task/TaskEntryViewModel.kt | 14 ++++---------- 4 files changed, 11 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/com/example/inventory/ui/task/TaskDetailsScreen.kt b/app/src/main/java/com/example/inventory/ui/task/TaskDetailsScreen.kt index e5df8270..4af98a93 100644 --- a/app/src/main/java/com/example/inventory/ui/task/TaskDetailsScreen.kt +++ b/app/src/main/java/com/example/inventory/ui/task/TaskDetailsScreen.kt @@ -152,7 +152,7 @@ private fun TaskDetailsBody( onClick = onSellTask, modifier = Modifier.fillMaxWidth(), shape = MaterialTheme.shapes.small, - enabled = !taskDetailsUiState.outOfStock + //enabled = !taskDetailsUiState.outOfStock ) { Text(stringResource(R.string.sell)) } @@ -205,7 +205,7 @@ fun TaskDetails( ) TaskDetailsRow( labelResID = R.string.quantity_in_stock, - taskDetail = task.priority.toString(), + taskDetail = task.priority, modifier = Modifier.padding( horizontal = dimensionResource( id = R.dimen @@ -264,7 +264,7 @@ private fun DeleteConfirmationDialog( fun TaskDetailsScreenPreview() { InventoryTheme { TaskDetailsBody(TaskDetailsUiState( - outOfStock = true, taskDetails = TaskDetails(1, "Pen", "$100", "10") + taskDetails = TaskDetails(1, "Pen", "$100", "10") ), onSellTask = {}, onDelete = {}) } } diff --git a/app/src/main/java/com/example/inventory/ui/task/TaskEditViewModel.kt b/app/src/main/java/com/example/inventory/ui/task/TaskEditViewModel.kt index 757e3ceb..6f838d5d 100644 --- a/app/src/main/java/com/example/inventory/ui/task/TaskEditViewModel.kt +++ b/app/src/main/java/com/example/inventory/ui/task/TaskEditViewModel.kt @@ -72,7 +72,7 @@ class TaskEditViewModel( private fun validateInput(uiState: TaskDetails = itemUiState.taskDetails): Boolean { return with(uiState) { - name.isNotBlank() && price.isNotBlank() && quantity.isNotBlank() + name.isNotBlank() && price.isNotBlank() && priority.isNotBlank() } } } diff --git a/app/src/main/java/com/example/inventory/ui/task/TaskEntryScreen.kt b/app/src/main/java/com/example/inventory/ui/task/TaskEntryScreen.kt index d7295f21..0b04feca 100644 --- a/app/src/main/java/com/example/inventory/ui/task/TaskEntryScreen.kt +++ b/app/src/main/java/com/example/inventory/ui/task/TaskEntryScreen.kt @@ -165,8 +165,8 @@ fun ItemInputForm( singleLine = true ) OutlinedTextField( - value = taskDetails.quantity, - onValueChange = { onValueChange(taskDetails.copy(quantity = it)) }, + value = taskDetails.priority, + onValueChange = { onValueChange(taskDetails.copy(priority = it)) }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), label = { Text(stringResource(R.string.quantity_req)) }, colors = OutlinedTextFieldDefaults.colors( @@ -193,7 +193,7 @@ private fun ItemEntryScreenPreview() { InventoryTheme { ItemEntryBody(itemUiState = ItemUiState( TaskDetails( - name = "Item name", price = "10.00", quantity = "5" + name = "Item name", price = "10.00", priority = "5" ) ), onItemValueChange = {}, onSaveClick = {}) } diff --git a/app/src/main/java/com/example/inventory/ui/task/TaskEntryViewModel.kt b/app/src/main/java/com/example/inventory/ui/task/TaskEntryViewModel.kt index 4bf5e9c8..2910ca94 100644 --- a/app/src/main/java/com/example/inventory/ui/task/TaskEntryViewModel.kt +++ b/app/src/main/java/com/example/inventory/ui/task/TaskEntryViewModel.kt @@ -55,7 +55,7 @@ class ItemEntryViewModel(private val tasksRepository: TasksRepository) : ViewMod private fun validateInput(uiState: TaskDetails = itemUiState.taskDetails): Boolean { return with(uiState) { - name.isNotBlank() && price.isNotBlank() && quantity.isNotBlank() + name.isNotBlank() && priority.isNotBlank() } } } @@ -71,8 +71,7 @@ data class ItemUiState( data class TaskDetails( val id: Int = 0, val name: String = "", - val price: String = "", - val quantity: String = "", + val priority: String = "", ) /** @@ -83,13 +82,9 @@ data class TaskDetails( fun TaskDetails.toTask(): Task = Task( id = id, name = name, - price = price.toDoubleOrNull() ?: 0.0, - quantity = quantity.toIntOrNull() ?: 0 + priority = priority ) -fun Task.formatedPrice(): String { - return NumberFormat.getCurrencyInstance().format(price) -} /** * Extension function to convert [Task] to [ItemUiState] @@ -105,6 +100,5 @@ fun Task.toItemUiState(isEntryValid: Boolean = false): ItemUiState = ItemUiState fun Task.toItemDetails(): TaskDetails = TaskDetails( id = id, name = name, - price = price.toString(), - quantity = quantity.toString() + priority = priority ) From c160c8ffa00040ca642f689ff9ac0674bbeec7e3 Mon Sep 17 00:00:00 2001 From: Max7414 <90671891+Max7414@users.noreply.github.com> Date: Mon, 12 May 2025 06:19:39 +0800 Subject: [PATCH 12/32] Preview edited price remove --- app/src/main/java/com/example/inventory/ui/home/HomeScreen.kt | 2 -- .../java/com/example/inventory/ui/task/TaskDetailsScreen.kt | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/app/src/main/java/com/example/inventory/ui/home/HomeScreen.kt b/app/src/main/java/com/example/inventory/ui/home/HomeScreen.kt index b7f0bf94..d8209b0d 100644 --- a/app/src/main/java/com/example/inventory/ui/home/HomeScreen.kt +++ b/app/src/main/java/com/example/inventory/ui/home/HomeScreen.kt @@ -59,7 +59,6 @@ import com.example.inventory.InventoryTopAppBar import com.example.inventory.R import com.example.inventory.data.Task import com.example.inventory.ui.AppViewModelProvider -import com.example.inventory.ui.task.formatedPrice import com.example.inventory.ui.navigation.NavigationDestination import com.example.inventory.ui.theme.InventoryTheme @@ -186,7 +185,6 @@ private fun InventoryItem( ) Spacer(Modifier.weight(1f)) Text( - text = task.formatedPrice(), style = MaterialTheme.typography.titleMedium ) } diff --git a/app/src/main/java/com/example/inventory/ui/task/TaskDetailsScreen.kt b/app/src/main/java/com/example/inventory/ui/task/TaskDetailsScreen.kt index 4af98a93..2f159575 100644 --- a/app/src/main/java/com/example/inventory/ui/task/TaskDetailsScreen.kt +++ b/app/src/main/java/com/example/inventory/ui/task/TaskDetailsScreen.kt @@ -264,7 +264,7 @@ private fun DeleteConfirmationDialog( fun TaskDetailsScreenPreview() { InventoryTheme { TaskDetailsBody(TaskDetailsUiState( - taskDetails = TaskDetails(1, "Pen", "$100", "10") + taskDetails = TaskDetails(1, "Task1", "High") ), onSellTask = {}, onDelete = {}) } } From dbdcee886b40aa77fd4935fdce28d6c85a983c9f Mon Sep 17 00:00:00 2001 From: Max7414 <90671891+Max7414@users.noreply.github.com> Date: Mon, 12 May 2025 06:20:19 +0800 Subject: [PATCH 13/32] text = "WAITING TO BE REMOVE", locate --- app/src/main/java/com/example/inventory/ui/home/HomeScreen.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/com/example/inventory/ui/home/HomeScreen.kt b/app/src/main/java/com/example/inventory/ui/home/HomeScreen.kt index d8209b0d..07dd4099 100644 --- a/app/src/main/java/com/example/inventory/ui/home/HomeScreen.kt +++ b/app/src/main/java/com/example/inventory/ui/home/HomeScreen.kt @@ -185,6 +185,7 @@ private fun InventoryItem( ) Spacer(Modifier.weight(1f)) Text( + text = "WAITING TO BE REMOVE", style = MaterialTheme.typography.titleMedium ) } From 160cd64ad41c5faf00ecb56c0f5af515133c3800 Mon Sep 17 00:00:00 2001 From: Max7414 <90671891+Max7414@users.noreply.github.com> Date: Mon, 12 May 2025 06:51:24 +0800 Subject: [PATCH 14/32] Runnable setting --- .../com/example/inventory/ui/task/TaskDetailsScreen.kt | 2 +- .../com/example/inventory/ui/task/TaskEditViewModel.kt | 2 +- .../java/com/example/inventory/ui/task/TaskEntryScreen.kt | 7 ++++--- build.gradle.kts | 4 ++-- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/example/inventory/ui/task/TaskDetailsScreen.kt b/app/src/main/java/com/example/inventory/ui/task/TaskDetailsScreen.kt index 2f159575..9c0fc019 100644 --- a/app/src/main/java/com/example/inventory/ui/task/TaskDetailsScreen.kt +++ b/app/src/main/java/com/example/inventory/ui/task/TaskDetailsScreen.kt @@ -215,7 +215,7 @@ fun TaskDetails( ) TaskDetailsRow( labelResID = R.string.price, - taskDetail = task.formatedPrice(), + taskDetail = "READY TO BE REMOVE", modifier = Modifier.padding( horizontal = dimensionResource( id = R.dimen diff --git a/app/src/main/java/com/example/inventory/ui/task/TaskEditViewModel.kt b/app/src/main/java/com/example/inventory/ui/task/TaskEditViewModel.kt index 6f838d5d..a302c987 100644 --- a/app/src/main/java/com/example/inventory/ui/task/TaskEditViewModel.kt +++ b/app/src/main/java/com/example/inventory/ui/task/TaskEditViewModel.kt @@ -72,7 +72,7 @@ class TaskEditViewModel( private fun validateInput(uiState: TaskDetails = itemUiState.taskDetails): Boolean { return with(uiState) { - name.isNotBlank() && price.isNotBlank() && priority.isNotBlank() + name.isNotBlank() && priority.isNotBlank() } } } diff --git a/app/src/main/java/com/example/inventory/ui/task/TaskEntryScreen.kt b/app/src/main/java/com/example/inventory/ui/task/TaskEntryScreen.kt index 0b04feca..79a3b742 100644 --- a/app/src/main/java/com/example/inventory/ui/task/TaskEntryScreen.kt +++ b/app/src/main/java/com/example/inventory/ui/task/TaskEntryScreen.kt @@ -150,8 +150,9 @@ fun ItemInputForm( singleLine = true ) OutlinedTextField( - value = taskDetails.price, - onValueChange = { onValueChange(taskDetails.copy(price = it)) }, + value = "READY TO BE REMOVE", + onValueChange = { //onValueChange(taskDetails.copy(price = it)) + }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal), label = { Text(stringResource(R.string.task_price_req)) }, colors = OutlinedTextFieldDefaults.colors( @@ -193,7 +194,7 @@ private fun ItemEntryScreenPreview() { InventoryTheme { ItemEntryBody(itemUiState = ItemUiState( TaskDetails( - name = "Item name", price = "10.00", priority = "5" + name = "Task name", priority = "LOW" ) ), onItemValueChange = {}, onSaveClick = {}) } diff --git a/build.gradle.kts b/build.gradle.kts index bc180f53..656cba28 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -22,8 +22,8 @@ buildscript { } plugins { - id("com.android.application") version "8.1.4" apply false - id("com.android.library") version "8.1.4" apply false + id("com.android.application") version "8.8.2" apply false + id("com.android.library") version "8.8.2" apply false id("org.jetbrains.kotlin.android") version "2.1.0" apply false id("org.jetbrains.kotlin.plugin.compose") version "2.1.0" apply false } From 1110d3eb729ef377eed6ddc4c511cf547559f839 Mon Sep 17 00:00:00 2001 From: Max7414 <90671891+Max7414@users.noreply.github.com> Date: Mon, 12 May 2025 06:55:40 +0800 Subject: [PATCH 15/32] TaskEntryScreen.kt remove on coulumn --- .../inventory/ui/task/TaskEntryScreen.kt | 21 ++----------------- app/src/main/res/values/strings.xml | 2 +- 2 files changed, 3 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/com/example/inventory/ui/task/TaskEntryScreen.kt b/app/src/main/java/com/example/inventory/ui/task/TaskEntryScreen.kt index 79a3b742..d8ba5199 100644 --- a/app/src/main/java/com/example/inventory/ui/task/TaskEntryScreen.kt +++ b/app/src/main/java/com/example/inventory/ui/task/TaskEntryScreen.kt @@ -47,8 +47,6 @@ import com.example.inventory.ui.AppViewModelProvider import com.example.inventory.ui.navigation.NavigationDestination import com.example.inventory.ui.theme.InventoryTheme import kotlinx.coroutines.launch -import java.util.Currency -import java.util.Locale object ItemEntryDestination : NavigationDestination { override val route = "item_entry" @@ -149,27 +147,12 @@ fun ItemInputForm( enabled = enabled, singleLine = true ) - OutlinedTextField( - value = "READY TO BE REMOVE", - onValueChange = { //onValueChange(taskDetails.copy(price = it)) - }, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal), - label = { Text(stringResource(R.string.task_price_req)) }, - colors = OutlinedTextFieldDefaults.colors( - focusedContainerColor = MaterialTheme.colorScheme.secondaryContainer, - unfocusedContainerColor = MaterialTheme.colorScheme.secondaryContainer, - disabledContainerColor = MaterialTheme.colorScheme.secondaryContainer, - ), - leadingIcon = { Text(Currency.getInstance(Locale.getDefault()).symbol) }, - modifier = Modifier.fillMaxWidth(), - enabled = enabled, - singleLine = true - ) + OutlinedTextField( value = taskDetails.priority, onValueChange = { onValueChange(taskDetails.copy(priority = it)) }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), - label = { Text(stringResource(R.string.quantity_req)) }, + label = { Text(stringResource(R.string.priority_level)) }, colors = OutlinedTextFieldDefaults.colors( focusedContainerColor = MaterialTheme.colorScheme.secondaryContainer, unfocusedContainerColor = MaterialTheme.colorScheme.secondaryContainer, diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4024d166..0bba1334 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -30,7 +30,7 @@ Price Quantity in stock %d in stock - Quantity in Stock* + Priority level* Save Sell Yes From 3d07b32729da821e427d1f47855ca138be49d7d7 Mon Sep 17 00:00:00 2001 From: Max7414 <90671891+Max7414@users.noreply.github.com> Date: Mon, 12 May 2025 06:59:58 +0800 Subject: [PATCH 16/32] price del --- .../java/com/example/inventory/ui/task/TaskDetailsScreen.kt | 2 +- app/src/main/res/values/strings.xml | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/example/inventory/ui/task/TaskDetailsScreen.kt b/app/src/main/java/com/example/inventory/ui/task/TaskDetailsScreen.kt index 9c0fc019..e67c01e1 100644 --- a/app/src/main/java/com/example/inventory/ui/task/TaskDetailsScreen.kt +++ b/app/src/main/java/com/example/inventory/ui/task/TaskDetailsScreen.kt @@ -204,7 +204,7 @@ fun TaskDetails( ) ) TaskDetailsRow( - labelResID = R.string.quantity_in_stock, + labelResID = R.string.priority_level_show, taskDetail = task.priority, modifier = Modifier.padding( horizontal = dimensionResource( diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0bba1334..6bef5901 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -25,10 +25,8 @@ Task Details Add Task Task Name* - Task Price* No - Price - Quantity in stock + Priority level %d in stock Priority level* Save From 7cf0ffe6dd06cb7ebec7ee8cb00170b074cf6c2d Mon Sep 17 00:00:00 2001 From: Max7414 <90671891+Max7414@users.noreply.github.com> Date: Mon, 12 May 2025 07:01:20 +0800 Subject: [PATCH 17/32] Remove price coloumn --- .../example/inventory/ui/task/TaskDetailsScreen.kt | 11 +---------- build.gradle.kts | 4 ++-- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/com/example/inventory/ui/task/TaskDetailsScreen.kt b/app/src/main/java/com/example/inventory/ui/task/TaskDetailsScreen.kt index e67c01e1..80be853a 100644 --- a/app/src/main/java/com/example/inventory/ui/task/TaskDetailsScreen.kt +++ b/app/src/main/java/com/example/inventory/ui/task/TaskDetailsScreen.kt @@ -213,16 +213,7 @@ fun TaskDetails( ) ) ) - TaskDetailsRow( - labelResID = R.string.price, - taskDetail = "READY TO BE REMOVE", - modifier = Modifier.padding( - horizontal = dimensionResource( - id = R.dimen - .padding_medium - ) - ) - ) + } } diff --git a/build.gradle.kts b/build.gradle.kts index 656cba28..da5856ff 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -24,10 +24,10 @@ buildscript { plugins { id("com.android.application") version "8.8.2" apply false id("com.android.library") version "8.8.2" apply false - id("org.jetbrains.kotlin.android") version "2.1.0" apply false + id("org.jetbrains.kotlin.android") version "2.1.20" apply false id("org.jetbrains.kotlin.plugin.compose") version "2.1.0" apply false } tasks.register("clean", Delete::class) { - delete(rootProject.buildDir) + delete(rootProject) } From d271a6b2ff94d7be52b91c84973adb4dc4203650 Mon Sep 17 00:00:00 2001 From: Max7414 <90671891+Max7414@users.noreply.github.com> Date: Mon, 12 May 2025 07:16:01 +0800 Subject: [PATCH 18/32] UI changed --- .../main/java/com/example/inventory/data/InventoryDatabase.kt | 2 +- app/src/main/java/com/example/inventory/ui/home/HomeScreen.kt | 4 ---- app/src/main/res/values/strings.xml | 2 +- build.gradle.kts | 4 ++-- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/example/inventory/data/InventoryDatabase.kt b/app/src/main/java/com/example/inventory/data/InventoryDatabase.kt index d9598176..b8d294d0 100644 --- a/app/src/main/java/com/example/inventory/data/InventoryDatabase.kt +++ b/app/src/main/java/com/example/inventory/data/InventoryDatabase.kt @@ -24,7 +24,7 @@ import androidx.room.RoomDatabase /** * Database class with a singleton Instance object. */ -@Database(entities = [Task::class], version = 1, exportSchema = false) +@Database(entities = [Task::class], version = 2, exportSchema = false) abstract class InventoryDatabase : RoomDatabase() { abstract fun taskDao(): TaskDao diff --git a/app/src/main/java/com/example/inventory/ui/home/HomeScreen.kt b/app/src/main/java/com/example/inventory/ui/home/HomeScreen.kt index 07dd4099..2ede03dc 100644 --- a/app/src/main/java/com/example/inventory/ui/home/HomeScreen.kt +++ b/app/src/main/java/com/example/inventory/ui/home/HomeScreen.kt @@ -184,10 +184,6 @@ private fun InventoryItem( style = MaterialTheme.typography.titleLarge, ) Spacer(Modifier.weight(1f)) - Text( - text = "WAITING TO BE REMOVE", - style = MaterialTheme.typography.titleMedium - ) } Text( text = stringResource(R.string.in_stock, task.priority), diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6bef5901..30c3f049 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -27,7 +27,7 @@ Task Name* No Priority level - %d in stock + Priority %s Priority level* Save Sell diff --git a/build.gradle.kts b/build.gradle.kts index da5856ff..656cba28 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -24,10 +24,10 @@ buildscript { plugins { id("com.android.application") version "8.8.2" apply false id("com.android.library") version "8.8.2" apply false - id("org.jetbrains.kotlin.android") version "2.1.20" apply false + id("org.jetbrains.kotlin.android") version "2.1.0" apply false id("org.jetbrains.kotlin.plugin.compose") version "2.1.0" apply false } tasks.register("clean", Delete::class) { - delete(rootProject) + delete(rootProject.buildDir) } From a31f3b94133de8c5f8c00945e16290125d1217d1 Mon Sep 17 00:00:00 2001 From: Max7414 <90671891+Max7414@users.noreply.github.com> Date: Mon, 12 May 2025 07:21:03 +0800 Subject: [PATCH 19/32] Add color system --- .../example/inventory/ui/home/HomeScreen.kt | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/example/inventory/ui/home/HomeScreen.kt b/app/src/main/java/com/example/inventory/ui/home/HomeScreen.kt index 2ede03dc..adccf2ee 100644 --- a/app/src/main/java/com/example/inventory/ui/home/HomeScreen.kt +++ b/app/src/main/java/com/example/inventory/ui/home/HomeScreen.kt @@ -47,6 +47,7 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.dimensionResource @@ -169,8 +170,17 @@ private fun InventoryList( private fun InventoryItem( task: Task, modifier: Modifier = Modifier ) { + val cardColor = when (task.priority.lowercase()) { + "high" -> MaterialTheme.colorScheme.errorContainer + "medium" -> Color(0xFFFFF9C4) + "low" -> Color(0xFFC8E6C9) + else -> MaterialTheme.colorScheme.surface + } + Card( - modifier = modifier, elevation = CardDefaults.cardElevation(defaultElevation = 2.dp) + modifier = modifier, + elevation = CardDefaults.cardElevation(defaultElevation = 2.dp), + colors = CardDefaults.cardColors(containerColor = cardColor) ) { Column( modifier = Modifier.padding(dimensionResource(id = R.dimen.padding_large)), @@ -184,11 +194,12 @@ private fun InventoryItem( style = MaterialTheme.typography.titleLarge, ) Spacer(Modifier.weight(1f)) + + Text( + text = stringResource(R.string.in_stock, task.priority), + style = MaterialTheme.typography.titleMedium + ) } - Text( - text = stringResource(R.string.in_stock, task.priority), - style = MaterialTheme.typography.titleMedium - ) } } } @@ -198,7 +209,7 @@ private fun InventoryItem( fun HomeBodyPreview() { InventoryTheme { HomeBody(listOf( - Task(1, "Task1", "High"), Task(2, "Task2", "High"), Task(3, "Task3", "Low") + Task(1, "Task1", "High"), Task(2, "Task2", "Medium"), Task(3, "Task3", "Low") ), onTaskClick = {}) } } @@ -216,7 +227,7 @@ fun HomeBodyEmptyListPreview() { fun InventoryItemPreview() { InventoryTheme { InventoryItem( - Task(1, "Task1", "High"), + Task(1, "Task1", "Medium"), ) } } From 9bbd6ec717258662028f349a2e0f0f796e976baf Mon Sep 17 00:00:00 2001 From: Max7414 <90671891+Max7414@users.noreply.github.com> Date: Mon, 12 May 2025 07:22:03 +0800 Subject: [PATCH 20/32] sell button remove --- .../com/example/inventory/ui/task/TaskDetailsScreen.kt | 8 -------- 1 file changed, 8 deletions(-) diff --git a/app/src/main/java/com/example/inventory/ui/task/TaskDetailsScreen.kt b/app/src/main/java/com/example/inventory/ui/task/TaskDetailsScreen.kt index 80be853a..4997c779 100644 --- a/app/src/main/java/com/example/inventory/ui/task/TaskDetailsScreen.kt +++ b/app/src/main/java/com/example/inventory/ui/task/TaskDetailsScreen.kt @@ -148,14 +148,6 @@ private fun TaskDetailsBody( TaskDetails( task = taskDetailsUiState.taskDetails.toTask(), modifier = Modifier.fillMaxWidth() ) - Button( - onClick = onSellTask, - modifier = Modifier.fillMaxWidth(), - shape = MaterialTheme.shapes.small, - //enabled = !taskDetailsUiState.outOfStock - ) { - Text(stringResource(R.string.sell)) - } OutlinedButton( onClick = { deleteConfirmationRequired = true }, shape = MaterialTheme.shapes.small, From 73bbecf6b76103b95123a0588b82328cf4f9ae46 Mon Sep 17 00:00:00 2001 From: Max7414 <90671891+Max7414@users.noreply.github.com> Date: Mon, 12 May 2025 08:11:20 +0800 Subject: [PATCH 21/32] Radio Button done --- .../inventory/ui/task/TaskEntryScreen.kt | 64 +++++++++++++------ 1 file changed, 45 insertions(+), 19 deletions(-) diff --git a/app/src/main/java/com/example/inventory/ui/task/TaskEntryScreen.kt b/app/src/main/java/com/example/inventory/ui/task/TaskEntryScreen.kt index d8ba5199..619295b9 100644 --- a/app/src/main/java/com/example/inventory/ui/task/TaskEntryScreen.kt +++ b/app/src/main/java/com/example/inventory/ui/task/TaskEntryScreen.kt @@ -18,11 +18,15 @@ package com.example.inventory.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.Spacer import androidx.compose.foundation.layout.calculateEndPadding import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.selection.selectable import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Button @@ -30,14 +34,18 @@ import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextFieldDefaults +import androidx.compose.material3.RadioButton import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.Role import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.tooling.preview.Preview import androidx.lifecycle.viewmodel.compose.viewModel @@ -129,7 +137,10 @@ fun ItemInputForm( modifier: Modifier = Modifier, onValueChange: (TaskDetails) -> Unit = {}, enabled: Boolean = true + ) { + val priorities = listOf("High", "Medium", "Low") + Column( modifier = modifier, verticalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.padding_medium)) @@ -147,26 +158,41 @@ fun ItemInputForm( enabled = enabled, singleLine = true ) + Row( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = dimensionResource(id = R.dimen.padding_small)), + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically + ) { + priorities.forEach { level -> + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .weight(1f) + .selectable( + selected = (taskDetails.priority == level), + onClick = { onValueChange(taskDetails.copy(priority = level)) }, + role = Role.RadioButton + ) + ) { + RadioButton( + selected = (taskDetails.priority == level), + onClick = null, // 整個 Row 都可點 + enabled = enabled + ) + Spacer(modifier = Modifier.width(dimensionResource(id = R.dimen.padding_small))) + Text(level, + color = if (level.lowercase() == "high") + MaterialTheme.colorScheme.error //High Red + else if (level.lowercase() == "medium") + Color(0xFFFBC02D) //High Yellow + else + Color(0xFF388E3C) //High Green - OutlinedTextField( - value = taskDetails.priority, - onValueChange = { onValueChange(taskDetails.copy(priority = it)) }, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), - label = { Text(stringResource(R.string.priority_level)) }, - colors = OutlinedTextFieldDefaults.colors( - focusedContainerColor = MaterialTheme.colorScheme.secondaryContainer, - unfocusedContainerColor = MaterialTheme.colorScheme.secondaryContainer, - disabledContainerColor = MaterialTheme.colorScheme.secondaryContainer, - ), - modifier = Modifier.fillMaxWidth(), - enabled = enabled, - singleLine = true - ) - if (enabled) { - Text( - text = stringResource(R.string.required_fields), - modifier = Modifier.padding(start = dimensionResource(id = R.dimen.padding_medium)) - ) + ) + } + } } } } From 63d11282ea9309d0f7e494478ba957187457d861 Mon Sep 17 00:00:00 2001 From: Max7414 <90671891+Max7414@users.noreply.github.com> Date: Tue, 13 May 2025 00:49:42 +0800 Subject: [PATCH 22/32] add edit button --- .../inventory/ui/task/TaskDetailsScreen.kt | 18 +++++++++++++++++- app/src/main/res/values/strings.xml | 1 + 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/example/inventory/ui/task/TaskDetailsScreen.kt b/app/src/main/java/com/example/inventory/ui/task/TaskDetailsScreen.kt index 4997c779..7ff78881 100644 --- a/app/src/main/java/com/example/inventory/ui/task/TaskDetailsScreen.kt +++ b/app/src/main/java/com/example/inventory/ui/task/TaskDetailsScreen.kt @@ -34,6 +34,7 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Edit import androidx.compose.material3.AlertDialog import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.ExperimentalMaterial3Api @@ -52,6 +53,7 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.stringResource @@ -148,10 +150,24 @@ private fun TaskDetailsBody( TaskDetails( task = taskDetailsUiState.taskDetails.toTask(), modifier = Modifier.fillMaxWidth() ) + OutlinedButton( - onClick = { deleteConfirmationRequired = true }, + onClick = { },//TODO shape = MaterialTheme.shapes.small, modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(R.string.edit)) + } + + + OutlinedButton( + onClick = { deleteConfirmationRequired = true }, + shape = MaterialTheme.shapes.small, + modifier = Modifier.fillMaxWidth(), + colors = ButtonDefaults.outlinedButtonColors( + containerColor = MaterialTheme.colorScheme.primaryContainer // 你可以換成其他顏色,例如 MaterialTheme.colorScheme.primary + ) + ) { Text(stringResource(R.string.delete)) } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 30c3f049..d271bdb2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -18,6 +18,7 @@ Attention Back Delete + Edit Are you sure you want to delete? Oops!\nNo tasks in the lists.\nTap + to add. Edit Task From 981f79639e2fa3ba3243b5badcbad746548c1539 Mon Sep 17 00:00:00 2001 From: Max7414 Date: Tue, 13 May 2025 08:49:50 +0800 Subject: [PATCH 23/32] =?UTF-8?q?=E5=A2=9E=E5=8A=A0disabled=E7=9A=84?= =?UTF-8?q?=E8=BC=B8=E5=85=A5=E7=AD=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inventory/ui/task/TaskDetailsScreen.kt | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/app/src/main/java/com/example/inventory/ui/task/TaskDetailsScreen.kt b/app/src/main/java/com/example/inventory/ui/task/TaskDetailsScreen.kt index 7ff78881..c395a860 100644 --- a/app/src/main/java/com/example/inventory/ui/task/TaskDetailsScreen.kt +++ b/app/src/main/java/com/example/inventory/ui/task/TaskDetailsScreen.kt @@ -28,7 +28,9 @@ import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.safeDrawing +import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.selection.selectable import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Edit @@ -42,6 +44,9 @@ import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.OutlinedTextFieldDefaults +import androidx.compose.material3.RadioButton import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TextButton @@ -52,13 +57,16 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.Role import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel import com.example.inventory.InventoryTopAppBar import com.example.inventory.R @@ -147,6 +155,9 @@ private fun TaskDetailsBody( verticalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.padding_medium)) ) { var deleteConfirmationRequired by rememberSaveable { mutableStateOf(false) } + var inputText by rememberSaveable { mutableStateOf(taskDetailsUiState.taskDetails.name) } + var editable by rememberSaveable { mutableStateOf(false) } // 編輯開關 + var selectedPriority by rememberSaveable { mutableStateOf(taskDetailsUiState.taskDetails.priority) } TaskDetails( task = taskDetailsUiState.taskDetails.toTask(), modifier = Modifier.fillMaxWidth() ) @@ -171,6 +182,51 @@ private fun TaskDetailsBody( ) { Text(stringResource(R.string.delete)) } + + OutlinedTextField( + value = inputText, + onValueChange = { inputText = it }, + label = { Text("輸入任務名稱") }, + colors = OutlinedTextFieldDefaults.colors( + focusedContainerColor = MaterialTheme.colorScheme.secondaryContainer, + unfocusedContainerColor = MaterialTheme.colorScheme.secondaryContainer, + disabledContainerColor = MaterialTheme.colorScheme.secondaryContainer, + ), + modifier = Modifier + .fillMaxWidth() + .padding(top = 16.dp), + enabled = editable + ) + val priorities = listOf("High", "Medium", "Low") + Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { + priorities.forEach { level -> + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .weight(1f) + .selectable( + selected = (selectedPriority == level), + onClick = { if (editable) selectedPriority = level }, + role = Role.RadioButton + ) + ) { + RadioButton( + selected = (selectedPriority == level), + onClick = null, // 整個 Row 都可點 + enabled = editable + ) + Spacer(modifier = Modifier.width(dimensionResource(id = R.dimen.padding_small))) + Text( + level, + color = when (level.lowercase()) { + "high" -> MaterialTheme.colorScheme.error // High:紅 + "medium" -> Color(0xFFFBC02D) // Medium:黃 + else -> Color(0xFF388E3C) // Low:綠 + } + ) + } + } + } if (deleteConfirmationRequired) { DeleteConfirmationDialog( onDeleteConfirm = { From efc3efcac4011e12e082a061b15dd53a872012ba Mon Sep 17 00:00:00 2001 From: Max7414 Date: Tue, 13 May 2025 08:51:04 +0800 Subject: [PATCH 24/32] =?UTF-8?q?=E5=88=AA=E9=99=A4=E6=8C=89=E9=88=95?= =?UTF-8?q?=E4=BD=8D=E7=BD=AE=E8=AA=BF=E6=8F=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inventory/ui/task/TaskDetailsScreen.kt | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/example/inventory/ui/task/TaskDetailsScreen.kt b/app/src/main/java/com/example/inventory/ui/task/TaskDetailsScreen.kt index c395a860..d734ed08 100644 --- a/app/src/main/java/com/example/inventory/ui/task/TaskDetailsScreen.kt +++ b/app/src/main/java/com/example/inventory/ui/task/TaskDetailsScreen.kt @@ -161,16 +161,6 @@ private fun TaskDetailsBody( TaskDetails( task = taskDetailsUiState.taskDetails.toTask(), modifier = Modifier.fillMaxWidth() ) - - OutlinedButton( - onClick = { },//TODO - shape = MaterialTheme.shapes.small, - modifier = Modifier.fillMaxWidth() - ) { - Text(stringResource(R.string.edit)) - } - - OutlinedButton( onClick = { deleteConfirmationRequired = true }, shape = MaterialTheme.shapes.small, @@ -183,6 +173,17 @@ private fun TaskDetailsBody( Text(stringResource(R.string.delete)) } + + OutlinedButton( + onClick = { },//TODO + shape = MaterialTheme.shapes.small, + modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(R.string.edit)) + } + + + OutlinedTextField( value = inputText, onValueChange = { inputText = it }, From c54f4439e8c4cba69d89d223ee6d1008d4796de9 Mon Sep 17 00:00:00 2001 From: Max7414 Date: Tue, 13 May 2025 09:29:41 +0800 Subject: [PATCH 25/32] =?UTF-8?q?=E5=AE=8C=E6=88=90=E7=B7=A8=E8=BC=AF?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inventory/ui/task/TaskDetailsScreen.kt | 198 +++++++++--------- .../inventory/ui/task/TaskDetailsViewModel.kt | 60 +++--- app/src/main/res/values/strings.xml | 2 + 3 files changed, 124 insertions(+), 136 deletions(-) diff --git a/app/src/main/java/com/example/inventory/ui/task/TaskDetailsScreen.kt b/app/src/main/java/com/example/inventory/ui/task/TaskDetailsScreen.kt index d734ed08..db6faffc 100644 --- a/app/src/main/java/com/example/inventory/ui/task/TaskDetailsScreen.kt +++ b/app/src/main/java/com/example/inventory/ui/task/TaskDetailsScreen.kt @@ -1,19 +1,4 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - +// TaskDetailsScreen.kt package com.example.inventory.ui.task import androidx.annotation.StringRes @@ -35,7 +20,6 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Edit import androidx.compose.material3.AlertDialog -import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults @@ -91,8 +75,9 @@ fun TaskDetailsScreen( modifier: Modifier = Modifier, viewModel: TaskDetailsViewModel = viewModel(factory = AppViewModelProvider.Factory) ) { - val uiState = viewModel.uiState.collectAsState() + val uiState by viewModel.uiState.collectAsState() val coroutineScope = rememberCoroutineScope() + Scaffold( topBar = { InventoryTopAppBar( @@ -103,7 +88,7 @@ fun TaskDetailsScreen( }, floatingActionButton = { FloatingActionButton( - onClick = { navigateToEditTask(uiState.value.taskDetails.id) }, + onClick = { navigateToEditTask(uiState.taskDetails.id) }, shape = MaterialTheme.shapes.medium, modifier = Modifier .padding( @@ -120,18 +105,19 @@ fun TaskDetailsScreen( modifier = modifier, ) { innerPadding -> TaskDetailsBody( - taskDetailsUiState = uiState.value, - onSellTask = { viewModel.reduceQuantityByOne() }, + taskDetailsUiState = uiState, onDelete = { - // Note: If the user rotates the screen very fast, the operation may get cancelled - // and the task may not be deleted from the Database. This is because when config - // change occurs, the Activity will be recreated and the rememberCoroutineScope will - // be cancelled - since the scope is bound to composition. coroutineScope.launch { viewModel.deleteTask() navigateBack() } }, + onConfirm = { newName, newPriority -> + viewModel.updateTask( + name = newName, + priority = newPriority + ) + }, modifier = Modifier .padding( start = innerPadding.calculateStartPadding(LocalLayoutDirection.current), @@ -146,60 +132,87 @@ fun TaskDetailsScreen( @Composable private fun TaskDetailsBody( taskDetailsUiState: TaskDetailsUiState, - onSellTask: () -> Unit, onDelete: () -> Unit, + onConfirm: (String, String) -> Unit, modifier: Modifier = Modifier ) { Column( modifier = modifier.padding(dimensionResource(id = R.dimen.padding_medium)), verticalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.padding_medium)) ) { + // 狀態 var deleteConfirmationRequired by rememberSaveable { mutableStateOf(false) } + var editable by rememberSaveable { mutableStateOf(false) } var inputText by rememberSaveable { mutableStateOf(taskDetailsUiState.taskDetails.name) } - var editable by rememberSaveable { mutableStateOf(false) } // 編輯開關 var selectedPriority by rememberSaveable { mutableStateOf(taskDetailsUiState.taskDetails.priority) } + + // 原始卡片顯示 TaskDetails( - task = taskDetailsUiState.taskDetails.toTask(), modifier = Modifier.fillMaxWidth() + task = taskDetailsUiState.taskDetails.toTask(), + modifier = Modifier.fillMaxWidth() ) + + // 刪除按鈕 OutlinedButton( onClick = { deleteConfirmationRequired = true }, shape = MaterialTheme.shapes.small, modifier = Modifier.fillMaxWidth(), colors = ButtonDefaults.outlinedButtonColors( - containerColor = MaterialTheme.colorScheme.primaryContainer // 你可以換成其他顏色,例如 MaterialTheme.colorScheme.primary + containerColor = MaterialTheme.colorScheme.primaryContainer ) - ) { Text(stringResource(R.string.delete)) } - - OutlinedButton( - onClick = { },//TODO - shape = MaterialTheme.shapes.small, + // Edit / Cancel & Confirm + Row( + horizontalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.fillMaxWidth() ) { - Text(stringResource(R.string.edit)) + if (!editable) { + OutlinedButton(onClick = { editable = true }, modifier = Modifier.weight(1f)) { + Text(stringResource(R.string.edit)) + } + } else { + OutlinedButton(onClick = { + // rollback + editable = false + inputText = taskDetailsUiState.taskDetails.name + selectedPriority = taskDetailsUiState.taskDetails.priority + }, modifier = Modifier.weight(1f)) { + Text(stringResource(R.string.cancel)) + } + OutlinedButton(onClick = { + editable = false + onConfirm(inputText, selectedPriority) + }, modifier = Modifier.weight(1f)) { + Text(stringResource(R.string.confirm)) + } + } } - - + // 編輯輸入框 OutlinedTextField( value = inputText, onValueChange = { inputText = it }, - label = { Text("輸入任務名稱") }, + label = { Text(stringResource(R.string.task)) }, colors = OutlinedTextFieldDefaults.colors( focusedContainerColor = MaterialTheme.colorScheme.secondaryContainer, unfocusedContainerColor = MaterialTheme.colorScheme.secondaryContainer, - disabledContainerColor = MaterialTheme.colorScheme.secondaryContainer, + disabledContainerColor = MaterialTheme.colorScheme.secondaryContainer ), modifier = Modifier .fillMaxWidth() .padding(top = 16.dp), enabled = editable ) + + // Priority RadioGroup val priorities = listOf("High", "Medium", "Low") - Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { priorities.forEach { level -> Row( verticalAlignment = Alignment.CenterVertically, @@ -213,41 +226,55 @@ private fun TaskDetailsBody( ) { RadioButton( selected = (selectedPriority == level), - onClick = null, // 整個 Row 都可點 + onClick = null, enabled = editable ) Spacer(modifier = Modifier.width(dimensionResource(id = R.dimen.padding_small))) Text( level, color = when (level.lowercase()) { - "high" -> MaterialTheme.colorScheme.error // High:紅 - "medium" -> Color(0xFFFBC02D) // Medium:黃 - else -> Color(0xFF388E3C) // Low:綠 - } + "high" -> MaterialTheme.colorScheme.error + "medium" -> Color(0xFFFBC02D) + else -> Color(0xFF388E3C) + }, + fontWeight = FontWeight.Bold ) } } } + + // 刪除確認對話框 if (deleteConfirmationRequired) { - DeleteConfirmationDialog( - onDeleteConfirm = { - deleteConfirmationRequired = false - onDelete() + AlertDialog( + onDismissRequest = { /* no-op */ }, + title = { Text(stringResource(R.string.attention)) }, + text = { Text(stringResource(R.string.delete_question)) }, + confirmButton = { + TextButton(onClick = { + deleteConfirmationRequired = false + onDelete() + }) { + Text(stringResource(R.string.yes)) + } }, - onDeleteCancel = { deleteConfirmationRequired = false }, - modifier = Modifier.padding(dimensionResource(id = R.dimen.padding_medium)) + dismissButton = { + TextButton(onClick = { deleteConfirmationRequired = false }) { + Text(stringResource(R.string.no)) + } + } ) } } } - @Composable fun TaskDetails( - task: Task, modifier: Modifier = Modifier + task: Task, + modifier: Modifier = Modifier ) { Card( - modifier = modifier, colors = CardDefaults.cardColors( + modifier = modifier, + colors = CardDefaults.cardColors( containerColor = MaterialTheme.colorScheme.primaryContainer, contentColor = MaterialTheme.colorScheme.onPrimaryContainer ) @@ -258,69 +285,36 @@ fun TaskDetails( .padding(dimensionResource(id = R.dimen.padding_medium)), verticalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.padding_medium)) ) { - TaskDetailsRow( - labelResID = R.string.task, - taskDetail = task.name, - modifier = Modifier.padding( - horizontal = dimensionResource( - id = R.dimen - .padding_medium - ) - ) - ) - TaskDetailsRow( - labelResID = R.string.priority_level_show, - taskDetail = task.priority, - modifier = Modifier.padding( - horizontal = dimensionResource( - id = R.dimen - .padding_medium - ) - ) - ) - + TaskDetailsRow(R.string.task, task.name) + TaskDetailsRow(R.string.priority_level_show, task.priority) } - } } @Composable private fun TaskDetailsRow( - @StringRes labelResID: Int, taskDetail: String, modifier: Modifier = Modifier + @StringRes labelResID: Int, + taskDetail: String ) { - Row(modifier = modifier) { + Row( + modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.padding_medium)) + ) { Text(text = stringResource(labelResID)) Spacer(modifier = Modifier.weight(1f)) Text(text = taskDetail, fontWeight = FontWeight.Bold) } } -@Composable -private fun DeleteConfirmationDialog( - onDeleteConfirm: () -> Unit, onDeleteCancel: () -> Unit, modifier: Modifier = Modifier -) { - AlertDialog(onDismissRequest = { /* Do nothing */ }, - title = { Text(stringResource(R.string.attention)) }, - text = { Text(stringResource(R.string.delete_question)) }, - modifier = modifier, - dismissButton = { - TextButton(onClick = onDeleteCancel) { - Text(text = stringResource(R.string.no)) - } - }, - confirmButton = { - TextButton(onClick = onDeleteConfirm) { - Text(text = stringResource(R.string.yes)) - } - }) -} - @Preview(showBackground = true) @Composable fun TaskDetailsScreenPreview() { InventoryTheme { - TaskDetailsBody(TaskDetailsUiState( - taskDetails = TaskDetails(1, "Task1", "High") - ), onSellTask = {}, onDelete = {}) + TaskDetailsBody( + taskDetailsUiState = TaskDetailsUiState( + taskDetails = TaskDetails(1, "示範任務", "Medium") + ), + onDelete = { /* no-op */ }, + onConfirm = { _, _ -> /* no-op */ } + ) } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/example/inventory/ui/task/TaskDetailsViewModel.kt b/app/src/main/java/com/example/inventory/ui/task/TaskDetailsViewModel.kt index 575b2ace..125c8025 100644 --- a/app/src/main/java/com/example/inventory/ui/task/TaskDetailsViewModel.kt +++ b/app/src/main/java/com/example/inventory/ui/task/TaskDetailsViewModel.kt @@ -1,19 +1,3 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - package com.example.inventory.ui.task import androidx.lifecycle.SavedStateHandle @@ -28,44 +12,52 @@ import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch /** - * ViewModel to retrieve, update and delete an item from the [TasksRepository]'s data source. + * ViewModel to retrieve, update and delete a task from the [TasksRepository]'s data source. */ class TaskDetailsViewModel( savedStateHandle: SavedStateHandle, - private val tasksRepository: TasksRepository, + private val tasksRepository: TasksRepository ) : ViewModel() { - private val itemId: Int = checkNotNull(savedStateHandle[TaskDetailsDestination.taskIdArg]) + // 從 navigation arguments 拿到要顯示的 taskId + private val taskId: Int = checkNotNull(savedStateHandle[TaskDetailsDestination.taskIdArg]) /** - * Holds the item details ui state. The data is retrieved from [TasksRepository] and mapped to - * the UI state. + * uiState 會隨著資料庫中該筆 task 更新自動推送最新值 */ val uiState: StateFlow = - tasksRepository.getTaskStream(itemId) - .filterNotNull() - .map { - TaskDetailsUiState(taskDetails = it.toItemDetails()) - }.stateIn( + tasksRepository + .getTaskStream(taskId) // 回傳 Flow + .filterNotNull() // 避免 null + .map { entity -> + // Map Entity -> UI state + TaskDetailsUiState(taskDetails = entity.toItemDetails()) + } + .stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(TIMEOUT_MILLIS), initialValue = TaskDetailsUiState() ) /** - * Reduces the item quantity by one and update the [TasksRepository]'s data source. + * 刪除該 task */ - fun reduceQuantityByOne() { + fun deleteTask() { viewModelScope.launch { - val currentItem = uiState.value.taskDetails.toTask() + tasksRepository.deleteTask(uiState.value.taskDetails.toTask()) } } /** - * Deletes the item from the [TasksRepository]'s data source. + * 更新該 task 的 name 與 priority */ - suspend fun deleteTask() { - tasksRepository.deleteTask(uiState.value.taskDetails.toTask()) + fun updateTask(name: String, priority: String) { + viewModelScope.launch { + // 構造一個新的 TaskData 並交給 repository 更新 + val updated = uiState.value.taskDetails.copy(name = name, priority = priority) + tasksRepository.updateTask(updated.toTask()) + // 不需要手動刷新,getTaskStream 會自動 emit 最新的資料 + } } companion object { @@ -74,8 +66,8 @@ class TaskDetailsViewModel( } /** - * UI state for ItemDetailsScreen + * UI state for TaskDetailsScreen */ data class TaskDetailsUiState( val taskDetails: TaskDetails = TaskDetails() -) +) \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d271bdb2..5eeb8d99 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -34,4 +34,6 @@ Sell Yes *required fields + Cancel + confirm From 991be78c42ca91f3318dd6bcc4fd8c332f086be9 Mon Sep 17 00:00:00 2001 From: Max7414 <90671891+Max7414@users.noreply.github.com> Date: Tue, 13 May 2025 00:49:42 +0800 Subject: [PATCH 26/32] add edit button --- .../inventory/ui/task/TaskDetailsScreen.kt | 18 +++++++++++++++++- app/src/main/res/values/strings.xml | 1 + 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/example/inventory/ui/task/TaskDetailsScreen.kt b/app/src/main/java/com/example/inventory/ui/task/TaskDetailsScreen.kt index 4997c779..7ff78881 100644 --- a/app/src/main/java/com/example/inventory/ui/task/TaskDetailsScreen.kt +++ b/app/src/main/java/com/example/inventory/ui/task/TaskDetailsScreen.kt @@ -34,6 +34,7 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Edit import androidx.compose.material3.AlertDialog import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.ExperimentalMaterial3Api @@ -52,6 +53,7 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.stringResource @@ -148,10 +150,24 @@ private fun TaskDetailsBody( TaskDetails( task = taskDetailsUiState.taskDetails.toTask(), modifier = Modifier.fillMaxWidth() ) + OutlinedButton( - onClick = { deleteConfirmationRequired = true }, + onClick = { },//TODO shape = MaterialTheme.shapes.small, modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(R.string.edit)) + } + + + OutlinedButton( + onClick = { deleteConfirmationRequired = true }, + shape = MaterialTheme.shapes.small, + modifier = Modifier.fillMaxWidth(), + colors = ButtonDefaults.outlinedButtonColors( + containerColor = MaterialTheme.colorScheme.primaryContainer // 你可以換成其他顏色,例如 MaterialTheme.colorScheme.primary + ) + ) { Text(stringResource(R.string.delete)) } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 30c3f049..d271bdb2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -18,6 +18,7 @@ Attention Back Delete + Edit Are you sure you want to delete? Oops!\nNo tasks in the lists.\nTap + to add. Edit Task From 7233e9f48eeb34eeeb78eebf638ae9fa4d7bd8b2 Mon Sep 17 00:00:00 2001 From: Max7414 Date: Tue, 13 May 2025 08:49:50 +0800 Subject: [PATCH 27/32] =?UTF-8?q?=E5=A2=9E=E5=8A=A0disabled=E7=9A=84?= =?UTF-8?q?=E8=BC=B8=E5=85=A5=E7=AD=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inventory/ui/task/TaskDetailsScreen.kt | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/app/src/main/java/com/example/inventory/ui/task/TaskDetailsScreen.kt b/app/src/main/java/com/example/inventory/ui/task/TaskDetailsScreen.kt index 7ff78881..c395a860 100644 --- a/app/src/main/java/com/example/inventory/ui/task/TaskDetailsScreen.kt +++ b/app/src/main/java/com/example/inventory/ui/task/TaskDetailsScreen.kt @@ -28,7 +28,9 @@ import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.safeDrawing +import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.selection.selectable import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Edit @@ -42,6 +44,9 @@ import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.OutlinedTextFieldDefaults +import androidx.compose.material3.RadioButton import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TextButton @@ -52,13 +57,16 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.Role import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel import com.example.inventory.InventoryTopAppBar import com.example.inventory.R @@ -147,6 +155,9 @@ private fun TaskDetailsBody( verticalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.padding_medium)) ) { var deleteConfirmationRequired by rememberSaveable { mutableStateOf(false) } + var inputText by rememberSaveable { mutableStateOf(taskDetailsUiState.taskDetails.name) } + var editable by rememberSaveable { mutableStateOf(false) } // 編輯開關 + var selectedPriority by rememberSaveable { mutableStateOf(taskDetailsUiState.taskDetails.priority) } TaskDetails( task = taskDetailsUiState.taskDetails.toTask(), modifier = Modifier.fillMaxWidth() ) @@ -171,6 +182,51 @@ private fun TaskDetailsBody( ) { Text(stringResource(R.string.delete)) } + + OutlinedTextField( + value = inputText, + onValueChange = { inputText = it }, + label = { Text("輸入任務名稱") }, + colors = OutlinedTextFieldDefaults.colors( + focusedContainerColor = MaterialTheme.colorScheme.secondaryContainer, + unfocusedContainerColor = MaterialTheme.colorScheme.secondaryContainer, + disabledContainerColor = MaterialTheme.colorScheme.secondaryContainer, + ), + modifier = Modifier + .fillMaxWidth() + .padding(top = 16.dp), + enabled = editable + ) + val priorities = listOf("High", "Medium", "Low") + Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { + priorities.forEach { level -> + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .weight(1f) + .selectable( + selected = (selectedPriority == level), + onClick = { if (editable) selectedPriority = level }, + role = Role.RadioButton + ) + ) { + RadioButton( + selected = (selectedPriority == level), + onClick = null, // 整個 Row 都可點 + enabled = editable + ) + Spacer(modifier = Modifier.width(dimensionResource(id = R.dimen.padding_small))) + Text( + level, + color = when (level.lowercase()) { + "high" -> MaterialTheme.colorScheme.error // High:紅 + "medium" -> Color(0xFFFBC02D) // Medium:黃 + else -> Color(0xFF388E3C) // Low:綠 + } + ) + } + } + } if (deleteConfirmationRequired) { DeleteConfirmationDialog( onDeleteConfirm = { From adee19680f75decbb42c3604b7a48ad954699617 Mon Sep 17 00:00:00 2001 From: Max7414 Date: Tue, 13 May 2025 08:51:04 +0800 Subject: [PATCH 28/32] =?UTF-8?q?=E5=88=AA=E9=99=A4=E6=8C=89=E9=88=95?= =?UTF-8?q?=E4=BD=8D=E7=BD=AE=E8=AA=BF=E6=8F=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inventory/ui/task/TaskDetailsScreen.kt | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/example/inventory/ui/task/TaskDetailsScreen.kt b/app/src/main/java/com/example/inventory/ui/task/TaskDetailsScreen.kt index c395a860..d734ed08 100644 --- a/app/src/main/java/com/example/inventory/ui/task/TaskDetailsScreen.kt +++ b/app/src/main/java/com/example/inventory/ui/task/TaskDetailsScreen.kt @@ -161,16 +161,6 @@ private fun TaskDetailsBody( TaskDetails( task = taskDetailsUiState.taskDetails.toTask(), modifier = Modifier.fillMaxWidth() ) - - OutlinedButton( - onClick = { },//TODO - shape = MaterialTheme.shapes.small, - modifier = Modifier.fillMaxWidth() - ) { - Text(stringResource(R.string.edit)) - } - - OutlinedButton( onClick = { deleteConfirmationRequired = true }, shape = MaterialTheme.shapes.small, @@ -183,6 +173,17 @@ private fun TaskDetailsBody( Text(stringResource(R.string.delete)) } + + OutlinedButton( + onClick = { },//TODO + shape = MaterialTheme.shapes.small, + modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(R.string.edit)) + } + + + OutlinedTextField( value = inputText, onValueChange = { inputText = it }, From d9b0ded39dae447a8d191fcc3fc3d90fae7672e4 Mon Sep 17 00:00:00 2001 From: Max7414 Date: Tue, 13 May 2025 09:29:41 +0800 Subject: [PATCH 29/32] =?UTF-8?q?=E5=AE=8C=E6=88=90=E7=B7=A8=E8=BC=AF?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inventory/ui/task/TaskDetailsScreen.kt | 198 +++++++++--------- .../inventory/ui/task/TaskDetailsViewModel.kt | 60 +++--- app/src/main/res/values/strings.xml | 2 + 3 files changed, 124 insertions(+), 136 deletions(-) diff --git a/app/src/main/java/com/example/inventory/ui/task/TaskDetailsScreen.kt b/app/src/main/java/com/example/inventory/ui/task/TaskDetailsScreen.kt index d734ed08..db6faffc 100644 --- a/app/src/main/java/com/example/inventory/ui/task/TaskDetailsScreen.kt +++ b/app/src/main/java/com/example/inventory/ui/task/TaskDetailsScreen.kt @@ -1,19 +1,4 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - +// TaskDetailsScreen.kt package com.example.inventory.ui.task import androidx.annotation.StringRes @@ -35,7 +20,6 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Edit import androidx.compose.material3.AlertDialog -import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults @@ -91,8 +75,9 @@ fun TaskDetailsScreen( modifier: Modifier = Modifier, viewModel: TaskDetailsViewModel = viewModel(factory = AppViewModelProvider.Factory) ) { - val uiState = viewModel.uiState.collectAsState() + val uiState by viewModel.uiState.collectAsState() val coroutineScope = rememberCoroutineScope() + Scaffold( topBar = { InventoryTopAppBar( @@ -103,7 +88,7 @@ fun TaskDetailsScreen( }, floatingActionButton = { FloatingActionButton( - onClick = { navigateToEditTask(uiState.value.taskDetails.id) }, + onClick = { navigateToEditTask(uiState.taskDetails.id) }, shape = MaterialTheme.shapes.medium, modifier = Modifier .padding( @@ -120,18 +105,19 @@ fun TaskDetailsScreen( modifier = modifier, ) { innerPadding -> TaskDetailsBody( - taskDetailsUiState = uiState.value, - onSellTask = { viewModel.reduceQuantityByOne() }, + taskDetailsUiState = uiState, onDelete = { - // Note: If the user rotates the screen very fast, the operation may get cancelled - // and the task may not be deleted from the Database. This is because when config - // change occurs, the Activity will be recreated and the rememberCoroutineScope will - // be cancelled - since the scope is bound to composition. coroutineScope.launch { viewModel.deleteTask() navigateBack() } }, + onConfirm = { newName, newPriority -> + viewModel.updateTask( + name = newName, + priority = newPriority + ) + }, modifier = Modifier .padding( start = innerPadding.calculateStartPadding(LocalLayoutDirection.current), @@ -146,60 +132,87 @@ fun TaskDetailsScreen( @Composable private fun TaskDetailsBody( taskDetailsUiState: TaskDetailsUiState, - onSellTask: () -> Unit, onDelete: () -> Unit, + onConfirm: (String, String) -> Unit, modifier: Modifier = Modifier ) { Column( modifier = modifier.padding(dimensionResource(id = R.dimen.padding_medium)), verticalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.padding_medium)) ) { + // 狀態 var deleteConfirmationRequired by rememberSaveable { mutableStateOf(false) } + var editable by rememberSaveable { mutableStateOf(false) } var inputText by rememberSaveable { mutableStateOf(taskDetailsUiState.taskDetails.name) } - var editable by rememberSaveable { mutableStateOf(false) } // 編輯開關 var selectedPriority by rememberSaveable { mutableStateOf(taskDetailsUiState.taskDetails.priority) } + + // 原始卡片顯示 TaskDetails( - task = taskDetailsUiState.taskDetails.toTask(), modifier = Modifier.fillMaxWidth() + task = taskDetailsUiState.taskDetails.toTask(), + modifier = Modifier.fillMaxWidth() ) + + // 刪除按鈕 OutlinedButton( onClick = { deleteConfirmationRequired = true }, shape = MaterialTheme.shapes.small, modifier = Modifier.fillMaxWidth(), colors = ButtonDefaults.outlinedButtonColors( - containerColor = MaterialTheme.colorScheme.primaryContainer // 你可以換成其他顏色,例如 MaterialTheme.colorScheme.primary + containerColor = MaterialTheme.colorScheme.primaryContainer ) - ) { Text(stringResource(R.string.delete)) } - - OutlinedButton( - onClick = { },//TODO - shape = MaterialTheme.shapes.small, + // Edit / Cancel & Confirm + Row( + horizontalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.fillMaxWidth() ) { - Text(stringResource(R.string.edit)) + if (!editable) { + OutlinedButton(onClick = { editable = true }, modifier = Modifier.weight(1f)) { + Text(stringResource(R.string.edit)) + } + } else { + OutlinedButton(onClick = { + // rollback + editable = false + inputText = taskDetailsUiState.taskDetails.name + selectedPriority = taskDetailsUiState.taskDetails.priority + }, modifier = Modifier.weight(1f)) { + Text(stringResource(R.string.cancel)) + } + OutlinedButton(onClick = { + editable = false + onConfirm(inputText, selectedPriority) + }, modifier = Modifier.weight(1f)) { + Text(stringResource(R.string.confirm)) + } + } } - - + // 編輯輸入框 OutlinedTextField( value = inputText, onValueChange = { inputText = it }, - label = { Text("輸入任務名稱") }, + label = { Text(stringResource(R.string.task)) }, colors = OutlinedTextFieldDefaults.colors( focusedContainerColor = MaterialTheme.colorScheme.secondaryContainer, unfocusedContainerColor = MaterialTheme.colorScheme.secondaryContainer, - disabledContainerColor = MaterialTheme.colorScheme.secondaryContainer, + disabledContainerColor = MaterialTheme.colorScheme.secondaryContainer ), modifier = Modifier .fillMaxWidth() .padding(top = 16.dp), enabled = editable ) + + // Priority RadioGroup val priorities = listOf("High", "Medium", "Low") - Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { priorities.forEach { level -> Row( verticalAlignment = Alignment.CenterVertically, @@ -213,41 +226,55 @@ private fun TaskDetailsBody( ) { RadioButton( selected = (selectedPriority == level), - onClick = null, // 整個 Row 都可點 + onClick = null, enabled = editable ) Spacer(modifier = Modifier.width(dimensionResource(id = R.dimen.padding_small))) Text( level, color = when (level.lowercase()) { - "high" -> MaterialTheme.colorScheme.error // High:紅 - "medium" -> Color(0xFFFBC02D) // Medium:黃 - else -> Color(0xFF388E3C) // Low:綠 - } + "high" -> MaterialTheme.colorScheme.error + "medium" -> Color(0xFFFBC02D) + else -> Color(0xFF388E3C) + }, + fontWeight = FontWeight.Bold ) } } } + + // 刪除確認對話框 if (deleteConfirmationRequired) { - DeleteConfirmationDialog( - onDeleteConfirm = { - deleteConfirmationRequired = false - onDelete() + AlertDialog( + onDismissRequest = { /* no-op */ }, + title = { Text(stringResource(R.string.attention)) }, + text = { Text(stringResource(R.string.delete_question)) }, + confirmButton = { + TextButton(onClick = { + deleteConfirmationRequired = false + onDelete() + }) { + Text(stringResource(R.string.yes)) + } }, - onDeleteCancel = { deleteConfirmationRequired = false }, - modifier = Modifier.padding(dimensionResource(id = R.dimen.padding_medium)) + dismissButton = { + TextButton(onClick = { deleteConfirmationRequired = false }) { + Text(stringResource(R.string.no)) + } + } ) } } } - @Composable fun TaskDetails( - task: Task, modifier: Modifier = Modifier + task: Task, + modifier: Modifier = Modifier ) { Card( - modifier = modifier, colors = CardDefaults.cardColors( + modifier = modifier, + colors = CardDefaults.cardColors( containerColor = MaterialTheme.colorScheme.primaryContainer, contentColor = MaterialTheme.colorScheme.onPrimaryContainer ) @@ -258,69 +285,36 @@ fun TaskDetails( .padding(dimensionResource(id = R.dimen.padding_medium)), verticalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.padding_medium)) ) { - TaskDetailsRow( - labelResID = R.string.task, - taskDetail = task.name, - modifier = Modifier.padding( - horizontal = dimensionResource( - id = R.dimen - .padding_medium - ) - ) - ) - TaskDetailsRow( - labelResID = R.string.priority_level_show, - taskDetail = task.priority, - modifier = Modifier.padding( - horizontal = dimensionResource( - id = R.dimen - .padding_medium - ) - ) - ) - + TaskDetailsRow(R.string.task, task.name) + TaskDetailsRow(R.string.priority_level_show, task.priority) } - } } @Composable private fun TaskDetailsRow( - @StringRes labelResID: Int, taskDetail: String, modifier: Modifier = Modifier + @StringRes labelResID: Int, + taskDetail: String ) { - Row(modifier = modifier) { + Row( + modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.padding_medium)) + ) { Text(text = stringResource(labelResID)) Spacer(modifier = Modifier.weight(1f)) Text(text = taskDetail, fontWeight = FontWeight.Bold) } } -@Composable -private fun DeleteConfirmationDialog( - onDeleteConfirm: () -> Unit, onDeleteCancel: () -> Unit, modifier: Modifier = Modifier -) { - AlertDialog(onDismissRequest = { /* Do nothing */ }, - title = { Text(stringResource(R.string.attention)) }, - text = { Text(stringResource(R.string.delete_question)) }, - modifier = modifier, - dismissButton = { - TextButton(onClick = onDeleteCancel) { - Text(text = stringResource(R.string.no)) - } - }, - confirmButton = { - TextButton(onClick = onDeleteConfirm) { - Text(text = stringResource(R.string.yes)) - } - }) -} - @Preview(showBackground = true) @Composable fun TaskDetailsScreenPreview() { InventoryTheme { - TaskDetailsBody(TaskDetailsUiState( - taskDetails = TaskDetails(1, "Task1", "High") - ), onSellTask = {}, onDelete = {}) + TaskDetailsBody( + taskDetailsUiState = TaskDetailsUiState( + taskDetails = TaskDetails(1, "示範任務", "Medium") + ), + onDelete = { /* no-op */ }, + onConfirm = { _, _ -> /* no-op */ } + ) } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/example/inventory/ui/task/TaskDetailsViewModel.kt b/app/src/main/java/com/example/inventory/ui/task/TaskDetailsViewModel.kt index 575b2ace..125c8025 100644 --- a/app/src/main/java/com/example/inventory/ui/task/TaskDetailsViewModel.kt +++ b/app/src/main/java/com/example/inventory/ui/task/TaskDetailsViewModel.kt @@ -1,19 +1,3 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - package com.example.inventory.ui.task import androidx.lifecycle.SavedStateHandle @@ -28,44 +12,52 @@ import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch /** - * ViewModel to retrieve, update and delete an item from the [TasksRepository]'s data source. + * ViewModel to retrieve, update and delete a task from the [TasksRepository]'s data source. */ class TaskDetailsViewModel( savedStateHandle: SavedStateHandle, - private val tasksRepository: TasksRepository, + private val tasksRepository: TasksRepository ) : ViewModel() { - private val itemId: Int = checkNotNull(savedStateHandle[TaskDetailsDestination.taskIdArg]) + // 從 navigation arguments 拿到要顯示的 taskId + private val taskId: Int = checkNotNull(savedStateHandle[TaskDetailsDestination.taskIdArg]) /** - * Holds the item details ui state. The data is retrieved from [TasksRepository] and mapped to - * the UI state. + * uiState 會隨著資料庫中該筆 task 更新自動推送最新值 */ val uiState: StateFlow = - tasksRepository.getTaskStream(itemId) - .filterNotNull() - .map { - TaskDetailsUiState(taskDetails = it.toItemDetails()) - }.stateIn( + tasksRepository + .getTaskStream(taskId) // 回傳 Flow + .filterNotNull() // 避免 null + .map { entity -> + // Map Entity -> UI state + TaskDetailsUiState(taskDetails = entity.toItemDetails()) + } + .stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(TIMEOUT_MILLIS), initialValue = TaskDetailsUiState() ) /** - * Reduces the item quantity by one and update the [TasksRepository]'s data source. + * 刪除該 task */ - fun reduceQuantityByOne() { + fun deleteTask() { viewModelScope.launch { - val currentItem = uiState.value.taskDetails.toTask() + tasksRepository.deleteTask(uiState.value.taskDetails.toTask()) } } /** - * Deletes the item from the [TasksRepository]'s data source. + * 更新該 task 的 name 與 priority */ - suspend fun deleteTask() { - tasksRepository.deleteTask(uiState.value.taskDetails.toTask()) + fun updateTask(name: String, priority: String) { + viewModelScope.launch { + // 構造一個新的 TaskData 並交給 repository 更新 + val updated = uiState.value.taskDetails.copy(name = name, priority = priority) + tasksRepository.updateTask(updated.toTask()) + // 不需要手動刷新,getTaskStream 會自動 emit 最新的資料 + } } companion object { @@ -74,8 +66,8 @@ class TaskDetailsViewModel( } /** - * UI state for ItemDetailsScreen + * UI state for TaskDetailsScreen */ data class TaskDetailsUiState( val taskDetails: TaskDetails = TaskDetails() -) +) \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d271bdb2..5eeb8d99 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -34,4 +34,6 @@ Sell Yes *required fields + Cancel + confirm From e0e9c3c49af386a2410b68741ce4631fe28ab555 Mon Sep 17 00:00:00 2001 From: Max7414 Date: Tue, 13 May 2025 11:48:10 +0800 Subject: [PATCH 30/32] =?UTF-8?q?=E5=8A=A0=E5=85=A5swipe=E7=9A=84=E4=BE=9D?= =?UTF-8?q?=E8=B3=B4=20=E5=8A=A0=E5=85=A5=E6=BB=91=E5=8B=95=E5=88=AA?= =?UTF-8?q?=E9=99=A4=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 4 +- .../example/inventory/ui/home/HomeScreen.kt | 201 +++++++++--------- .../inventory/ui/home/HomeViewModel.kt | 47 +--- 3 files changed, 109 insertions(+), 143 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index b9008c76..3b122b29 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -75,7 +75,9 @@ dependencies { implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.7") implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.7") implementation("androidx.navigation:navigation-compose:2.8.4") - + implementation(platform("androidx.compose:compose-bom:2025.02.00")) + implementation("androidx.compose.material3:material3") + implementation("androidx.compose.material:material:1.8.1") //Room implementation("androidx.room:room-runtime:${rootProject.extra["room_version"]}") implementation("androidx.core:core-ktx:1.15.0") diff --git a/app/src/main/java/com/example/inventory/ui/home/HomeScreen.kt b/app/src/main/java/com/example/inventory/ui/home/HomeScreen.kt index adccf2ee..5128ed1e 100644 --- a/app/src/main/java/com/example/inventory/ui/home/HomeScreen.kt +++ b/app/src/main/java/com/example/inventory/ui/home/HomeScreen.kt @@ -1,50 +1,22 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - package com.example.inventory.ui.home +import androidx.compose.animation.animateColorAsState +import androidx.compose.foundation.background import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.asPaddingValues -import androidx.compose.foundation.layout.calculateEndPadding -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.safeDrawing +import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items +import androidx.compose.material.DismissDirection +import androidx.compose.material.DismissState +import androidx.compose.material.DismissValue +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.SwipeToDismiss import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add -import androidx.compose.material3.Card -import androidx.compose.material3.CardDefaults -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.FloatingActionButton -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Scaffold -import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBarDefaults -import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue +import androidx.compose.material.icons.filled.Delete +import androidx.compose.material.rememberDismissState +import androidx.compose.material3.* +import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -62,15 +34,13 @@ import com.example.inventory.data.Task import com.example.inventory.ui.AppViewModelProvider import com.example.inventory.ui.navigation.NavigationDestination import com.example.inventory.ui.theme.InventoryTheme +import kotlinx.coroutines.launch object HomeDestination : NavigationDestination { override val route = "home" override val titleRes = R.string.app_name } -/** - * Entry route for Home screen - */ @OptIn(ExperimentalMaterial3Api::class) @Composable fun HomeScreen( @@ -82,6 +52,9 @@ fun HomeScreen( val homeUiState by viewModel.homeUiState.collectAsState() val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior() + val snackbarHostState = remember { SnackbarHostState() } + val scope = rememberCoroutineScope() + Scaffold( modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection), topBar = { @@ -91,6 +64,7 @@ fun HomeScreen( scrollBehavior = scrollBehavior ) }, + snackbarHost = { SnackbarHost(snackbarHostState) }, floatingActionButton = { FloatingActionButton( onClick = navigateToItemEntry, @@ -101,17 +75,23 @@ fun HomeScreen( .calculateEndPadding(LocalLayoutDirection.current) ) ) { - Icon( - imageVector = Icons.Default.Add, - contentDescription = stringResource(R.string.task_entry_title) - ) + Icon(Icons.Default.Add, contentDescription = stringResource(R.string.task_entry_title)) } }, ) { innerPadding -> HomeBody( taskList = homeUiState.taskList, onTaskClick = navigateToItemUpdate, - modifier = modifier.fillMaxSize(), + onDelete = { task -> + viewModel.delete(task) + scope.launch { + val res = snackbarHostState.showSnackbar( + "已刪除 ${task.name}", actionLabel = "復原", duration = SnackbarDuration.Short + ) + if (res == SnackbarResult.ActionPerformed) viewModel.insert(task) + } + }, + modifier = Modifier.fillMaxSize(), contentPadding = innerPadding, ) } @@ -121,24 +101,23 @@ fun HomeScreen( private fun HomeBody( taskList: List, onTaskClick: (Int) -> Unit, + onDelete: (Task) -> Unit, modifier: Modifier = Modifier, contentPadding: PaddingValues = PaddingValues(0.dp), ) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = modifier, - ) { + Column(horizontalAlignment = Alignment.CenterHorizontally, modifier = modifier) { if (taskList.isEmpty()) { Text( - text = stringResource(R.string.no_task_description), + stringResource(R.string.no_task_description), textAlign = TextAlign.Center, style = MaterialTheme.typography.titleLarge, - modifier = Modifier.padding(contentPadding), + modifier = Modifier.padding(contentPadding) ) } else { InventoryList( taskList = taskList, onItemClick = { onTaskClick(it.id) }, + onDelete = onDelete, contentPadding = contentPadding, modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.padding_small)) ) @@ -150,55 +129,88 @@ private fun HomeBody( private fun InventoryList( taskList: List, onItemClick: (Task) -> Unit, + onDelete: (Task) -> Unit, contentPadding: PaddingValues, modifier: Modifier = Modifier ) { - LazyColumn( - modifier = modifier, - contentPadding = contentPadding - ) { - items(items = taskList, key = { it.id }) { item -> - InventoryItem(task = item, - modifier = Modifier - .padding(dimensionResource(id = R.dimen.padding_small)) - .clickable { onItemClick(item) }) + LazyColumn(modifier = modifier, contentPadding = contentPadding) { + items(taskList, key = { it.id }) { task -> + DismissibleTask( + task = task, + onDelete = onDelete, + onClick = onItemClick, + modifier = Modifier.padding(dimensionResource(id = R.dimen.padding_small)) + ) } } } +@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class) @Composable -private fun InventoryItem( - task: Task, modifier: Modifier = Modifier +fun DismissibleTask( + task: Task, + onDelete: (Task) -> Unit, + onClick: (Task) -> Unit, + modifier: Modifier = Modifier ) { + val dismissState = rememberDismissState(confirmStateChange = { newState: DismissValue -> + if (newState == DismissValue.DismissedToEnd || newState == DismissValue.DismissedToStart) { + onDelete(task) + true + } else false + }) + + SwipeToDismiss( + state = dismissState, + background = { SwipeBackground(dismissState) }, + dismissContent = { + InventoryItem(task, modifier.clickable { onClick(task) }) + } + ) +} + +@OptIn(ExperimentalMaterialApi::class) +@Composable +private fun SwipeBackground(state: DismissState) { + val bgColor by animateColorAsState( + if (state.targetValue == DismissValue.Default) MaterialTheme.colorScheme.surfaceVariant + else MaterialTheme.colorScheme.errorContainer + ) + val alignment = when (state.dismissDirection) { + DismissDirection.StartToEnd -> Alignment.CenterStart + DismissDirection.EndToStart -> Alignment.CenterEnd + else -> Alignment.Center + } + Box( + Modifier.fillMaxWidth().height(64.dp).background(bgColor).padding(horizontal = 20.dp), + contentAlignment = alignment + ) { + Icon(Icons.Default.Delete, contentDescription = null, + tint = MaterialTheme.colorScheme.onErrorContainer) + } +} + +@Composable +private fun InventoryItem(task: Task, modifier: Modifier = Modifier) { val cardColor = when (task.priority.lowercase()) { "high" -> MaterialTheme.colorScheme.errorContainer "medium" -> Color(0xFFFFF9C4) "low" -> Color(0xFFC8E6C9) else -> MaterialTheme.colorScheme.surface } - Card( - modifier = modifier, - elevation = CardDefaults.cardElevation(defaultElevation = 2.dp), + modifier, + elevation = CardDefaults.cardElevation(2.dp), colors = CardDefaults.cardColors(containerColor = cardColor) ) { Column( - modifier = Modifier.padding(dimensionResource(id = R.dimen.padding_large)), + Modifier.padding(dimensionResource(id = R.dimen.padding_large)), verticalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.padding_small)) ) { - Row( - modifier = Modifier.fillMaxWidth() - ) { - Text( - text = task.name, - style = MaterialTheme.typography.titleLarge, - ) + Row(Modifier.fillMaxWidth()) { + Text(task.name, style = MaterialTheme.typography.titleLarge) Spacer(Modifier.weight(1f)) - - Text( - text = stringResource(R.string.in_stock, task.priority), - style = MaterialTheme.typography.titleMedium - ) + Text(stringResource(R.string.in_stock, task.priority), style = MaterialTheme.typography.titleMedium) } } } @@ -206,28 +218,11 @@ private fun InventoryItem( @Preview(showBackground = true) @Composable -fun HomeBodyPreview() { - InventoryTheme { - HomeBody(listOf( - Task(1, "Task1", "High"), Task(2, "Task2", "Medium"), Task(3, "Task3", "Low") - ), onTaskClick = {}) - } -} - -@Preview(showBackground = true) -@Composable -fun HomeBodyEmptyListPreview() { +fun PreviewHomeBody() { InventoryTheme { - HomeBody(listOf(), onTaskClick = {}) - } -} - -@Preview(showBackground = true) -@Composable -fun InventoryItemPreview() { - InventoryTheme { - InventoryItem( - Task(1, "Task1", "Medium"), + HomeBody( + listOf(Task(1, "Task1", "High"), Task(2, "Task2", "Medium"), Task(3, "Task3", "Low")), + onTaskClick = {}, onDelete = {} ) } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/example/inventory/ui/home/HomeViewModel.kt b/app/src/main/java/com/example/inventory/ui/home/HomeViewModel.kt index 9ed7f17a..e68678dc 100644 --- a/app/src/main/java/com/example/inventory/ui/home/HomeViewModel.kt +++ b/app/src/main/java/com/example/inventory/ui/home/HomeViewModel.kt @@ -1,19 +1,3 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - package com.example.inventory.ui.home import androidx.lifecycle.ViewModel @@ -24,30 +8,15 @@ import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch -/** - * ViewModel to retrieve all items in the Room database. - */ -class HomeViewModel(tasksRepository: TasksRepository) : ViewModel() { - - /** - * Holds home ui state. The list of items are retrieved from [TasksRepository] and mapped to - * [HomeUiState] - */ - val homeUiState: StateFlow = - tasksRepository.getAllTasksStream().map { HomeUiState(it) } - .stateIn( - scope = viewModelScope, - started = SharingStarted.WhileSubscribed(TIMEOUT_MILLIS), - initialValue = HomeUiState() - ) +class HomeViewModel(private val repository: TasksRepository) : ViewModel() { + val homeUiState: StateFlow = repository.getAllTasksStream() + .map(::HomeUiState) + .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), HomeUiState()) - companion object { - private const val TIMEOUT_MILLIS = 5_000L - } + fun delete(task: Task) = viewModelScope.launch { repository.deleteTask(task) } + fun insert(task: Task) = viewModelScope.launch { repository.insertTask(task) } } -/** - * Ui State for HomeScreen - */ -data class HomeUiState(val taskList: List = listOf()) +data class HomeUiState(val taskList: List = emptyList()) \ No newline at end of file From e83b626e09358235be1474a5731db9e0fc86f78d Mon Sep 17 00:00:00 2001 From: Max7414 Date: Wed, 14 May 2025 13:50:41 +0800 Subject: [PATCH 31/32] =?UTF-8?q?=E5=A2=9E=E5=8A=A0readme.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 90 +++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 71 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index fbaa35b6..35ac8962 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,81 @@ -Inventory app +To Do List app ================================== -Solution code for Android Basics with Compose. -Introduction + +問題一 ------------ -This app is an Inventory tracking app. Demos how to add, update, sell, and delete items from the local database. -This app demonstrated the use of Android Jetpack component [Room](https://developer.android.com/training/data-storage/room) database. -The app also leverages [ViewModel](https://developer.android.com/topic/libraries/architecture/viewmodel), -[Flow](https://developer.android.com/kotlin/flow), -and [Navigation](https://developer.android.com/topic/libraries/architecture/navigation/). +*clone下來的存貨APP無法開啟* -Pre-requisites --------------- +## 解決方法 + +更改虛擬機的版本以及系統 + + + +## 問題二 + +*變數 函式名 檔案名在重構的過程中有許多問題 或是在修改後不能使用* + +## 解決方法 + +用Git進行版本控制 在每個重大改變前先commit + + + +## 問題三 + +*修改 Enity後程式運行會崩潰* + +## 解決方法 + +查看log cat 找到崩潰原因 + +```kotlin +android.database.sqlite.SQLiteException: […] +no such column: priority (code 1 SQLITE_ERROR): + , while compiling: SELECT * FROM tasks ORDER BY name ASC +``` -You need to know: -- How to create and use composables. -- How to navigate between composables, and pass data between them. -- How to use architecture components including ViewModel, Flow, StateFlow and StateUi. -- How to use coroutines for long-running tasks. -- SQLite database and the SQLite query language +詢問ChatGPT後是Room的語法問題 +```kotlin +@Database(entities = [Task::class], version = 2, exportSchema = false) -Getting Started ---------------- +//需要將version改成2 +abstract class AppDatabase : RoomDatabase() { + // … + companion object { + fun create(context: Context): AppDatabase = + Room.databaseBuilder(context, AppDatabase::class.java, "app.db") + .fallbackToDestructiveMigration() // 直接砍掉舊 DB,重建新結構 + .build() + } +} +``` -1. Download and run the app. +## 問題四 + +*無法使用swipe元件* + +## 解決方法 + +```kotlin + implementation("androidx.compose.material:material:1.8.1") +``` + +修改gradle檔 導入相依元件 + + + + + + + + + + + + +-------------- From 4dc34066a76af44763746bcbcb67def7b6a1cb88 Mon Sep 17 00:00:00 2001 From: OvO <90671891+Max7414@users.noreply.github.com> Date: Wed, 17 Sep 2025 13:16:53 +0800 Subject: [PATCH 32/32] DT-1 Adjust readme DT-1 Adjust readme --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 35ac8962..e27ae0e2 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +TESTMESSAGE + To Do List app ==================================