diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..9309143 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,29 @@ +name: Build + +on: + pull_request: + branches: + - main + - starter + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set Up JDK + uses: actions/setup-java@v4 + with: + distribution: 'zulu' # See 'Supported distributions' for available options + java-version: '17' + cache: 'gradle' + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + + - name: Setup Android SDK + uses: android-actions/setup-android@v3 + + - name: Build project + run: ./gradlew :app:assembleDebug diff --git a/app/build.gradle b/app/build.gradle deleted file mode 100644 index 6fbeae4..0000000 --- a/app/build.gradle +++ /dev/null @@ -1,89 +0,0 @@ -/* - * 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. - */ -plugins { - id 'com.android.application' - id 'org.jetbrains.kotlin.android' - id 'com.google.devtools.ksp' version "1.8.21-1.0.11" -} - -android { - compileSdk 33 - - defaultConfig { - applicationId "com.example.inventory" - minSdk 24 - targetSdk 33 - versionCode 1 - versionName "1.0" - - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - vectorDrawables { - useSupportLibrary true - } - } - - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - } - compileOptions { - sourceCompatibility JavaVersion.VERSION_17 - targetCompatibility JavaVersion.VERSION_17 - } - kotlinOptions { - jvmTarget = '17' - freeCompilerArgs += "-opt-in=androidx.compose.material3.ExperimentalMaterial3Api" - } - buildFeatures { - compose true - } - composeOptions { - kotlinCompilerExtensionVersion '1.4.7' - } - packagingOptions { - resources { - excludes += '/META-INF/{AL2.0,LGPL2.1}' - } - } - namespace 'com.example.inventory' -} - -dependencies { - // Import the Compose BOM - implementation platform('androidx.compose:compose-bom:2023.05.00') - - implementation 'androidx.activity:activity-compose:1.7.1' - implementation 'androidx.compose.material3:material3' - implementation "androidx.compose.ui:ui" - implementation "androidx.compose.ui:ui-tooling" - implementation "androidx.compose.ui:ui-tooling-preview" - implementation 'androidx.core:core-ktx:1.10.0' - implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.1' - implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1" - implementation "androidx.navigation:navigation-compose:2.5.3" - - //Room - implementation "androidx.room:room-runtime:$room_version" - implementation 'androidx.core:core-ktx:1.10.0' - ksp "androidx.room:room-compiler:$room_version" - implementation "androidx.room:room-ktx:$room_version" - - // Testing - androidTestImplementation "androidx.test.espresso:espresso-core:3.5.1" - androidTestImplementation "androidx.test.ext:junit:1.1.5" -} diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 0000000..e891559 --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,82 @@ +/* + * 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. + */ + +plugins { + id("com.android.application") + id("org.jetbrains.kotlin.android") + id("com.google.devtools.ksp") version "2.1.0-1.0.29" + id("org.jetbrains.kotlin.plugin.compose") +} + +android { + compileSdk = 35 + + defaultConfig { + applicationId = "com.example.inventory" + minSdk = 24 + targetSdk = 35 + versionCode = 1 + versionName = "1.0" + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + vectorDrawables { + useSupportLibrary = true + } + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + kotlinOptions { + jvmTarget = "17" + } + buildFeatures { + compose = true + } + packaging { + resources { + excludes += "/META-INF/{AL2.0,LGPL2.1}" + } + } + namespace = "com.example.inventory" +} + +dependencies { + // Import the Compose BOM + implementation(platform("androidx.compose:compose-bom:2024.12.01")) + implementation("androidx.activity:activity-compose:1.9.3") + implementation("androidx.compose.material3:material3") + implementation("androidx.compose.ui:ui") + implementation("androidx.compose.ui:ui-tooling") + implementation("androidx.compose.ui:ui-tooling-preview") + 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.5") + + // Testing + androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1") + androidTestImplementation("androidx.test.ext:junit:1.2.1") +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 481bb43..2f9dc5a 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -1,6 +1,6 @@ # Add project specific ProGuard rules here. # You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. +# proguardFiles setting in build.gradle.kts. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html @@ -18,4 +18,4 @@ # If you keep the line number information, uncomment this to # hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file +#-renamesourcefileattribute SourceFile diff --git a/app/src/androidTest/java/com/example/inventory/ItemDaoTest.kt b/app/src/androidTest/java/com/example/inventory/ItemDaoTest.kt deleted file mode 100644 index da73558..0000000 --- a/app/src/androidTest/java/com/example/inventory/ItemDaoTest.kt +++ /dev/null @@ -1,118 +0,0 @@ -/* - * 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 - -import android.content.Context -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 kotlinx.coroutines.flow.first -import kotlinx.coroutines.runBlocking -import org.junit.After -import org.junit.Assert.assertEquals -import org.junit.Assert.assertTrue -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import java.io.IOException - -@RunWith(AndroidJUnit4::class) -class ItemDaoTest { - - private lateinit var itemDao: ItemDao - private lateinit var inventoryDatabase: InventoryDatabase - private val item1 = Item(1, "Apples", 10.0, 20) - private val item2 = Item(2, "Bananas", 15.0, 97) - - @Before - fun createDb() { - val context: Context = ApplicationProvider.getApplicationContext() - // Using an in-memory database because the information stored here disappears when the - // process is killed. - inventoryDatabase = Room.inMemoryDatabaseBuilder(context, InventoryDatabase::class.java) - // Allowing main thread queries, just for testing. - .allowMainThreadQueries() - .build() - itemDao = inventoryDatabase.itemDao() - } - - @After - @Throws(IOException::class) - fun closeDb() { - inventoryDatabase.close() - } - - @Test - @Throws(Exception::class) - fun daoInsert_insertsItemIntoDB() = runBlocking { - addOneItemToDb() - val allItems = itemDao.getAllItems().first() - assertEquals(allItems[0], item1) - } - - @Test - @Throws(Exception::class) - fun daoGetAllItems_returnsAllItemsFromDB() = runBlocking { - addTwoItemsToDb() - val allItems = itemDao.getAllItems().first() - assertEquals(allItems[0], item1) - assertEquals(allItems[1], item2) - } - - - @Test - @Throws(Exception::class) - fun daoGetItem_returnsItemFromDB() = runBlocking { - addOneItemToDb() - val item = itemDao.getItem(1) - assertEquals(item.first(), item1) - } - - @Test - @Throws(Exception::class) - fun daoDeleteItems_deletesAllItemsFromDB() = runBlocking { - addTwoItemsToDb() - itemDao.delete(item1) - itemDao.delete(item2) - val allItems = itemDao.getAllItems().first() - assertTrue(allItems.isEmpty()) - } - - @Test - @Throws(Exception::class) - fun daoUpdateItems_updatesItemsInDB() = runBlocking { - addTwoItemsToDb() - itemDao.update(Item(1, "Apples", 15.0, 25)) - itemDao.update(Item(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)) - } - - private suspend fun addOneItemToDb() { - itemDao.insert(item1) - } - - private suspend fun addTwoItemsToDb() { - itemDao.insert(item1) - itemDao.insert(item2) - } -} diff --git a/app/src/main/java/com/example/inventory/InventoryApp.kt b/app/src/main/java/com/example/inventory/InventoryApp.kt index 4140222..576b048 100644 --- a/app/src/main/java/com/example/inventory/InventoryApp.kt +++ b/app/src/main/java/com/example/inventory/InventoryApp.kt @@ -14,12 +14,14 @@ * limitations under the License. */ +@file:OptIn(ExperimentalMaterial3Api::class) + package com.example.inventory -import android.annotation.SuppressLint import androidx.compose.material.icons.Icons.Filled import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material3.CenterAlignedTopAppBar +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.Text @@ -35,7 +37,6 @@ import com.example.inventory.ui.navigation.InventoryNavHost /** * Top level composable that represents screens for the application. */ -@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter") @Composable fun InventoryApp(navController: NavHostController = rememberNavController()) { InventoryNavHost(navController = navController) diff --git a/app/src/main/java/com/example/inventory/InventoryApplication.kt b/app/src/main/java/com/example/inventory/InventoryApplication.kt index 7a6d654..c0d8c1d 100644 --- a/app/src/main/java/com/example/inventory/InventoryApplication.kt +++ b/app/src/main/java/com/example/inventory/InventoryApplication.kt @@ -26,6 +26,7 @@ class InventoryApplication : Application() { * AppContainer instance used by the rest of classes to obtain dependencies */ lateinit var container: AppContainer + override fun onCreate() { super.onCreate() container = AppDataContainer(this) diff --git a/app/src/main/java/com/example/inventory/MainActivity.kt b/app/src/main/java/com/example/inventory/MainActivity.kt index 86480b7..e48ea52 100644 --- a/app/src/main/java/com/example/inventory/MainActivity.kt +++ b/app/src/main/java/com/example/inventory/MainActivity.kt @@ -18,6 +18,7 @@ package com.example.inventory import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface @@ -26,6 +27,7 @@ import com.example.inventory.ui.theme.InventoryTheme class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { + enableEdgeToEdge() super.onCreate(savedInstanceState) setContent { InventoryTheme { 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 fc35231..0d4414c 100644 --- a/app/src/main/java/com/example/inventory/data/AppContainer.kt +++ b/app/src/main/java/com/example/inventory/data/AppContainer.kt @@ -33,6 +33,6 @@ class AppDataContainer(private val context: Context) : AppContainer { * Implementation for [ItemsRepository] */ override val itemsRepository: ItemsRepository by lazy { - OfflineItemsRepository(InventoryDatabase.getDatabase(context).itemDao()) + OfflineItemsRepository() } } diff --git a/app/src/main/java/com/example/inventory/data/InventoryDatabase.kt b/app/src/main/java/com/example/inventory/data/InventoryDatabase.kt deleted file mode 100644 index d9977d9..0000000 --- a/app/src/main/java/com/example/inventory/data/InventoryDatabase.kt +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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.data - -import android.content.Context -import androidx.room.Database -import androidx.room.Room -import androidx.room.RoomDatabase - -/** - * Database class with a singleton Instance object. - */ -@Database(entities = [Item::class], version = 1, exportSchema = false) -abstract class InventoryDatabase : RoomDatabase() { - - abstract fun itemDao(): ItemDao - - companion object { - @Volatile - private var Instance: InventoryDatabase? = null - - fun getDatabase(context: Context): InventoryDatabase { - // if the Instance is not null, return it, otherwise create a new database instance. - return Instance ?: synchronized(this) { - Room.databaseBuilder(context, InventoryDatabase::class.java, "item_database") - /** - * Setting this option in your app's database builder means that Room - * permanently deletes all data from the tables in your database when it - * attempts to perform a migration with no defined migration path. - */ - .fallbackToDestructiveMigration() - .build() - .also { Instance = it } - } - } - } -} diff --git a/app/src/main/java/com/example/inventory/data/Item.kt b/app/src/main/java/com/example/inventory/data/Item.kt index edad7ca..eeef6bf 100644 --- a/app/src/main/java/com/example/inventory/data/Item.kt +++ b/app/src/main/java/com/example/inventory/data/Item.kt @@ -16,15 +16,11 @@ package com.example.inventory.data -import androidx.room.Entity -import androidx.room.PrimaryKey /** * Entity data class represents a single row in the database. */ -@Entity(tableName = "items") -data class Item( - @PrimaryKey(autoGenerate = true) +class Item( val id: Int = 0, val name: String, val price: Double, diff --git a/app/src/main/java/com/example/inventory/data/ItemDao.kt b/app/src/main/java/com/example/inventory/data/ItemDao.kt deleted file mode 100644 index 22b14c7..0000000 --- a/app/src/main/java/com/example/inventory/data/ItemDao.kt +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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.data - -import androidx.room.Dao -import androidx.room.Delete -import androidx.room.Insert -import androidx.room.OnConflictStrategy -import androidx.room.Query -import androidx.room.Update -import kotlinx.coroutines.flow.Flow - -/** - * Database access object to access the Inventory database - */ -@Dao -interface ItemDao { - - @Query("SELECT * from items ORDER BY name ASC") - fun getAllItems(): Flow> - - @Query("SELECT * from items 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) - - @Update - suspend fun update(item: Item) - - @Delete - suspend fun delete(item: Item) -} diff --git a/app/src/main/java/com/example/inventory/data/ItemsRepository.kt b/app/src/main/java/com/example/inventory/data/ItemsRepository.kt index 5702954..b956ea6 100644 --- a/app/src/main/java/com/example/inventory/data/ItemsRepository.kt +++ b/app/src/main/java/com/example/inventory/data/ItemsRepository.kt @@ -16,34 +16,7 @@ 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. */ -interface ItemsRepository { - /** - * Retrieve all the items from the the given data source. - */ - fun getAllItemsStream(): Flow> - - /** - * Retrieve an item from the given data source that matches with the [id]. - */ - fun getItemStream(id: Int): Flow - - /** - * Insert item in the data source - */ - suspend fun insertItem(item: Item) - - /** - * Delete item from the data source - */ - suspend fun deleteItem(item: Item) - - /** - * Update item in the data source - */ - suspend fun updateItem(item: Item) -} +interface ItemsRepository diff --git a/app/src/main/java/com/example/inventory/data/OfflineItemsRepository.kt b/app/src/main/java/com/example/inventory/data/OfflineItemsRepository.kt index ed4c03b..d621a8f 100644 --- a/app/src/main/java/com/example/inventory/data/OfflineItemsRepository.kt +++ b/app/src/main/java/com/example/inventory/data/OfflineItemsRepository.kt @@ -16,16 +16,4 @@ package com.example.inventory.data -import kotlinx.coroutines.flow.Flow - -class OfflineItemsRepository(private val itemDao: ItemDao) : ItemsRepository { - override fun getAllItemsStream(): Flow> = itemDao.getAllItems() - - override fun getItemStream(id: Int): Flow = itemDao.getItem(id) - - override suspend fun insertItem(item: Item) = itemDao.insert(item) - - override suspend fun deleteItem(item: Item) = itemDao.delete(item) - - override suspend fun updateItem(item: Item) = itemDao.update(item) -} +class OfflineItemsRepository : ItemsRepository 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 be50e54..8aa8b48 100644 --- a/app/src/main/java/com/example/inventory/ui/AppViewModelProvider.kt +++ b/app/src/main/java/com/example/inventory/ui/AppViewModelProvider.kt @@ -36,26 +36,24 @@ object AppViewModelProvider { // Initializer for ItemEditViewModel initializer { ItemEditViewModel( - this.createSavedStateHandle(), - inventoryApplication().container.itemsRepository + this.createSavedStateHandle() ) } // Initializer for ItemEntryViewModel initializer { - ItemEntryViewModel(inventoryApplication().container.itemsRepository) + ItemEntryViewModel() } // Initializer for ItemDetailsViewModel initializer { ItemDetailsViewModel( - this.createSavedStateHandle(), - inventoryApplication().container.itemsRepository + this.createSavedStateHandle() ) } // Initializer for HomeViewModel initializer { - HomeViewModel(inventoryApplication().container.itemsRepository) + HomeViewModel() } } } 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 7a5a3c9..f3cda80 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 @@ -16,10 +16,10 @@ package com.example.inventory.ui.home -import android.annotation.SuppressLint 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.fillMaxSize @@ -31,6 +31,7 @@ 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 @@ -38,8 +39,6 @@ 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.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll @@ -48,11 +47,9 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign 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 import com.example.inventory.data.Item -import com.example.inventory.ui.AppViewModelProvider import com.example.inventory.ui.item.formatedPrice import com.example.inventory.ui.navigation.NavigationDestination import com.example.inventory.ui.theme.InventoryTheme @@ -65,15 +62,13 @@ object HomeDestination : NavigationDestination { /** * Entry route for Home screen */ -@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter") +@OptIn(ExperimentalMaterial3Api::class) @Composable fun HomeScreen( navigateToItemEntry: () -> Unit, navigateToItemUpdate: (Int) -> Unit, - modifier: Modifier = Modifier, - viewModel: HomeViewModel = viewModel(factory = AppViewModelProvider.Factory) + modifier: Modifier = Modifier ) { - val homeUiState by viewModel.homeUiState.collectAsState() val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior() Scaffold( @@ -99,33 +94,37 @@ fun HomeScreen( }, ) { innerPadding -> HomeBody( - itemList = homeUiState.itemList, + itemList = listOf(), onItemClick = navigateToItemUpdate, - modifier = modifier - .padding(innerPadding) - .fillMaxSize() + modifier = modifier.fillMaxSize(), + contentPadding = innerPadding, ) } } @Composable private fun HomeBody( - itemList: List, onItemClick: (Int) -> Unit, modifier: Modifier = Modifier + itemList: List, + onItemClick: (Int) -> Unit, + modifier: Modifier = Modifier, + contentPadding: PaddingValues = PaddingValues(0.dp), ) { Column( horizontalAlignment = Alignment.CenterHorizontally, - modifier = modifier + modifier = modifier, ) { if (itemList.isEmpty()) { Text( text = stringResource(R.string.no_item_description), textAlign = TextAlign.Center, - style = MaterialTheme.typography.titleLarge + style = MaterialTheme.typography.titleLarge, + modifier = Modifier.padding(contentPadding), ) } else { InventoryList( itemList = itemList, onItemClick = { onItemClick(it.id) }, + contentPadding = contentPadding, modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.padding_small)) ) } @@ -134,9 +133,15 @@ private fun HomeBody( @Composable private fun InventoryList( - itemList: List, onItemClick: (Item) -> Unit, modifier: Modifier = Modifier + itemList: List, + onItemClick: (Item) -> Unit, + contentPadding: PaddingValues, + modifier: Modifier = Modifier ) { - LazyColumn(modifier = modifier) { + LazyColumn( + modifier = modifier, + contentPadding = contentPadding + ) { items(items = itemList, key = { it.id }) { item -> InventoryItem(item = item, modifier = Modifier @@ -151,7 +156,8 @@ private fun InventoryItem( item: Item, modifier: Modifier = Modifier ) { Card( - modifier = modifier, elevation = CardDefaults.cardElevation(defaultElevation = 2.dp) + modifier = modifier, + elevation = CardDefaults.cardElevation(defaultElevation = 2.dp) ) { Column( modifier = Modifier.padding(dimensionResource(id = R.dimen.padding_large)), 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 9370177..7808690 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 @@ -17,31 +17,12 @@ 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 kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.stateIn /** - * View Model to retrieve all items in the Room database. + * ViewModel to retrieve all items in the Room database. */ -class HomeViewModel(itemsRepository: ItemsRepository) : ViewModel() { - - /** - * Holds home ui state. The list of items are retrieved from [ItemsRepository] and mapped to - * [HomeUiState] - */ - val homeUiState: StateFlow = - itemsRepository.getAllItemsStream().map { HomeUiState(it) } - .stateIn( - scope = viewModelScope, - started = SharingStarted.WhileSubscribed(TIMEOUT_MILLIS), - initialValue = HomeUiState() - ) - +class HomeViewModel : ViewModel() { companion object { private const val TIMEOUT_MILLIS = 5_000L } 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 143eff2..2c95c5d 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 @@ -21,6 +21,8 @@ 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.rememberScrollState @@ -31,6 +33,7 @@ import androidx.compose.material3.AlertDialog import androidx.compose.material3.Button 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 @@ -39,25 +42,21 @@ import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue 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.Modifier +import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight 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.ui.AppViewModelProvider import com.example.inventory.ui.navigation.NavigationDestination import com.example.inventory.ui.theme.InventoryTheme -import kotlinx.coroutines.launch object ItemDetailsDestination : NavigationDestination { override val route = "item_details" @@ -66,52 +65,46 @@ object ItemDetailsDestination : NavigationDestination { val routeWithArgs = "$route/{$itemIdArg}" } +@OptIn(ExperimentalMaterial3Api::class) @Composable fun ItemDetailsScreen( navigateToEditItem: (Int) -> Unit, navigateBack: () -> Unit, - modifier: Modifier = Modifier, - viewModel: ItemDetailsViewModel = viewModel(factory = AppViewModelProvider.Factory) + modifier: Modifier = Modifier ) { - val uiState = viewModel.uiState.collectAsState() - val coroutineScope = rememberCoroutineScope() - Scaffold(topBar = { - InventoryTopAppBar( - title = stringResource(ItemDetailsDestination.titleRes), - canNavigateBack = true, - navigateUp = navigateBack - ) - }, floatingActionButton = { - FloatingActionButton( - onClick = { navigateToEditItem(uiState.value.itemDetails.id) }, - shape = MaterialTheme.shapes.medium, - modifier = Modifier.padding(dimensionResource(id = R.dimen.padding_large)) - - ) { - Icon( - imageVector = Icons.Default.Edit, - contentDescription = stringResource(R.string.edit_item_title), + Scaffold( + topBar = { + InventoryTopAppBar( + title = stringResource(ItemDetailsDestination.titleRes), + canNavigateBack = true, + navigateUp = navigateBack ) - } - }, modifier = modifier + }, floatingActionButton = { + FloatingActionButton( + onClick = { navigateToEditItem(0) }, + shape = MaterialTheme.shapes.medium, + modifier = Modifier.padding(dimensionResource(id = R.dimen.padding_large)) + + ) { + Icon( + imageVector = Icons.Default.Edit, + contentDescription = stringResource(R.string.edit_item_title), + ) + } + }, modifier = modifier ) { innerPadding -> ItemDetailsBody( - itemDetailsUiState = uiState.value, - onSellItem = { 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 - // change occurs, the Activity will be recreated and the rememberCoroutineScope will - // be cancelled - since the scope is bound to composition. - coroutineScope.launch { - viewModel.deleteItem() - navigateBack() - } - }, + itemDetailsUiState = ItemDetailsUiState(), + onSellItem = { }, + onDelete = { }, modifier = Modifier - .padding(innerPadding) + .padding( + start = innerPadding.calculateStartPadding(LocalLayoutDirection.current), + end = innerPadding.calculateEndPadding(LocalLayoutDirection.current), + top = innerPadding.calculateTopPadding() + ) .verticalScroll(rememberScrollState()) - ) + ) } } @@ -127,14 +120,16 @@ private fun ItemDetailsBody( verticalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.padding_medium)) ) { var deleteConfirmationRequired by rememberSaveable { mutableStateOf(false) } + ItemDetails( - item = itemDetailsUiState.itemDetails.toItem(), modifier = Modifier.fillMaxWidth() + item = itemDetailsUiState.itemDetails.toItem(), + modifier = Modifier.fillMaxWidth() ) Button( onClick = onSellItem, modifier = Modifier.fillMaxWidth(), shape = MaterialTheme.shapes.small, - enabled = !itemDetailsUiState.outOfStock + enabled = true ) { Text(stringResource(R.string.sell)) } @@ -146,10 +141,11 @@ private fun ItemDetailsBody( Text(stringResource(R.string.delete)) } if (deleteConfirmationRequired) { - DeleteConfirmationDialog(onDeleteConfirm = { - deleteConfirmationRequired = false - onDelete() - }, + DeleteConfirmationDialog( + onDeleteConfirm = { + deleteConfirmationRequired = false + onDelete() + }, onDeleteCancel = { deleteConfirmationRequired = false }, modifier = Modifier.padding(dimensionResource(id = R.dimen.padding_medium)) ) @@ -157,13 +153,13 @@ private fun ItemDetailsBody( } } - @Composable fun ItemDetails( item: Item, modifier: Modifier = Modifier ) { Card( - modifier = modifier, colors = CardDefaults.cardColors( + modifier = modifier, + colors = CardDefaults.cardColors( containerColor = MaterialTheme.colorScheme.primaryContainer, contentColor = MaterialTheme.colorScheme.onPrimaryContainer ) @@ -172,28 +168,32 @@ fun ItemDetails( modifier = Modifier .fillMaxWidth() .padding(dimensionResource(id = R.dimen.padding_medium)), - verticalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.padding_medium)) + verticalArrangement = Arrangement.spacedBy( + dimensionResource(id = R.dimen.padding_medium) + ) ) { ItemDetailsRow( labelResID = R.string.item, itemDetail = item.name, - modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen - .padding_medium)) + modifier = Modifier.padding( + horizontal = dimensionResource(id = R.dimen.padding_medium) + ) ) ItemDetailsRow( labelResID = R.string.quantity_in_stock, itemDetail = item.quantity.toString(), - modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen - .padding_medium)) + modifier = Modifier.padding( + horizontal = dimensionResource(id = R.dimen.padding_medium) + ) ) ItemDetailsRow( labelResID = R.string.price, itemDetail = item.formatedPrice(), - modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen - .padding_medium)) + modifier = Modifier.padding( + horizontal = dimensionResource(id = R.dimen.padding_medium) + ) ) } - } } @@ -202,7 +202,7 @@ private fun ItemDetailsRow( @StringRes labelResID: Int, itemDetail: String, modifier: Modifier = Modifier ) { Row(modifier = modifier) { - Text(text = stringResource(labelResID)) + Text(stringResource(labelResID)) Spacer(modifier = Modifier.weight(1f)) Text(text = itemDetail, fontWeight = FontWeight.Bold) } @@ -210,7 +210,9 @@ private fun ItemDetailsRow( @Composable private fun DeleteConfirmationDialog( - onDeleteConfirm: () -> Unit, onDeleteCancel: () -> Unit, modifier: Modifier = Modifier + onDeleteConfirm: () -> Unit, + onDeleteCancel: () -> Unit, + modifier: Modifier = Modifier ) { AlertDialog(onDismissRequest = { /* Do nothing */ }, title = { Text(stringResource(R.string.attention)) }, @@ -218,12 +220,12 @@ private fun DeleteConfirmationDialog( modifier = modifier, dismissButton = { TextButton(onClick = onDeleteCancel) { - Text(text = stringResource(R.string.no)) + Text(stringResource(R.string.no)) } }, confirmButton = { TextButton(onClick = onDeleteConfirm) { - Text(text = stringResource(R.string.yes)) + Text(stringResource(R.string.yes)) } }) } @@ -232,8 +234,13 @@ private fun DeleteConfirmationDialog( @Composable fun ItemDetailsScreenPreview() { InventoryTheme { - ItemDetailsBody(ItemDetailsUiState( - outOfStock = true, itemDetails = ItemDetails(1, "Pen", "$100", "10") - ), onSellItem = {}, onDelete = {}) + ItemDetailsBody( + ItemDetailsUiState( + outOfStock = true, + itemDetails = ItemDetails(1, "Pen", "$100", "10") + ), + onSellItem = {}, + onDelete = {} + ) } } 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 c37b0f5..e92c5bd 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 @@ -18,59 +18,17 @@ 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 kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.filterNotNull -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.stateIn -import kotlinx.coroutines.launch /** * ViewModel to retrieve, update and delete an item from the [ItemsRepository]'s data source. */ class ItemDetailsViewModel( - savedStateHandle: SavedStateHandle, - private val itemsRepository: ItemsRepository, + savedStateHandle: SavedStateHandle ) : ViewModel() { private val itemId: Int = checkNotNull(savedStateHandle[ItemDetailsDestination.itemIdArg]) - /** - * Holds the item details ui state. The data is retrieved from [ItemsRepository] and mapped to - * the UI state. - */ - val uiState: StateFlow = - itemsRepository.getItemStream(itemId) - .filterNotNull() - .map { - ItemDetailsUiState(outOfStock = it.quantity <= 0, itemDetails = it.toItemDetails()) - }.stateIn( - scope = viewModelScope, - started = SharingStarted.WhileSubscribed(TIMEOUT_MILLIS), - initialValue = ItemDetailsUiState() - ) - - /** - * Reduces the item quantity by one and update the [ItemsRepository]'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)) - } - } - } - - /** - * Deletes the item from the [ItemsRepository]'s data source. - */ - suspend fun deleteItem() { - itemsRepository.deleteItem(uiState.value.itemDetails.toItem()) - } - companion object { private const val TIMEOUT_MILLIS = 5_000L } diff --git a/app/src/main/java/com/example/inventory/ui/item/ItemEditScreen.kt b/app/src/main/java/com/example/inventory/ui/item/ItemEditScreen.kt index f7a5ab4..51c154e 100644 --- a/app/src/main/java/com/example/inventory/ui/item/ItemEditScreen.kt +++ b/app/src/main/java/com/example/inventory/ui/item/ItemEditScreen.kt @@ -16,11 +16,16 @@ package com.example.inventory.ui.item +import androidx.compose.foundation.layout.calculateEndPadding +import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.lifecycle.viewmodel.compose.viewModel @@ -29,7 +34,6 @@ import com.example.inventory.R 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 ItemEditDestination : NavigationDestination { override val route = "item_edit" @@ -38,6 +42,7 @@ object ItemEditDestination : NavigationDestination { val routeWithArgs = "$route/{$itemIdArg}" } +@OptIn(ExperimentalMaterial3Api::class) @Composable fun ItemEditScreen( navigateBack: () -> Unit, @@ -45,7 +50,6 @@ fun ItemEditScreen( modifier: Modifier = Modifier, viewModel: ItemEditViewModel = viewModel(factory = AppViewModelProvider.Factory) ) { - val coroutineScope = rememberCoroutineScope() Scaffold( topBar = { InventoryTopAppBar( @@ -58,18 +62,15 @@ fun ItemEditScreen( ) { innerPadding -> ItemEntryBody( itemUiState = viewModel.itemUiState, - onItemValueChange = viewModel::updateUiState, - onSaveClick = { - // Note: If the user rotates the screen very fast, the operation may get cancelled - // and the item may not be updated in 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.updateItem() - navigateBack() - } - }, - modifier = Modifier.padding(innerPadding) + onItemValueChange = { }, + onSaveClick = { }, + modifier = Modifier + .padding( + start = innerPadding.calculateStartPadding(LocalLayoutDirection.current), + end = innerPadding.calculateEndPadding(LocalLayoutDirection.current), + top = innerPadding.calculateTopPadding() + ) + .verticalScroll(rememberScrollState()) ) } } 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 bdd6491..abd424d 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 @@ -21,18 +21,13 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope import com.example.inventory.data.ItemsRepository -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. */ class ItemEditViewModel( savedStateHandle: SavedStateHandle, - private val itemsRepository: ItemsRepository ) : ViewModel() { /** @@ -43,33 +38,6 @@ class ItemEditViewModel( private val itemId: Int = checkNotNull(savedStateHandle[ItemEditDestination.itemIdArg]) - init { - viewModelScope.launch { - itemUiState = itemsRepository.getItemStream(itemId) - .filterNotNull() - .first() - .toItemUiState(true) - } - } - - /** - * Update the item in the [ItemsRepository]'s data source - */ - suspend fun updateItem() { - if (validateInput(itemUiState.itemDetails)) { - itemsRepository.updateItem(itemUiState.itemDetails.toItem()) - } - } - - /** - * Updates the [itemUiState] with the value provided in the argument. This method also triggers - * a validation for input values. - */ - fun updateUiState(itemDetails: ItemDetails) { - itemUiState = - ItemUiState(itemDetails = itemDetails, isEntryValid = validateInput(itemDetails)) - } - private fun validateInput(uiState: ItemDetails = itemUiState.itemDetails): Boolean { return with(uiState) { name.isNotBlank() && price.isNotBlank() && quantity.isNotBlank() 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 f1b6bcb..565f3e7 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 @@ -18,20 +18,23 @@ package com.example.inventory.ui.item import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +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.rememberScrollState import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Button +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.OutlinedTextFieldDefaults import androidx.compose.material3.Scaffold import androidx.compose.material3.Text -import androidx.compose.material3.TextFieldDefaults import androidx.compose.runtime.Composable -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.KeyboardType @@ -42,7 +45,6 @@ import com.example.inventory.R 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 @@ -51,6 +53,7 @@ object ItemEntryDestination : NavigationDestination { override val titleRes = R.string.item_entry_title } +@OptIn(ExperimentalMaterial3Api::class) @Composable fun ItemEntryScreen( navigateBack: () -> Unit, @@ -58,7 +61,6 @@ fun ItemEntryScreen( canNavigateBack: Boolean = true, viewModel: ItemEntryViewModel = viewModel(factory = AppViewModelProvider.Factory) ) { - val coroutineScope = rememberCoroutineScope() Scaffold( topBar = { InventoryTopAppBar( @@ -71,18 +73,13 @@ fun ItemEntryScreen( ItemEntryBody( itemUiState = viewModel.itemUiState, onItemValueChange = viewModel::updateUiState, - onSaveClick = { - // Note: If the user rotates the screen very fast, the operation may get cancelled - // and the item may not be saved in 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.saveItem() - navigateBack() - } - }, + onSaveClick = { }, modifier = Modifier - .padding(innerPadding) + .padding( + start = innerPadding.calculateStartPadding(LocalLayoutDirection.current), + end = innerPadding.calculateEndPadding(LocalLayoutDirection.current), + top = innerPadding.calculateTopPadding() + ) .verticalScroll(rememberScrollState()) .fillMaxWidth() ) @@ -97,9 +94,9 @@ fun ItemEntryBody( modifier: Modifier = Modifier ) { Column( - modifier = modifier.padding(dimensionResource(id = R.dimen.padding_medium)), - verticalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.padding_large)) - ) { + verticalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.padding_large)), + modifier = modifier.padding(dimensionResource(id = R.dimen.padding_medium)) + ) { ItemInputForm( itemDetails = itemUiState.itemDetails, onValueChange = onItemValueChange, @@ -131,8 +128,10 @@ fun ItemInputForm( value = itemDetails.name, onValueChange = { onValueChange(itemDetails.copy(name = it)) }, label = { Text(stringResource(R.string.item_name_req)) }, - colors = TextFieldDefaults.outlinedTextFieldColors( - containerColor = MaterialTheme.colorScheme.secondaryContainer + colors = OutlinedTextFieldDefaults.colors( + focusedContainerColor = MaterialTheme.colorScheme.secondaryContainer, + unfocusedContainerColor = MaterialTheme.colorScheme.secondaryContainer, + disabledContainerColor = MaterialTheme.colorScheme.secondaryContainer, ), modifier = Modifier.fillMaxWidth(), enabled = enabled, @@ -143,8 +142,10 @@ fun ItemInputForm( onValueChange = { onValueChange(itemDetails.copy(price = it)) }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal), label = { Text(stringResource(R.string.item_price_req)) }, - colors = TextFieldDefaults.outlinedTextFieldColors( - containerColor = MaterialTheme.colorScheme.secondaryContainer + 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(), @@ -156,8 +157,10 @@ fun ItemInputForm( onValueChange = { onValueChange(itemDetails.copy(quantity = it)) }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), label = { Text(stringResource(R.string.quantity_req)) }, - colors = TextFieldDefaults.outlinedTextFieldColors( - containerColor = MaterialTheme.colorScheme.secondaryContainer + colors = OutlinedTextFieldDefaults.colors( + focusedContainerColor = MaterialTheme.colorScheme.secondaryContainer, + unfocusedContainerColor = MaterialTheme.colorScheme.secondaryContainer, + disabledContainerColor = MaterialTheme.colorScheme.secondaryContainer, ), modifier = Modifier.fillMaxWidth(), enabled = enabled, 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 fbb01ee..2291504 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 @@ -21,13 +21,12 @@ 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 java.text.NumberFormat /** - * View Model to validate and insert items in the Room database. + * ViewModel to validate and insert items in the Room database. */ -class ItemEntryViewModel(private val itemsRepository: ItemsRepository) : ViewModel() { +class ItemEntryViewModel : ViewModel() { /** * Holds current item ui state @@ -44,15 +43,6 @@ class ItemEntryViewModel(private val itemsRepository: ItemsRepository) : ViewMod ItemUiState(itemDetails = itemDetails, isEntryValid = validateInput(itemDetails)) } - /** - * Inserts an [Item] in the Room database - */ - suspend fun saveItem() { - if (validateInput()) { - itemsRepository.insertItem(itemUiState.itemDetails.toItem()) - } - } - private fun validateInput(uiState: ItemDetails = itemUiState.itemDetails): Boolean { return with(uiState) { name.isNotBlank() && price.isNotBlank() && quantity.isNotBlank() @@ -76,9 +66,9 @@ data class ItemDetails( ) /** - * Extension function to convert [ItemUiState] to [Item]. If the value of [ItemDetails.price] is + * Extension function to convert [ItemDetails] to [Item]. 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 + * [ItemDetails.quantity] is not a valid [Int], then the quantity will be set to 0 */ fun ItemDetails.toItem(): Item = Item( id = id, @@ -91,8 +81,6 @@ fun Item.formatedPrice(): String { return NumberFormat.getCurrencyInstance().format(price) } - - /** * Extension function to convert [Item] to [ItemUiState] */ diff --git a/app/src/main/java/com/example/inventory/ui/theme/Theme.kt b/app/src/main/java/com/example/inventory/ui/theme/Theme.kt index 64411b6..676bcbc 100644 --- a/app/src/main/java/com/example/inventory/ui/theme/Theme.kt +++ b/app/src/main/java/com/example/inventory/ui/theme/Theme.kt @@ -25,7 +25,6 @@ import androidx.compose.material3.dynamicLightColorScheme import androidx.compose.material3.lightColorScheme import androidx.compose.runtime.Composable import androidx.compose.runtime.SideEffect -import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalView @@ -112,16 +111,15 @@ fun InventoryTheme( darkTheme -> DarkColorScheme else -> LightColorScheme } + val view = LocalView.current if (!view.isInEditMode) { SideEffect { val window = (view.context as Activity).window - if(darkTheme) { - window.statusBarColor = colorScheme.primary.toArgb() - } else { - window.statusBarColor = Color.Transparent.toArgb() - } - WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = !darkTheme + window.statusBarColor = colorScheme.primary.toArgb() + WindowCompat + .getInsetsController(window, view) + .isAppearanceLightStatusBars = darkTheme } } diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 2ff1b92..4947386 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -15,6 +15,5 @@ ~ limitations under the License. --> -