From 4a42d1b51c78de7399e5103f09d8bdca79927f32 Mon Sep 17 00:00:00 2001 From: Sanju S Date: Sat, 1 May 2021 14:35:43 +0530 Subject: [PATCH 1/8] Add dependencies Signed-off-by: Spikeysanju --- app/build.gradle | 22 ++++++++++++++++++++++ build.gradle | 7 +++++++ 2 files changed, 29 insertions(+) diff --git a/app/build.gradle b/app/build.gradle index 32e0270..b9d9c3b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -29,6 +29,8 @@ plugins { id 'com.android.application' id 'kotlin-android' + id 'kotlin-kapt' + id 'dagger.hilt.android.plugin' } android { @@ -94,4 +96,24 @@ dependencies { implementation "androidx.datastore:datastore-preferences:1.0.0-beta01" implementation "androidx.lifecycle:lifecycle-viewmodel-compose:1.0.0-alpha04" implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.3.1" + + // Room + implementation "androidx.room:room-runtime:$roomVersion" + kapt "androidx.room:room-compiler:$roomVersion" + implementation "androidx.room:room-ktx:$roomVersion" + + // Coroutines + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion" + + + // Dagger Hilt + implementation "com.google.dagger:hilt-android:$hiltVersion" + kapt "com.google.dagger:hilt-android-compiler:$hiltVersion" + implementation "androidx.hilt:hilt-lifecycle-viewmodel:$hiltAndroidXVersion" + kapt "androidx.hilt:hilt-compiler:$hiltAndroidXVersion" + implementation "androidx.hilt:hilt-navigation-compose:1.0.0-alpha01" + implementation "androidx.hilt:hilt-common:1.0.0-beta01" + kapt "com.google.dagger:hilt-compiler:2.33-beta" + } \ No newline at end of file diff --git a/build.gradle b/build.gradle index a2a2c18..70aa7af 100644 --- a/build.gradle +++ b/build.gradle @@ -30,6 +30,11 @@ buildscript { ext { compose_version = '1.0.0-beta05' + hiltAndroidXVersion = "1.0.0-alpha03" + hiltVersion = "2.33-beta" + coroutinesVersion = "1.4.3" + roomVersion = "2.3.0" + } ext.kotlin_version = "1.4.32" repositories { @@ -39,6 +44,8 @@ buildscript { dependencies { classpath 'com.android.tools.build:gradle:7.0.0-alpha15' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "com.google.dagger:hilt-android-gradle-plugin:$hiltVersion" + classpath "com.google.dagger:hilt-android-gradle-plugin:$hiltVersion" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files From cf1af2dc48028593279e606abb6fa61381e596a5 Mon Sep 17 00:00:00 2001 From: Sanju S Date: Sat, 1 May 2021 17:17:06 +0530 Subject: [PATCH 2/8] Add Database & setup repository Signed-off-by: Spikeysanju --- .../spikeysanju/jetquotes/app/JetQuotes.kt | 9 +- .../jetquotes/components/QuotesCard.kt | 2 +- .../data/preference/db/FavouritesDao.kt | 53 +++++++++++ .../data/preference/db/JetQuotesDatabase.kt | 38 ++++++++ .../spikeysanju/jetquotes/di/AppComponent.kt | 59 ++++++++++++ .../spikeysanju/jetquotes/model/Favourite.kt | 40 ++++++++ .../jetquotes/navigation/JetQuotesMain.kt | 31 +++++-- .../jetquotes/navigation/Screen.kt | 1 + .../jetquotes/repository/MainRepository.kt | 48 ++++++++++ .../jetquotes/utils/FavouriteViewState.kt | 41 ++++++++ .../jetquotes/view/MainActivity.kt | 2 + .../view/favourites/FavouritesScreen.kt | 93 +++++++++++++++++++ .../jetquotes/view/quotes/QuotesListScreen.kt | 7 +- .../jetquotes/view/viewModel/MainViewModel.kt | 41 +++++++- app/src/main/res/values/strings.xml | 1 + 15 files changed, 452 insertions(+), 14 deletions(-) create mode 100644 app/src/main/java/www/spikeysanju/jetquotes/data/preference/db/FavouritesDao.kt create mode 100644 app/src/main/java/www/spikeysanju/jetquotes/data/preference/db/JetQuotesDatabase.kt create mode 100644 app/src/main/java/www/spikeysanju/jetquotes/di/AppComponent.kt create mode 100644 app/src/main/java/www/spikeysanju/jetquotes/model/Favourite.kt create mode 100644 app/src/main/java/www/spikeysanju/jetquotes/repository/MainRepository.kt create mode 100644 app/src/main/java/www/spikeysanju/jetquotes/utils/FavouriteViewState.kt create mode 100644 app/src/main/java/www/spikeysanju/jetquotes/view/favourites/FavouritesScreen.kt diff --git a/app/src/main/java/www/spikeysanju/jetquotes/app/JetQuotes.kt b/app/src/main/java/www/spikeysanju/jetquotes/app/JetQuotes.kt index 448bb01..b30f109 100644 --- a/app/src/main/java/www/spikeysanju/jetquotes/app/JetQuotes.kt +++ b/app/src/main/java/www/spikeysanju/jetquotes/app/JetQuotes.kt @@ -30,9 +30,8 @@ package www.spikeysanju.jetquotes.app import android.app.Application -class JetQuotes : Application() { - override fun onCreate() { - super.onCreate() - } -} +import dagger.hilt.android.HiltAndroidApp + +@HiltAndroidApp +class JetQuotes : Application() diff --git a/app/src/main/java/www/spikeysanju/jetquotes/components/QuotesCard.kt b/app/src/main/java/www/spikeysanju/jetquotes/components/QuotesCard.kt index 6337691..176c970 100644 --- a/app/src/main/java/www/spikeysanju/jetquotes/components/QuotesCard.kt +++ b/app/src/main/java/www/spikeysanju/jetquotes/components/QuotesCard.kt @@ -54,7 +54,7 @@ fun QuotesCard(quote: Quote, actions: MainActions) { .wrapContentSize() .padding(12.dp) .clickable(onClick = { - actions.quoteDetails(quote.quote!!, quote.author!!) + actions.gotoDetails(quote.quote!!, quote.author!!) }) .background(MaterialTheme.colors.primaryVariant) .padding(12.dp)) { diff --git a/app/src/main/java/www/spikeysanju/jetquotes/data/preference/db/FavouritesDao.kt b/app/src/main/java/www/spikeysanju/jetquotes/data/preference/db/FavouritesDao.kt new file mode 100644 index 0000000..32e6820 --- /dev/null +++ b/app/src/main/java/www/spikeysanju/jetquotes/data/preference/db/FavouritesDao.kt @@ -0,0 +1,53 @@ +/* + * + * * + * * * MIT License + * * * + * * * Copyright (c) 2020 Sanju S + * * * + * * * Permission is hereby granted, free of charge, to any person obtaining a copy + * * * of this software and associated documentation files (the "Software"), to deal + * * * in the Software without restriction, including without limitation the rights + * * * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * * * copies of the Software, and to permit persons to whom the Software is + * * * furnished to do so, subject to the following conditions: + * * * + * * * The above copyright notice and this permission notice shall be included in all + * * * copies or substantial portions of the Software. + * * * + * * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * * * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * * * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * * * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * * * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * * * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * * * SOFTWARE. + * * + * + */ + +package www.spikeysanju.jetquotes.data.preference.db + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.Query +import androidx.room.Update +import kotlinx.coroutines.flow.Flow +import www.spikeysanju.jetquotes.model.Favourite + +@Dao +interface FavouritesDao { + + @Query("SELECT * FROM favourites") + fun getAllFavourites(): Flow> + + @Insert + suspend fun insertFavourite(favourite: Favourite) + + @Query("DELETE FROM favourites where id=:id") + suspend fun deleteByID(id: Int) + + @Update + suspend fun updateFavourite(favourite: Favourite) + +} \ No newline at end of file diff --git a/app/src/main/java/www/spikeysanju/jetquotes/data/preference/db/JetQuotesDatabase.kt b/app/src/main/java/www/spikeysanju/jetquotes/data/preference/db/JetQuotesDatabase.kt new file mode 100644 index 0000000..5d90485 --- /dev/null +++ b/app/src/main/java/www/spikeysanju/jetquotes/data/preference/db/JetQuotesDatabase.kt @@ -0,0 +1,38 @@ +/* + * + * * + * * * MIT License + * * * + * * * Copyright (c) 2020 Sanju S + * * * + * * * Permission is hereby granted, free of charge, to any person obtaining a copy + * * * of this software and associated documentation files (the "Software"), to deal + * * * in the Software without restriction, including without limitation the rights + * * * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * * * copies of the Software, and to permit persons to whom the Software is + * * * furnished to do so, subject to the following conditions: + * * * + * * * The above copyright notice and this permission notice shall be included in all + * * * copies or substantial portions of the Software. + * * * + * * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * * * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * * * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * * * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * * * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * * * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * * * SOFTWARE. + * * + * + */ + +package www.spikeysanju.jetquotes.data.preference.db + +import androidx.room.Database +import androidx.room.RoomDatabase +import www.spikeysanju.jetquotes.model.Favourite + +@Database(entities = [Favourite::class], version = 1) +abstract class JetQuotesDatabase : RoomDatabase() { + abstract fun getFavouritesDao(): FavouritesDao +} \ No newline at end of file diff --git a/app/src/main/java/www/spikeysanju/jetquotes/di/AppComponent.kt b/app/src/main/java/www/spikeysanju/jetquotes/di/AppComponent.kt new file mode 100644 index 0000000..6a27612 --- /dev/null +++ b/app/src/main/java/www/spikeysanju/jetquotes/di/AppComponent.kt @@ -0,0 +1,59 @@ +/* + * + * * + * * * MIT License + * * * + * * * Copyright (c) 2020 Sanju S + * * * + * * * Permission is hereby granted, free of charge, to any person obtaining a copy + * * * of this software and associated documentation files (the "Software"), to deal + * * * in the Software without restriction, including without limitation the rights + * * * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * * * copies of the Software, and to permit persons to whom the Software is + * * * furnished to do so, subject to the following conditions: + * * * + * * * The above copyright notice and this permission notice shall be included in all + * * * copies or substantial portions of the Software. + * * * + * * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * * * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * * * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * * * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * * * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * * * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * * * SOFTWARE. + * * + * + */ + +package www.spikeysanju.jetquotes.di + +import android.content.Context +import androidx.room.Room +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import www.spikeysanju.jetquotes.data.preference.db.FavouritesDao +import www.spikeysanju.jetquotes.data.preference.db.JetQuotesDatabase +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object AppComponent { + + @Singleton + @Provides + fun provideDao(jetQuotesDatabase: JetQuotesDatabase): FavouritesDao = + jetQuotesDatabase.getFavouritesDao() + + @Singleton + @Provides + fun provideAppDatabase(@ApplicationContext context: Context): JetQuotesDatabase = + Room.databaseBuilder( + context, + JetQuotesDatabase::class.java, + "favourites-db" + ).fallbackToDestructiveMigration().build() +} \ No newline at end of file diff --git a/app/src/main/java/www/spikeysanju/jetquotes/model/Favourite.kt b/app/src/main/java/www/spikeysanju/jetquotes/model/Favourite.kt new file mode 100644 index 0000000..0df0119 --- /dev/null +++ b/app/src/main/java/www/spikeysanju/jetquotes/model/Favourite.kt @@ -0,0 +1,40 @@ +/* + * + * * + * * * MIT License + * * * + * * * Copyright (c) 2020 Sanju S + * * * + * * * Permission is hereby granted, free of charge, to any person obtaining a copy + * * * of this software and associated documentation files (the "Software"), to deal + * * * in the Software without restriction, including without limitation the rights + * * * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * * * copies of the Software, and to permit persons to whom the Software is + * * * furnished to do so, subject to the following conditions: + * * * + * * * The above copyright notice and this permission notice shall be included in all + * * * copies or substantial portions of the Software. + * * * + * * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * * * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * * * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * * * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * * * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * * * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * * * SOFTWARE. + * * + * + */ + +package www.spikeysanju.jetquotes.model + +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity(tableName = "favourites") +data class Favourite( + val quote: String? = "", + val author: String? = "", + @PrimaryKey(autoGenerate = true) + val id: Int = 0 +) \ No newline at end of file diff --git a/app/src/main/java/www/spikeysanju/jetquotes/navigation/JetQuotesMain.kt b/app/src/main/java/www/spikeysanju/jetquotes/navigation/JetQuotesMain.kt index 3689240..b0168ba 100644 --- a/app/src/main/java/www/spikeysanju/jetquotes/navigation/JetQuotesMain.kt +++ b/app/src/main/java/www/spikeysanju/jetquotes/navigation/JetQuotesMain.kt @@ -38,32 +38,46 @@ import androidx.navigation.compose.navArgument import androidx.navigation.compose.navigate import androidx.navigation.compose.rememberNavController import www.spikeysanju.jetquotes.view.details.DetailScreen +import www.spikeysanju.jetquotes.view.favourites.FavouritesScreen import www.spikeysanju.jetquotes.view.quotes.QuotesListScreen import www.spikeysanju.jetquotes.view.viewModel.MainViewModel +object EndPoints { + const val QUOTE = "quote" + const val AUTHOR = "author" +} + @Composable fun JetQuotesMain(viewModel: MainViewModel, toggleTheme: () -> Unit) { val navController = rememberNavController() val actions = remember(navController) { MainActions(navController) } NavHost(navController, startDestination = Screen.Home.route) { + // Quotes List composable(Screen.Home.route) { QuotesListScreen(viewModel, toggleTheme, actions) } + + // Quotes Details composable( "${Screen.Details.route}/{quote}/{author}", arguments = listOf( - navArgument("quote") { type = NavType.StringType }, - navArgument("author") { + navArgument(EndPoints.QUOTE) { type = NavType.StringType }, + navArgument(EndPoints.AUTHOR) { type = NavType.StringType }) ) { DetailScreen( actions.upPress, - it.arguments?.getString("quote") ?: "", - it.arguments?.getString("author") ?: "" + it.arguments?.getString(EndPoints.QUOTE) ?: "", + it.arguments?.getString(EndPoints.AUTHOR) ?: "" ) } + + // Favourites + composable(Screen.Favourites.route) { + FavouritesScreen(viewModel, actions.upPress) + } } } @@ -71,7 +85,12 @@ class MainActions(navController: NavHostController) { val upPress: () -> Unit = { navController.navigateUp() } - val quoteDetails: (String, String) -> Unit = { quote, author -> + + val gotoDetails: (String, String) -> Unit = { quote, author -> navController.navigate("${Screen.Details.route}/$quote/$author") } -} \ No newline at end of file + + val gotoFavourites: () -> Unit = { + navController.navigate(Screen.Favourites.route) + } +} diff --git a/app/src/main/java/www/spikeysanju/jetquotes/navigation/Screen.kt b/app/src/main/java/www/spikeysanju/jetquotes/navigation/Screen.kt index feb31bb..2fc3bc5 100644 --- a/app/src/main/java/www/spikeysanju/jetquotes/navigation/Screen.kt +++ b/app/src/main/java/www/spikeysanju/jetquotes/navigation/Screen.kt @@ -34,4 +34,5 @@ import www.spikeysanju.jetquotes.R sealed class Screen(val route: String, @StringRes val resourceId: Int) { object Home : Screen("quotes", R.string.quotes) object Details : Screen("details", R.string.details) + object Favourites : Screen("favourites", R.string.favourites) } \ No newline at end of file diff --git a/app/src/main/java/www/spikeysanju/jetquotes/repository/MainRepository.kt b/app/src/main/java/www/spikeysanju/jetquotes/repository/MainRepository.kt new file mode 100644 index 0000000..7d68a29 --- /dev/null +++ b/app/src/main/java/www/spikeysanju/jetquotes/repository/MainRepository.kt @@ -0,0 +1,48 @@ +/* + * + * * + * * * MIT License + * * * + * * * Copyright (c) 2020 Sanju S + * * * + * * * Permission is hereby granted, free of charge, to any person obtaining a copy + * * * of this software and associated documentation files (the "Software"), to deal + * * * in the Software without restriction, including without limitation the rights + * * * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * * * copies of the Software, and to permit persons to whom the Software is + * * * furnished to do so, subject to the following conditions: + * * * + * * * The above copyright notice and this permission notice shall be included in all + * * * copies or substantial portions of the Software. + * * * + * * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * * * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * * * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * * * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * * * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * * * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * * * SOFTWARE. + * * + * + */ + +package www.spikeysanju.jetquotes.repository + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.conflate +import kotlinx.coroutines.flow.flowOn +import www.spikeysanju.jetquotes.data.preference.db.FavouritesDao +import www.spikeysanju.jetquotes.model.Favourite +import javax.inject.Inject + +class MainRepository @Inject constructor(private val favouritesDao: FavouritesDao) { + + fun getAllFavourites(): Flow> = + favouritesDao.getAllFavourites().flowOn(Dispatchers.IO).conflate() + + suspend fun insert(favourite: Favourite) = favouritesDao.insertFavourite(favourite) + suspend fun update(favourite: Favourite) = favouritesDao.updateFavourite(favourite) + suspend fun deleteByID(id: Int) = favouritesDao.deleteByID(id) + +} \ No newline at end of file diff --git a/app/src/main/java/www/spikeysanju/jetquotes/utils/FavouriteViewState.kt b/app/src/main/java/www/spikeysanju/jetquotes/utils/FavouriteViewState.kt new file mode 100644 index 0000000..0b3e2ef --- /dev/null +++ b/app/src/main/java/www/spikeysanju/jetquotes/utils/FavouriteViewState.kt @@ -0,0 +1,41 @@ +/* + * + * * + * * * MIT License + * * * + * * * Copyright (c) 2020 Sanju S + * * * + * * * Permission is hereby granted, free of charge, to any person obtaining a copy + * * * of this software and associated documentation files (the "Software"), to deal + * * * in the Software without restriction, including without limitation the rights + * * * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * * * copies of the Software, and to permit persons to whom the Software is + * * * furnished to do so, subject to the following conditions: + * * * + * * * The above copyright notice and this permission notice shall be included in all + * * * copies or substantial portions of the Software. + * * * + * * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * * * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * * * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * * * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * * * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * * * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * * * SOFTWARE. + * * + * + */ + +package www.spikeysanju.jetquotes.utils + +import www.spikeysanju.jetquotes.model.Favourite + +sealed class FavouriteViewState { + + // Represents different states for quotes + object Empty : FavouriteViewState() + object Loading : FavouriteViewState() + data class Success(val quote: List) : FavouriteViewState() + data class Error(val exception: Throwable) : FavouriteViewState() + +} \ No newline at end of file diff --git a/app/src/main/java/www/spikeysanju/jetquotes/view/MainActivity.kt b/app/src/main/java/www/spikeysanju/jetquotes/view/MainActivity.kt index af00a92..5302a2f 100644 --- a/app/src/main/java/www/spikeysanju/jetquotes/view/MainActivity.kt +++ b/app/src/main/java/www/spikeysanju/jetquotes/view/MainActivity.kt @@ -39,6 +39,7 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.lifecycle.lifecycleScope import androidx.lifecycle.viewmodel.compose.viewModel +import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.InternalCoroutinesApi import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch @@ -47,6 +48,7 @@ import www.spikeysanju.jetquotes.ui.JetQuotesTheme import www.spikeysanju.jetquotes.view.viewModel.MainViewModel +@AndroidEntryPoint class MainActivity : AppCompatActivity() { private lateinit var viewModel: MainViewModel diff --git a/app/src/main/java/www/spikeysanju/jetquotes/view/favourites/FavouritesScreen.kt b/app/src/main/java/www/spikeysanju/jetquotes/view/favourites/FavouritesScreen.kt new file mode 100644 index 0000000..bbc0063 --- /dev/null +++ b/app/src/main/java/www/spikeysanju/jetquotes/view/favourites/FavouritesScreen.kt @@ -0,0 +1,93 @@ +/* + * + * * + * * * MIT License + * * * + * * * Copyright (c) 2020 Sanju S + * * * + * * * Permission is hereby granted, free of charge, to any person obtaining a copy + * * * of this software and associated documentation files (the "Software"), to deal + * * * in the Software without restriction, including without limitation the rights + * * * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * * * copies of the Software, and to permit persons to whom the Software is + * * * furnished to do so, subject to the following conditions: + * * * + * * * The above copyright notice and this permission notice shall be included in all + * * * copies or substantial portions of the Software. + * * * + * * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * * * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * * * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * * * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * * * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * * * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * * * SOFTWARE. + * * + * + */ + +package www.spikeysanju.jetquotes.view.favourites + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material.Icon +import androidx.compose.material.IconButton +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Scaffold +import androidx.compose.material.Text +import androidx.compose.material.TopAppBar +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import www.spikeysanju.jetquotes.R +import www.spikeysanju.jetquotes.utils.FavouriteViewState +import www.spikeysanju.jetquotes.view.viewModel.MainViewModel + +@Composable +fun FavouritesScreen(viewModel: MainViewModel, upPress: () -> Unit) { + Scaffold(topBar = { + TopAppBar( + title = { + Text( + text = "Favourites", + textAlign = TextAlign.Center, + modifier = Modifier + .fillMaxWidth() + .padding(end = 36.dp) + ) + }, + backgroundColor = MaterialTheme.colors.primary, + contentColor = MaterialTheme.colors.onPrimary, + navigationIcon = { + IconButton(onClick = upPress) { + Icon( + painter = painterResource(id = R.drawable.ic_back), + contentDescription = "Back Icon" + ) + } + }, + elevation = 0.dp + ) + }, content = { + // pass quote & author params to details card + when (val result = viewModel.favState.collectAsState().value) { + is FavouriteViewState.Empty -> { + } + is FavouriteViewState.Loading -> { + } + is FavouriteViewState.Success -> { + LazyColumn { + items(result.quote) { + Text(text = "${it.quote}") + } + } + } + } + + }) +} \ No newline at end of file diff --git a/app/src/main/java/www/spikeysanju/jetquotes/view/quotes/QuotesListScreen.kt b/app/src/main/java/www/spikeysanju/jetquotes/view/quotes/QuotesListScreen.kt index e97dd19..f149990 100644 --- a/app/src/main/java/www/spikeysanju/jetquotes/view/quotes/QuotesListScreen.kt +++ b/app/src/main/java/www/spikeysanju/jetquotes/view/quotes/QuotesListScreen.kt @@ -30,6 +30,7 @@ package www.spikeysanju.jetquotes.view.quotes import android.annotation.SuppressLint import android.widget.Toast +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material.MaterialTheme import androidx.compose.material.Scaffold @@ -60,7 +61,11 @@ fun QuotesListScreen( Text( text = "JetQuotes", textAlign = TextAlign.Center, - modifier = Modifier.fillMaxWidth() + modifier = Modifier + .fillMaxWidth() + .clickable { + actions.gotoFavourites + } ) }, backgroundColor = MaterialTheme.colors.primary, diff --git a/app/src/main/java/www/spikeysanju/jetquotes/view/viewModel/MainViewModel.kt b/app/src/main/java/www/spikeysanju/jetquotes/view/viewModel/MainViewModel.kt index 0478eb9..f637358 100644 --- a/app/src/main/java/www/spikeysanju/jetquotes/view/viewModel/MainViewModel.kt +++ b/app/src/main/java/www/spikeysanju/jetquotes/view/viewModel/MainViewModel.kt @@ -36,27 +36,38 @@ import com.squareup.moshi.JsonAdapter import com.squareup.moshi.Moshi import com.squareup.moshi.Types import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory +import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.launch import www.spikeysanju.jetquotes.data.preference.UIModeDataStore +import www.spikeysanju.jetquotes.model.Favourite import www.spikeysanju.jetquotes.model.Quote +import www.spikeysanju.jetquotes.repository.MainRepository +import www.spikeysanju.jetquotes.utils.FavouriteViewState import www.spikeysanju.jetquotes.utils.UIModeState import www.spikeysanju.jetquotes.utils.ViewState +import javax.inject.Inject -class MainViewModel(application: Application) : AndroidViewModel(application) { +@HiltViewModel +class MainViewModel @Inject constructor(application: Application, val repository: MainRepository) : + AndroidViewModel(application) { // init UIModeDataStore private val uiModeDataStore = UIModeDataStore(application) // Backing property to avoid state updates from other classes private val _uiState = MutableStateFlow(ViewState.Loading) + private val _favState = MutableStateFlow(FavouriteViewState.Loading) private val _uiModeState = MutableStateFlow(UIModeState.Default(false)) // UI collects from this StateFlow to get it's state update val uiState = _uiState.asStateFlow() val uiMode = _uiModeState.asStateFlow() + val favState = _favState.asStateFlow() // get all quotes from assets folder fun getAllQuotes(application: Application) = viewModelScope.launch { @@ -92,4 +103,32 @@ class MainViewModel(application: Application) : AndroidViewModel(application) { uiModeDataStore.saveToDataStore(isNightMode) } } + + // get all favourites + init { + viewModelScope.launch { + repository.getAllFavourites().distinctUntilChanged().collect { result -> + if (result.isNullOrEmpty()) { + _favState.value = FavouriteViewState.Empty + } else { + _favState.value = FavouriteViewState.Success(result) + } + } + } + } + + // insert favourite + fun insertFavourite(favourite: Favourite) = viewModelScope.launch { + repository.insert(favourite) + } + + // update favourite + fun updateFavourite(favourite: Favourite) = viewModelScope.launch { + repository.update(favourite) + } + + // delete favourite + fun deleteFavourite(id: Int) = viewModelScope.launch { + repository.deleteByID(id) + } } \ 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 93ad575..acae8a5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -32,4 +32,5 @@ Test Activity Quotes Details + Favourites \ No newline at end of file From 83d66eeb22b31f43624a9230db21dfc702311800 Mon Sep 17 00:00:00 2001 From: Sanju S Date: Sun, 2 May 2021 19:34:19 +0530 Subject: [PATCH 3/8] Add TopBar Signed-off-by: Spikeysanju --- .../jetquotes/components/CTAButtons.kt | 27 +++- .../jetquotes/components/DetailCard.kt | 14 +- .../jetquotes/components/QuotesCard.kt | 5 +- .../jetquotes/components/TopBarWithBack.kt | 132 ++++++++++++++++++ .../data/preference/db/FavouritesDao.kt | 18 +-- .../data/preference/db/JetQuotesDatabase.kt | 4 +- .../spikeysanju/jetquotes/model/Favourite.kt | 40 ------ .../www/spikeysanju/jetquotes/model/Quote.kt | 19 ++- .../jetquotes/navigation/JetQuotesMain.kt | 6 +- .../jetquotes/repository/MainRepository.kt | 10 +- .../jetquotes/utils/FavouriteViewState.kt | 4 +- .../jetquotes/utils/UIModeState.kt | 38 ----- .../jetquotes/view/details/DetailScreen.kt | 13 +- .../view/favourites/FavouritesScreen.kt | 13 +- .../jetquotes/view/quotes/QuotesListScreen.kt | 4 - .../jetquotes/view/viewModel/MainViewModel.kt | 31 ++-- app/src/main/res/drawable/ic_heart.xml | 41 ++++++ 17 files changed, 275 insertions(+), 144 deletions(-) create mode 100644 app/src/main/java/www/spikeysanju/jetquotes/components/TopBarWithBack.kt delete mode 100644 app/src/main/java/www/spikeysanju/jetquotes/model/Favourite.kt delete mode 100644 app/src/main/java/www/spikeysanju/jetquotes/utils/UIModeState.kt create mode 100644 app/src/main/res/drawable/ic_heart.xml diff --git a/app/src/main/java/www/spikeysanju/jetquotes/components/CTAButtons.kt b/app/src/main/java/www/spikeysanju/jetquotes/components/CTAButtons.kt index 66175f0..9473239 100644 --- a/app/src/main/java/www/spikeysanju/jetquotes/components/CTAButtons.kt +++ b/app/src/main/java/www/spikeysanju/jetquotes/components/CTAButtons.kt @@ -31,7 +31,12 @@ package www.spikeysanju.jetquotes.components import android.widget.Toast import androidx.compose.foundation.background import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width import androidx.compose.material.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -40,19 +45,35 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp import www.spikeysanju.jetquotes.R +import www.spikeysanju.jetquotes.model.Quote import www.spikeysanju.jetquotes.utils.copyToClipboard import www.spikeysanju.jetquotes.utils.shareToOthers +import www.spikeysanju.jetquotes.view.viewModel.MainViewModel @Composable -fun CTAButtons(quote: String, author: String) { +fun CTAButtons(viewModel: MainViewModel, quote: String, author: String) { val context = LocalContext.current Box(modifier = Modifier.fillMaxSize()) { Row( - modifier = Modifier.background(MaterialTheme.colors.primaryVariant) + modifier = Modifier + .background(MaterialTheme.colors.primaryVariant) .align(Alignment.BottomEnd) .padding(30.dp, 30.dp, 0.dp, 30.dp) ) { + + Button( + icon = painterResource(id = R.drawable.ic_heart), + name = "FAVOURITE", + modifier = Modifier.clickable(onClick = { + val quotes = Quote(quote, author) + viewModel.insertFavourite(quotes) + Toast.makeText(context, "Added to Favourites!", Toast.LENGTH_SHORT).show() + }) + ) + + Spacer(modifier = Modifier.width(30.dp)) + Button( icon = painterResource(id = R.drawable.ic_copy), name = "COPY", diff --git a/app/src/main/java/www/spikeysanju/jetquotes/components/DetailCard.kt b/app/src/main/java/www/spikeysanju/jetquotes/components/DetailCard.kt index 63b17f0..650e533 100644 --- a/app/src/main/java/www/spikeysanju/jetquotes/components/DetailCard.kt +++ b/app/src/main/java/www/spikeysanju/jetquotes/components/DetailCard.kt @@ -37,6 +37,7 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.material.MaterialTheme import androidx.compose.material.MaterialTheme.typography import androidx.compose.material.Text @@ -47,9 +48,10 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import www.spikeysanju.jetquotes.utils.copyToClipboard +import www.spikeysanju.jetquotes.view.viewModel.MainViewModel @Composable -fun DetailCard(quote: String, author: String) { +fun DetailCard(viewModel: MainViewModel, quote: String, author: String) { val context = LocalContext.current Box(modifier = Modifier.fillMaxSize()) { @@ -73,7 +75,9 @@ fun DetailCard(quote: String, author: String) { ) { Text( - modifier = Modifier.align(Alignment.CenterHorizontally), + modifier = Modifier + .align(Alignment.CenterHorizontally) + .wrapContentSize(align = Alignment.Center), text = """ " """, style = typography.h4, color = MaterialTheme.colors.onBackground, @@ -83,7 +87,9 @@ fun DetailCard(quote: String, author: String) { Spacer(Modifier.height(16.dp)) Text( - modifier = Modifier.align(Alignment.CenterHorizontally), + modifier = Modifier + .align(Alignment.CenterHorizontally) + .wrapContentSize(align = Alignment.Center), text = quote.ifBlank { " No Quotes found" }, style = typography.h5, color = MaterialTheme.colors.onBackground, @@ -100,7 +106,7 @@ fun DetailCard(quote: String, author: String) { textAlign = TextAlign.Center ) } - CTAButtons(quote, author) + CTAButtons(viewModel, quote, author) } } diff --git a/app/src/main/java/www/spikeysanju/jetquotes/components/QuotesCard.kt b/app/src/main/java/www/spikeysanju/jetquotes/components/QuotesCard.kt index 176c970..fdc263c 100644 --- a/app/src/main/java/www/spikeysanju/jetquotes/components/QuotesCard.kt +++ b/app/src/main/java/www/spikeysanju/jetquotes/components/QuotesCard.kt @@ -54,7 +54,8 @@ fun QuotesCard(quote: Quote, actions: MainActions) { .wrapContentSize() .padding(12.dp) .clickable(onClick = { - actions.gotoDetails(quote.quote!!, quote.author!!) + actions.gotoDetails(quote.quote, quote.author!!) + }) .background(MaterialTheme.colors.primaryVariant) .padding(12.dp)) { @@ -66,7 +67,7 @@ fun QuotesCard(quote: Quote, actions: MainActions) { ) Text( - text = quote.quote.toString(), + text = quote.quote, style = typography.body1, color = MaterialTheme.colors.onBackground, modifier = Modifier.padding(12.dp, 0.dp, 0.dp, 0.dp) diff --git a/app/src/main/java/www/spikeysanju/jetquotes/components/TopBarWithBack.kt b/app/src/main/java/www/spikeysanju/jetquotes/components/TopBarWithBack.kt new file mode 100644 index 0000000..18401b1 --- /dev/null +++ b/app/src/main/java/www/spikeysanju/jetquotes/components/TopBarWithBack.kt @@ -0,0 +1,132 @@ +/* + * + * * + * * * MIT License + * * * + * * * Copyright (c) 2020 Sanju S + * * * + * * * Permission is hereby granted, free of charge, to any person obtaining a copy + * * * of this software and associated documentation files (the "Software"), to deal + * * * in the Software without restriction, including without limitation the rights + * * * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * * * copies of the Software, and to permit persons to whom the Software is + * * * furnished to do so, subject to the following conditions: + * * * + * * * The above copyright notice and this permission notice shall be included in all + * * * copies or substantial portions of the Software. + * * * + * * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * * * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * * * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * * * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * * * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * * * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * * * SOFTWARE. + * * + * + */ + +package www.spikeysanju.jetquotes.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.material.Icon +import androidx.compose.material.IconButton +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.dp +import www.spikeysanju.jetquotes.R +import www.spikeysanju.jetquotes.ui.typography + + +@Composable +fun TopBar(title: String, onToggle: () -> Unit, onFavouritesClick: () -> Unit) { + Box( + modifier = Modifier + .fillMaxWidth() + .height(70.dp) + .background(MaterialTheme.colors.background), contentAlignment = Alignment.CenterStart + ) { + Row( + modifier = Modifier.fillMaxWidth(), + Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = title, + style = typography.h5, + color = MaterialTheme.colors.secondaryVariant, + modifier = Modifier.padding(start = 16.dp) + ) + Row(modifier = Modifier.wrapContentSize(), Arrangement.End) { + + IconButton( + onClick = { onFavouritesClick() }, + modifier = Modifier.padding(end = 16.dp) + ) { + val settingsIcon: Painter = painterResource(id = R.drawable.ic_heart) + Icon( + painter = settingsIcon, + contentDescription = "Favourites Icon", + tint = MaterialTheme.colors.secondaryVariant + ) + } + + IconButton(onClick = { onToggle() }, modifier = Modifier.padding(end = 16.dp)) { + val toggleIcon = if (isSystemInDarkTheme()) + painterResource(id = R.drawable.ic_night) + else + painterResource(id = R.drawable.ic_day) + Icon( + painter = toggleIcon, + contentDescription = "Day/Night Icon", + tint = MaterialTheme.colors.secondaryVariant + ) + } + + } + + } + } +} + + +@Composable +fun TopBarWithBack(title: String, onBackClick: () -> Unit) { + Row( + modifier = Modifier.fillMaxWidth(), + Arrangement.Start, + verticalAlignment = Alignment.CenterVertically + ) { + IconButton(onClick = { onBackClick() }, modifier = Modifier.padding(start = 16.dp)) { + Icon( + imageVector = Icons.Default.ArrowBack, + contentDescription = "Back Icon", + tint = MaterialTheme.colors.secondaryVariant + ) + } + + Text( + text = title, + style = typography.h6, + color = MaterialTheme.colors.secondaryVariant, + modifier = Modifier.padding(start = 16.dp) + ) + + } + +} \ No newline at end of file diff --git a/app/src/main/java/www/spikeysanju/jetquotes/data/preference/db/FavouritesDao.kt b/app/src/main/java/www/spikeysanju/jetquotes/data/preference/db/FavouritesDao.kt index 32e6820..43b936a 100644 --- a/app/src/main/java/www/spikeysanju/jetquotes/data/preference/db/FavouritesDao.kt +++ b/app/src/main/java/www/spikeysanju/jetquotes/data/preference/db/FavouritesDao.kt @@ -29,25 +29,27 @@ package www.spikeysanju.jetquotes.data.preference.db 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 -import www.spikeysanju.jetquotes.model.Favourite +import www.spikeysanju.jetquotes.model.Quote @Dao interface FavouritesDao { @Query("SELECT * FROM favourites") - fun getAllFavourites(): Flow> + fun getAllFavourites(): Flow> - @Insert - suspend fun insertFavourite(favourite: Favourite) + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insertFavourite(quote: Quote) - @Query("DELETE FROM favourites where id=:id") - suspend fun deleteByID(id: Int) + @Delete + suspend fun delete(quote: Quote) - @Update - suspend fun updateFavourite(favourite: Favourite) + @Update(onConflict = OnConflictStrategy.REPLACE) + suspend fun updateFavourite(quote: Quote) } \ No newline at end of file diff --git a/app/src/main/java/www/spikeysanju/jetquotes/data/preference/db/JetQuotesDatabase.kt b/app/src/main/java/www/spikeysanju/jetquotes/data/preference/db/JetQuotesDatabase.kt index 5d90485..ee0e0e4 100644 --- a/app/src/main/java/www/spikeysanju/jetquotes/data/preference/db/JetQuotesDatabase.kt +++ b/app/src/main/java/www/spikeysanju/jetquotes/data/preference/db/JetQuotesDatabase.kt @@ -30,9 +30,9 @@ package www.spikeysanju.jetquotes.data.preference.db import androidx.room.Database import androidx.room.RoomDatabase -import www.spikeysanju.jetquotes.model.Favourite +import www.spikeysanju.jetquotes.model.Quote -@Database(entities = [Favourite::class], version = 1) +@Database(entities = [Quote::class], version = 1) abstract class JetQuotesDatabase : RoomDatabase() { abstract fun getFavouritesDao(): FavouritesDao } \ No newline at end of file diff --git a/app/src/main/java/www/spikeysanju/jetquotes/model/Favourite.kt b/app/src/main/java/www/spikeysanju/jetquotes/model/Favourite.kt deleted file mode 100644 index 0df0119..0000000 --- a/app/src/main/java/www/spikeysanju/jetquotes/model/Favourite.kt +++ /dev/null @@ -1,40 +0,0 @@ -/* - * - * * - * * * MIT License - * * * - * * * Copyright (c) 2020 Sanju S - * * * - * * * Permission is hereby granted, free of charge, to any person obtaining a copy - * * * of this software and associated documentation files (the "Software"), to deal - * * * in the Software without restriction, including without limitation the rights - * * * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * * * copies of the Software, and to permit persons to whom the Software is - * * * furnished to do so, subject to the following conditions: - * * * - * * * The above copyright notice and this permission notice shall be included in all - * * * copies or substantial portions of the Software. - * * * - * * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * * * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * * * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * * * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * * * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * * * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * * * SOFTWARE. - * * - * - */ - -package www.spikeysanju.jetquotes.model - -import androidx.room.Entity -import androidx.room.PrimaryKey - -@Entity(tableName = "favourites") -data class Favourite( - val quote: String? = "", - val author: String? = "", - @PrimaryKey(autoGenerate = true) - val id: Int = 0 -) \ No newline at end of file diff --git a/app/src/main/java/www/spikeysanju/jetquotes/model/Quote.kt b/app/src/main/java/www/spikeysanju/jetquotes/model/Quote.kt index 65b9c0a..531a09d 100644 --- a/app/src/main/java/www/spikeysanju/jetquotes/model/Quote.kt +++ b/app/src/main/java/www/spikeysanju/jetquotes/model/Quote.kt @@ -28,11 +28,22 @@ package www.spikeysanju.jetquotes.model +import androidx.annotation.NonNull +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey import com.squareup.moshi.Json +@Entity(tableName = "favourites") data class Quote( - @Json(name = "quoteText") - val quote: String? = null, - @Json(name = "quoteAuthor") - val author: String? = null + + @NonNull + @PrimaryKey + @ColumnInfo(name = "quote") + @Json(name = "quoteText") + val quote: String = "", + + @ColumnInfo(name = "author") + @Json(name = "quoteAuthor") + val author: String? = "" ) \ No newline at end of file diff --git a/app/src/main/java/www/spikeysanju/jetquotes/navigation/JetQuotesMain.kt b/app/src/main/java/www/spikeysanju/jetquotes/navigation/JetQuotesMain.kt index b0168ba..7c549e3 100644 --- a/app/src/main/java/www/spikeysanju/jetquotes/navigation/JetQuotesMain.kt +++ b/app/src/main/java/www/spikeysanju/jetquotes/navigation/JetQuotesMain.kt @@ -43,6 +43,7 @@ import www.spikeysanju.jetquotes.view.quotes.QuotesListScreen import www.spikeysanju.jetquotes.view.viewModel.MainViewModel object EndPoints { + const val ID = "id" const val QUOTE = "quote" const val AUTHOR = "author" } @@ -52,7 +53,7 @@ fun JetQuotesMain(viewModel: MainViewModel, toggleTheme: () -> Unit) { val navController = rememberNavController() val actions = remember(navController) { MainActions(navController) } - NavHost(navController, startDestination = Screen.Home.route) { + NavHost(navController, startDestination = Screen.Favourites.route) { // Quotes List composable(Screen.Home.route) { QuotesListScreen(viewModel, toggleTheme, actions) @@ -68,6 +69,7 @@ fun JetQuotesMain(viewModel: MainViewModel, toggleTheme: () -> Unit) { }) ) { DetailScreen( + viewModel, actions.upPress, it.arguments?.getString(EndPoints.QUOTE) ?: "", it.arguments?.getString(EndPoints.AUTHOR) ?: "" @@ -76,7 +78,7 @@ fun JetQuotesMain(viewModel: MainViewModel, toggleTheme: () -> Unit) { // Favourites composable(Screen.Favourites.route) { - FavouritesScreen(viewModel, actions.upPress) + FavouritesScreen(viewModel, actions) } } } diff --git a/app/src/main/java/www/spikeysanju/jetquotes/repository/MainRepository.kt b/app/src/main/java/www/spikeysanju/jetquotes/repository/MainRepository.kt index 7d68a29..5dd40f9 100644 --- a/app/src/main/java/www/spikeysanju/jetquotes/repository/MainRepository.kt +++ b/app/src/main/java/www/spikeysanju/jetquotes/repository/MainRepository.kt @@ -33,16 +33,16 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.flow.flowOn import www.spikeysanju.jetquotes.data.preference.db.FavouritesDao -import www.spikeysanju.jetquotes.model.Favourite +import www.spikeysanju.jetquotes.model.Quote import javax.inject.Inject class MainRepository @Inject constructor(private val favouritesDao: FavouritesDao) { - fun getAllFavourites(): Flow> = + fun getAllFavourites(): Flow> = favouritesDao.getAllFavourites().flowOn(Dispatchers.IO).conflate() - suspend fun insert(favourite: Favourite) = favouritesDao.insertFavourite(favourite) - suspend fun update(favourite: Favourite) = favouritesDao.updateFavourite(favourite) - suspend fun deleteByID(id: Int) = favouritesDao.deleteByID(id) + suspend fun insert(quote: Quote) = favouritesDao.insertFavourite(quote) + suspend fun update(quote: Quote) = favouritesDao.updateFavourite(quote) + suspend fun delete(quote: Quote) = favouritesDao.delete(quote = quote) } \ No newline at end of file diff --git a/app/src/main/java/www/spikeysanju/jetquotes/utils/FavouriteViewState.kt b/app/src/main/java/www/spikeysanju/jetquotes/utils/FavouriteViewState.kt index 0b3e2ef..027560c 100644 --- a/app/src/main/java/www/spikeysanju/jetquotes/utils/FavouriteViewState.kt +++ b/app/src/main/java/www/spikeysanju/jetquotes/utils/FavouriteViewState.kt @@ -28,14 +28,14 @@ package www.spikeysanju.jetquotes.utils -import www.spikeysanju.jetquotes.model.Favourite +import www.spikeysanju.jetquotes.model.Quote sealed class FavouriteViewState { // Represents different states for quotes object Empty : FavouriteViewState() object Loading : FavouriteViewState() - data class Success(val quote: List) : FavouriteViewState() + data class Success(val quote: List) : FavouriteViewState() data class Error(val exception: Throwable) : FavouriteViewState() } \ No newline at end of file diff --git a/app/src/main/java/www/spikeysanju/jetquotes/utils/UIModeState.kt b/app/src/main/java/www/spikeysanju/jetquotes/utils/UIModeState.kt deleted file mode 100644 index 7d8aa44..0000000 --- a/app/src/main/java/www/spikeysanju/jetquotes/utils/UIModeState.kt +++ /dev/null @@ -1,38 +0,0 @@ -/* - * - * * - * * * MIT License - * * * - * * * Copyright (c) 2020 Sanju S - * * * - * * * Permission is hereby granted, free of charge, to any person obtaining a copy - * * * of this software and associated documentation files (the "Software"), to deal - * * * in the Software without restriction, including without limitation the rights - * * * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * * * copies of the Software, and to permit persons to whom the Software is - * * * furnished to do so, subject to the following conditions: - * * * - * * * The above copyright notice and this permission notice shall be included in all - * * * copies or substantial portions of the Software. - * * * - * * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * * * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * * * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * * * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * * * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * * * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * * * SOFTWARE. - * * - * - */ - -package www.spikeysanju.jetquotes.utils - -import kotlinx.coroutines.flow.Flow - -sealed class UIModeState { - // Represents different states for the LatestNews screen - data class Default(val boolean: Boolean) : UIModeState() - data class Success(val isNightMode: Flow) : UIModeState() - data class Error(val exception: Throwable) : UIModeState() -} \ No newline at end of file diff --git a/app/src/main/java/www/spikeysanju/jetquotes/view/details/DetailScreen.kt b/app/src/main/java/www/spikeysanju/jetquotes/view/details/DetailScreen.kt index 20abb5e..89b2c02 100644 --- a/app/src/main/java/www/spikeysanju/jetquotes/view/details/DetailScreen.kt +++ b/app/src/main/java/www/spikeysanju/jetquotes/view/details/DetailScreen.kt @@ -43,9 +43,15 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import www.spikeysanju.jetquotes.R import www.spikeysanju.jetquotes.components.DetailCard +import www.spikeysanju.jetquotes.view.viewModel.MainViewModel @Composable -fun DetailScreen(upPress: () -> Unit, quote: String, author: String) { +fun DetailScreen( + viewModel: MainViewModel, + upPress: () -> Unit, + quote: String, + author: String +) { Scaffold(topBar = { TopAppBar( title = { @@ -72,8 +78,9 @@ fun DetailScreen(upPress: () -> Unit, quote: String, author: String) { }, content = { // pass quote & author params to details card DetailCard( - quote = quote, - author = author + viewModel, + quote, + author ) }) } \ No newline at end of file diff --git a/app/src/main/java/www/spikeysanju/jetquotes/view/favourites/FavouritesScreen.kt b/app/src/main/java/www/spikeysanju/jetquotes/view/favourites/FavouritesScreen.kt index bbc0063..6bcfe53 100644 --- a/app/src/main/java/www/spikeysanju/jetquotes/view/favourites/FavouritesScreen.kt +++ b/app/src/main/java/www/spikeysanju/jetquotes/view/favourites/FavouritesScreen.kt @@ -45,11 +45,13 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import www.spikeysanju.jetquotes.R +import www.spikeysanju.jetquotes.components.QuotesCard +import www.spikeysanju.jetquotes.navigation.MainActions import www.spikeysanju.jetquotes.utils.FavouriteViewState import www.spikeysanju.jetquotes.view.viewModel.MainViewModel @Composable -fun FavouritesScreen(viewModel: MainViewModel, upPress: () -> Unit) { +fun FavouritesScreen(viewModel: MainViewModel, actions: MainActions) { Scaffold(topBar = { TopAppBar( title = { @@ -64,7 +66,7 @@ fun FavouritesScreen(viewModel: MainViewModel, upPress: () -> Unit) { backgroundColor = MaterialTheme.colors.primary, contentColor = MaterialTheme.colors.onPrimary, navigationIcon = { - IconButton(onClick = upPress) { + IconButton(onClick = actions.upPress) { Icon( painter = painterResource(id = R.drawable.ic_back), contentDescription = "Back Icon" @@ -77,13 +79,12 @@ fun FavouritesScreen(viewModel: MainViewModel, upPress: () -> Unit) { // pass quote & author params to details card when (val result = viewModel.favState.collectAsState().value) { is FavouriteViewState.Empty -> { - } - is FavouriteViewState.Loading -> { + Text(text = "Empty List") } is FavouriteViewState.Success -> { LazyColumn { - items(result.quote) { - Text(text = "${it.quote}") + items(result.quote) { quote -> + QuotesCard(quote = quote, actions = actions) } } } diff --git a/app/src/main/java/www/spikeysanju/jetquotes/view/quotes/QuotesListScreen.kt b/app/src/main/java/www/spikeysanju/jetquotes/view/quotes/QuotesListScreen.kt index f149990..f1f3326 100644 --- a/app/src/main/java/www/spikeysanju/jetquotes/view/quotes/QuotesListScreen.kt +++ b/app/src/main/java/www/spikeysanju/jetquotes/view/quotes/QuotesListScreen.kt @@ -30,7 +30,6 @@ package www.spikeysanju.jetquotes.view.quotes import android.annotation.SuppressLint import android.widget.Toast -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material.MaterialTheme import androidx.compose.material.Scaffold @@ -63,9 +62,6 @@ fun QuotesListScreen( textAlign = TextAlign.Center, modifier = Modifier .fillMaxWidth() - .clickable { - actions.gotoFavourites - } ) }, backgroundColor = MaterialTheme.colors.primary, diff --git a/app/src/main/java/www/spikeysanju/jetquotes/view/viewModel/MainViewModel.kt b/app/src/main/java/www/spikeysanju/jetquotes/view/viewModel/MainViewModel.kt index f637358..2746bf1 100644 --- a/app/src/main/java/www/spikeysanju/jetquotes/view/viewModel/MainViewModel.kt +++ b/app/src/main/java/www/spikeysanju/jetquotes/view/viewModel/MainViewModel.kt @@ -29,7 +29,6 @@ package www.spikeysanju.jetquotes.view.viewModel import android.app.Application -import android.util.Log import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.viewModelScope import com.squareup.moshi.JsonAdapter @@ -44,16 +43,17 @@ import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.launch import www.spikeysanju.jetquotes.data.preference.UIModeDataStore -import www.spikeysanju.jetquotes.model.Favourite import www.spikeysanju.jetquotes.model.Quote import www.spikeysanju.jetquotes.repository.MainRepository import www.spikeysanju.jetquotes.utils.FavouriteViewState -import www.spikeysanju.jetquotes.utils.UIModeState import www.spikeysanju.jetquotes.utils.ViewState import javax.inject.Inject @HiltViewModel -class MainViewModel @Inject constructor(application: Application, val repository: MainRepository) : +class MainViewModel @Inject constructor( + application: Application, + private val repository: MainRepository +) : AndroidViewModel(application) { // init UIModeDataStore @@ -62,11 +62,9 @@ class MainViewModel @Inject constructor(application: Application, val repository // Backing property to avoid state updates from other classes private val _uiState = MutableStateFlow(ViewState.Loading) private val _favState = MutableStateFlow(FavouriteViewState.Loading) - private val _uiModeState = MutableStateFlow(UIModeState.Default(false)) // UI collects from this StateFlow to get it's state update val uiState = _uiState.asStateFlow() - val uiMode = _uiModeState.asStateFlow() val favState = _favState.asStateFlow() // get all quotes from assets folder @@ -88,15 +86,6 @@ class MainViewModel @Inject constructor(application: Application, val repository // get ui mode val getUIMode = uiModeDataStore.uiMode - fun fetchUIMode() = viewModelScope.launch { - try { - _uiModeState.value = UIModeState.Success(uiModeDataStore.uiMode) - } catch (e: Exception) { - Log.i("Error occurred", "${e.printStackTrace()}") - _uiModeState.value = UIModeState.Error(e) - } - } - // save ui mode fun setUIMode(isNightMode: Boolean) { viewModelScope.launch(IO) { @@ -118,17 +107,17 @@ class MainViewModel @Inject constructor(application: Application, val repository } // insert favourite - fun insertFavourite(favourite: Favourite) = viewModelScope.launch { - repository.insert(favourite) + fun insertFavourite(quote: Quote) = viewModelScope.launch { + repository.insert(quote) } // update favourite - fun updateFavourite(favourite: Favourite) = viewModelScope.launch { - repository.update(favourite) + fun updateFavourite(quote: Quote) = viewModelScope.launch { + repository.update(quote) } // delete favourite - fun deleteFavourite(id: Int) = viewModelScope.launch { - repository.deleteByID(id) + fun deleteFavourite(quote: Quote) = viewModelScope.launch { + repository.delete(quote) } } \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_heart.xml b/app/src/main/res/drawable/ic_heart.xml new file mode 100644 index 0000000..0bd7a9d --- /dev/null +++ b/app/src/main/res/drawable/ic_heart.xml @@ -0,0 +1,41 @@ + + + + + From 04aea89093f324d90b393eefbc07ab9382a1e694 Mon Sep 17 00:00:00 2001 From: Sanju S Date: Sun, 2 May 2021 20:06:41 +0530 Subject: [PATCH 4/8] Complete Add to Favourites #24 Signed-off-by: Spikeysanju --- .idea/misc.xml | 2 + .../jetquotes/components/TopBarWithBack.kt | 10 ++--- .../jetquotes/navigation/JetQuotesMain.kt | 2 +- .../jetquotes/view/details/DetailScreen.kt | 37 ++---------------- .../view/favourites/FavouritesScreen.kt | 39 +++---------------- .../jetquotes/view/quotes/QuotesListScreen.kt | 28 +++---------- app/src/main/res/drawable/ic_day.xml | 36 +++++++++++++++-- 7 files changed, 55 insertions(+), 99 deletions(-) diff --git a/.idea/misc.xml b/.idea/misc.xml index cdc90c7..11c923c 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -4,6 +4,8 @@ diff --git a/app/src/main/java/www/spikeysanju/jetquotes/components/TopBarWithBack.kt b/app/src/main/java/www/spikeysanju/jetquotes/components/TopBarWithBack.kt index 18401b1..1befba6 100644 --- a/app/src/main/java/www/spikeysanju/jetquotes/components/TopBarWithBack.kt +++ b/app/src/main/java/www/spikeysanju/jetquotes/components/TopBarWithBack.kt @@ -69,7 +69,7 @@ fun TopBar(title: String, onToggle: () -> Unit, onFavouritesClick: () -> Unit) { Text( text = title, style = typography.h5, - color = MaterialTheme.colors.secondaryVariant, + color = MaterialTheme.colors.onPrimary, modifier = Modifier.padding(start = 16.dp) ) Row(modifier = Modifier.wrapContentSize(), Arrangement.End) { @@ -82,7 +82,7 @@ fun TopBar(title: String, onToggle: () -> Unit, onFavouritesClick: () -> Unit) { Icon( painter = settingsIcon, contentDescription = "Favourites Icon", - tint = MaterialTheme.colors.secondaryVariant + tint = MaterialTheme.colors.onPrimary ) } @@ -94,7 +94,7 @@ fun TopBar(title: String, onToggle: () -> Unit, onFavouritesClick: () -> Unit) { Icon( painter = toggleIcon, contentDescription = "Day/Night Icon", - tint = MaterialTheme.colors.secondaryVariant + tint = MaterialTheme.colors.onPrimary ) } @@ -116,14 +116,14 @@ fun TopBarWithBack(title: String, onBackClick: () -> Unit) { Icon( imageVector = Icons.Default.ArrowBack, contentDescription = "Back Icon", - tint = MaterialTheme.colors.secondaryVariant + tint = MaterialTheme.colors.onPrimary ) } Text( text = title, style = typography.h6, - color = MaterialTheme.colors.secondaryVariant, + color = MaterialTheme.colors.onPrimary, modifier = Modifier.padding(start = 16.dp) ) diff --git a/app/src/main/java/www/spikeysanju/jetquotes/navigation/JetQuotesMain.kt b/app/src/main/java/www/spikeysanju/jetquotes/navigation/JetQuotesMain.kt index 7c549e3..6d0acad 100644 --- a/app/src/main/java/www/spikeysanju/jetquotes/navigation/JetQuotesMain.kt +++ b/app/src/main/java/www/spikeysanju/jetquotes/navigation/JetQuotesMain.kt @@ -53,7 +53,7 @@ fun JetQuotesMain(viewModel: MainViewModel, toggleTheme: () -> Unit) { val navController = rememberNavController() val actions = remember(navController) { MainActions(navController) } - NavHost(navController, startDestination = Screen.Favourites.route) { + NavHost(navController, startDestination = Screen.Home.route) { // Quotes List composable(Screen.Home.route) { QuotesListScreen(viewModel, toggleTheme, actions) diff --git a/app/src/main/java/www/spikeysanju/jetquotes/view/details/DetailScreen.kt b/app/src/main/java/www/spikeysanju/jetquotes/view/details/DetailScreen.kt index 89b2c02..711736c 100644 --- a/app/src/main/java/www/spikeysanju/jetquotes/view/details/DetailScreen.kt +++ b/app/src/main/java/www/spikeysanju/jetquotes/view/details/DetailScreen.kt @@ -28,21 +28,10 @@ package www.spikeysanju.jetquotes.view.details -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.material.Icon -import androidx.compose.material.IconButton -import androidx.compose.material.MaterialTheme import androidx.compose.material.Scaffold -import androidx.compose.material.Text -import androidx.compose.material.TopAppBar import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.dp -import www.spikeysanju.jetquotes.R import www.spikeysanju.jetquotes.components.DetailCard +import www.spikeysanju.jetquotes.components.TopBarWithBack import www.spikeysanju.jetquotes.view.viewModel.MainViewModel @Composable @@ -53,27 +42,9 @@ fun DetailScreen( author: String ) { Scaffold(topBar = { - TopAppBar( - title = { - Text( - text = "Details", - textAlign = TextAlign.Center, - modifier = Modifier - .fillMaxWidth() - .padding(end = 36.dp) - ) - }, - backgroundColor = MaterialTheme.colors.primary, - contentColor = MaterialTheme.colors.onPrimary, - navigationIcon = { - IconButton(onClick = upPress) { - Icon( - painter = painterResource(id = R.drawable.ic_back), - contentDescription = "Back Icon" - ) - } - }, - elevation = 0.dp + TopBarWithBack( + title = "Details", + onBackClick = { upPress() }, ) }, content = { // pass quote & author params to details card diff --git a/app/src/main/java/www/spikeysanju/jetquotes/view/favourites/FavouritesScreen.kt b/app/src/main/java/www/spikeysanju/jetquotes/view/favourites/FavouritesScreen.kt index 6bcfe53..fb95d7e 100644 --- a/app/src/main/java/www/spikeysanju/jetquotes/view/favourites/FavouritesScreen.kt +++ b/app/src/main/java/www/spikeysanju/jetquotes/view/favourites/FavouritesScreen.kt @@ -28,24 +28,14 @@ package www.spikeysanju.jetquotes.view.favourites -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items -import androidx.compose.material.Icon -import androidx.compose.material.IconButton -import androidx.compose.material.MaterialTheme import androidx.compose.material.Scaffold import androidx.compose.material.Text -import androidx.compose.material.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.dp -import www.spikeysanju.jetquotes.R import www.spikeysanju.jetquotes.components.QuotesCard +import www.spikeysanju.jetquotes.components.TopBarWithBack import www.spikeysanju.jetquotes.navigation.MainActions import www.spikeysanju.jetquotes.utils.FavouriteViewState import www.spikeysanju.jetquotes.view.viewModel.MainViewModel @@ -53,28 +43,11 @@ import www.spikeysanju.jetquotes.view.viewModel.MainViewModel @Composable fun FavouritesScreen(viewModel: MainViewModel, actions: MainActions) { Scaffold(topBar = { - TopAppBar( - title = { - Text( - text = "Favourites", - textAlign = TextAlign.Center, - modifier = Modifier - .fillMaxWidth() - .padding(end = 36.dp) - ) - }, - backgroundColor = MaterialTheme.colors.primary, - contentColor = MaterialTheme.colors.onPrimary, - navigationIcon = { - IconButton(onClick = actions.upPress) { - Icon( - painter = painterResource(id = R.drawable.ic_back), - contentDescription = "Back Icon" - ) - } - }, - elevation = 0.dp - ) + + TopBarWithBack( + title = "JetQuotes", + onBackClick = { actions.upPress() }) + }, content = { // pass quote & author params to details card when (val result = viewModel.favState.collectAsState().value) { diff --git a/app/src/main/java/www/spikeysanju/jetquotes/view/quotes/QuotesListScreen.kt b/app/src/main/java/www/spikeysanju/jetquotes/view/quotes/QuotesListScreen.kt index f1f3326..1f210a9 100644 --- a/app/src/main/java/www/spikeysanju/jetquotes/view/quotes/QuotesListScreen.kt +++ b/app/src/main/java/www/spikeysanju/jetquotes/view/quotes/QuotesListScreen.kt @@ -30,19 +30,12 @@ package www.spikeysanju.jetquotes.view.quotes import android.annotation.SuppressLint import android.widget.Toast -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.material.MaterialTheme import androidx.compose.material.Scaffold -import androidx.compose.material.Text -import androidx.compose.material.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState -import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.dp import www.spikeysanju.jetquotes.components.QuotesList -import www.spikeysanju.jetquotes.components.QuotesThemeSwitch +import www.spikeysanju.jetquotes.components.TopBar import www.spikeysanju.jetquotes.navigation.MainActions import www.spikeysanju.jetquotes.utils.ViewState import www.spikeysanju.jetquotes.view.viewModel.MainViewModel @@ -55,21 +48,10 @@ fun QuotesListScreen( actions: MainActions, ) { Scaffold(topBar = { - TopAppBar( - title = { - Text( - text = "JetQuotes", - textAlign = TextAlign.Center, - modifier = Modifier - .fillMaxWidth() - ) - }, - backgroundColor = MaterialTheme.colors.primary, - contentColor = MaterialTheme.colors.onPrimary, - elevation = 0.dp, - actions = { - QuotesThemeSwitch(toggleTheme) - } + TopBar( + title = "JetQuotes", + onToggle = { toggleTheme() }, + onFavouritesClick = actions.gotoFavourites ) }, content = { val context = LocalContext.current diff --git a/app/src/main/res/drawable/ic_day.xml b/app/src/main/res/drawable/ic_day.xml index 2ac8239..658b4e4 100644 --- a/app/src/main/res/drawable/ic_day.xml +++ b/app/src/main/res/drawable/ic_day.xml @@ -1,8 +1,36 @@ + + + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> Date: Tue, 4 May 2021 09:37:32 +0530 Subject: [PATCH 5/8] Add Swipe to Delete #24 Signed-off-by: Spikeysanju --- .../www/spikeysanju/jetquotes/ui/Color.kt | 1 + .../view/favourites/FavouritesScreen.kt | 100 +++++++++++++++++- 2 files changed, 98 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/www/spikeysanju/jetquotes/ui/Color.kt b/app/src/main/java/www/spikeysanju/jetquotes/ui/Color.kt index 32064b1..be3d324 100644 --- a/app/src/main/java/www/spikeysanju/jetquotes/ui/Color.kt +++ b/app/src/main/java/www/spikeysanju/jetquotes/ui/Color.kt @@ -34,6 +34,7 @@ val white = Color(0xffffffff) val black = Color(0xff000000) val paleWhite = Color(0xfff3f7f9) val paleBlack = Color(0xff222325) +val red = Color(0xFFEB5757) diff --git a/app/src/main/java/www/spikeysanju/jetquotes/view/favourites/FavouritesScreen.kt b/app/src/main/java/www/spikeysanju/jetquotes/view/favourites/FavouritesScreen.kt index fb95d7e..d4d31ee 100644 --- a/app/src/main/java/www/spikeysanju/jetquotes/view/favourites/FavouritesScreen.kt +++ b/app/src/main/java/www/spikeysanju/jetquotes/view/favourites/FavouritesScreen.kt @@ -28,24 +28,52 @@ package www.spikeysanju.jetquotes.view.favourites +import androidx.compose.animation.animateColorAsState +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items +import androidx.compose.material.DismissDirection +import androidx.compose.material.DismissValue +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.FractionalThreshold +import androidx.compose.material.Icon +import androidx.compose.material.ListItem +import androidx.compose.material.MaterialTheme import androidx.compose.material.Scaffold +import androidx.compose.material.SwipeToDismiss import androidx.compose.material.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Delete +import androidx.compose.material.rememberDismissState import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.scale +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp import www.spikeysanju.jetquotes.components.QuotesCard import www.spikeysanju.jetquotes.components.TopBarWithBack import www.spikeysanju.jetquotes.navigation.MainActions +import www.spikeysanju.jetquotes.ui.red import www.spikeysanju.jetquotes.utils.FavouriteViewState import www.spikeysanju.jetquotes.view.viewModel.MainViewModel +@ExperimentalMaterialApi @Composable fun FavouritesScreen(viewModel: MainViewModel, actions: MainActions) { Scaffold(topBar = { - TopBarWithBack( - title = "JetQuotes", + title = "Favourites", onBackClick = { actions.upPress() }) }, content = { @@ -57,7 +85,73 @@ fun FavouritesScreen(viewModel: MainViewModel, actions: MainActions) { is FavouriteViewState.Success -> { LazyColumn { items(result.quote) { quote -> - QuotesCard(quote = quote, actions = actions) + var unread by remember { mutableStateOf(false) } + val dismissState = rememberDismissState( + confirmStateChange = { + if (it == DismissValue.DismissedToEnd) unread = !unread + it != DismissValue.DismissedToEnd + } + ) + + SwipeToDismiss( + state = dismissState, + modifier = Modifier.padding(vertical = 4.dp), + directions = setOf( + DismissDirection.StartToEnd, + DismissDirection.EndToStart + ), + dismissThresholds = { direction -> + FractionalThreshold(if (direction == DismissDirection.StartToEnd) 0.25f else 0.5f) + }, + background = { + val direction = + dismissState.dismissDirection ?: return@SwipeToDismiss + val color by animateColorAsState( + when (dismissState.targetValue) { + DismissValue.Default -> MaterialTheme.colors.background + DismissValue.DismissedToEnd -> red + DismissValue.DismissedToStart -> red + } + ) + val alignment = when (direction) { + DismissDirection.StartToEnd -> Alignment.CenterStart + DismissDirection.EndToStart -> Alignment.CenterEnd + } + val icon = when (direction) { + DismissDirection.StartToEnd -> Icons.Outlined.Delete + DismissDirection.EndToStart -> Icons.Outlined.Delete + } + val scale by animateFloatAsState( + if (dismissState.targetValue == DismissValue.Default) 0.75f else 1.5f + ) + + Box( + Modifier + .fillMaxSize() + .background(color) + .padding(horizontal = 20.dp), + contentAlignment = alignment + ) { + Icon( + icon, + contentDescription = "Localized description", + modifier = Modifier + .scale(scale) + .clickable { viewModel.deleteFavourite(quote) }, + tint = Color.White + ) + } + }, + dismissContent = { + ListItem( + text = { + QuotesCard(quote = quote, actions = actions) + }, + secondaryText = { Text("Swipe to Remove from favourites!") } + ) + } + ) + } } } From ba749bd29427a7c5b329165c4b95b2a809c06d9b Mon Sep 17 00:00:00 2001 From: Sanju S Date: Tue, 4 May 2021 09:44:19 +0530 Subject: [PATCH 6/8] Set Schema Export #24 Signed-off-by: Spikeysanju --- .../jetquotes/data/preference/db/JetQuotesDatabase.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/www/spikeysanju/jetquotes/data/preference/db/JetQuotesDatabase.kt b/app/src/main/java/www/spikeysanju/jetquotes/data/preference/db/JetQuotesDatabase.kt index ee0e0e4..619b5f3 100644 --- a/app/src/main/java/www/spikeysanju/jetquotes/data/preference/db/JetQuotesDatabase.kt +++ b/app/src/main/java/www/spikeysanju/jetquotes/data/preference/db/JetQuotesDatabase.kt @@ -32,7 +32,7 @@ import androidx.room.Database import androidx.room.RoomDatabase import www.spikeysanju.jetquotes.model.Quote -@Database(entities = [Quote::class], version = 1) +@Database(entities = [Quote::class], version = 1, exportSchema = false) abstract class JetQuotesDatabase : RoomDatabase() { abstract fun getFavouritesDao(): FavouritesDao } \ No newline at end of file From 59d577ec7b536723f31be22cee44ae10ca0cc523 Mon Sep 17 00:00:00 2001 From: Sanju S Date: Tue, 4 May 2021 09:48:56 +0530 Subject: [PATCH 7/8] Add annotation for Experimental API Signed-off-by: Spikeysanju --- .../java/www/spikeysanju/jetquotes/navigation/JetQuotesMain.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/java/www/spikeysanju/jetquotes/navigation/JetQuotesMain.kt b/app/src/main/java/www/spikeysanju/jetquotes/navigation/JetQuotesMain.kt index 6d0acad..9ae9daf 100644 --- a/app/src/main/java/www/spikeysanju/jetquotes/navigation/JetQuotesMain.kt +++ b/app/src/main/java/www/spikeysanju/jetquotes/navigation/JetQuotesMain.kt @@ -28,6 +28,7 @@ package www.spikeysanju.jetquotes.navigation +import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.navigation.NavHostController @@ -48,6 +49,7 @@ object EndPoints { const val AUTHOR = "author" } +@ExperimentalMaterialApi @Composable fun JetQuotesMain(viewModel: MainViewModel, toggleTheme: () -> Unit) { val navController = rememberNavController() From e3e999ecf4deed21dfeacbdcd6778cd9c8430594 Mon Sep 17 00:00:00 2001 From: Sanju S Date: Tue, 4 May 2021 09:52:30 +0530 Subject: [PATCH 8/8] Add annotation for Experimental API Signed-off-by: Spikeysanju --- .../main/java/www/spikeysanju/jetquotes/view/MainActivity.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/java/www/spikeysanju/jetquotes/view/MainActivity.kt b/app/src/main/java/www/spikeysanju/jetquotes/view/MainActivity.kt index 5302a2f..9d46826 100644 --- a/app/src/main/java/www/spikeysanju/jetquotes/view/MainActivity.kt +++ b/app/src/main/java/www/spikeysanju/jetquotes/view/MainActivity.kt @@ -33,6 +33,7 @@ import androidx.activity.compose.setContent import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatDelegate import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.MaterialTheme import androidx.compose.material.Surface import androidx.compose.runtime.collectAsState @@ -48,6 +49,7 @@ import www.spikeysanju.jetquotes.ui.JetQuotesTheme import www.spikeysanju.jetquotes.view.viewModel.MainViewModel +@ExperimentalMaterialApi @AndroidEntryPoint class MainActivity : AppCompatActivity() { private lateinit var viewModel: MainViewModel