From b9ca44892b0e31211b30afbe9fd90edf1bb0e3eb Mon Sep 17 00:00:00 2001 From: Joren Date: Mon, 29 Apr 2024 20:27:38 +0200 Subject: [PATCH 1/8] Add favourite button, make tiles larger --- .../com/ti/mobpo/ui/screens/PokeSearch.kt | 55 ++++++++++++++++--- 1 file changed, 46 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/ti/mobpo/ui/screens/PokeSearch.kt b/app/src/main/java/com/ti/mobpo/ui/screens/PokeSearch.kt index 105daa3..d91ef3e 100644 --- a/app/src/main/java/com/ti/mobpo/ui/screens/PokeSearch.kt +++ b/app/src/main/java/com/ti/mobpo/ui/screens/PokeSearch.kt @@ -4,6 +4,7 @@ import androidx.compose.foundation.Image import com.ti.mobpo.ui.PokeSearchViewModel 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.aspectRatio import androidx.compose.foundation.layout.fillMaxSize @@ -14,19 +15,30 @@ import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.items +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Favorite +import androidx.compose.material.icons.outlined.FavoriteBorder import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.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.layout.ContentScale +import androidx.compose.ui.layout.layout import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.dp import com.ti.mobpo.R import com.ti.mobpo.ui.SearchBar @@ -81,13 +93,14 @@ fun PokeSearch(pokeSearchViewModel: PokeSearchViewModel) { @Composable fun PokemonCard(pokemon: PokemonDetails) { + var isFavourite by remember { mutableStateOf(false) } Card( shape = MaterialTheme.shapes.medium, elevation = CardDefaults.cardElevation(defaultElevation = 8.dp), modifier = Modifier .padding(4.dp) .fillMaxWidth() - .aspectRatio(1f) + .aspectRatio(0.75f) ) { Column( modifier = Modifier.fillMaxSize(), @@ -102,14 +115,38 @@ fun PokemonCard(pokemon: PokemonDetails) { .fillMaxWidth() .weight(1f) ) - Text( - text = capitalizeFirstLetterAfterHyphens(pokemon.name), - style = MaterialTheme.typography.bodySmall, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 8.dp, vertical = 4.dp) // Adjust padding as needed - .wrapContentSize(Alignment.Center) - ) + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth() + ) { + Text( + text = capitalizeFirstLetterAfterHyphens(pokemon.name), + modifier = Modifier + .padding(8.dp) + .weight(1f), + maxLines = 2, + overflow = TextOverflow.Ellipsis + ) + IconButton( + onClick = { isFavourite = !isFavourite }, + modifier = Modifier.wrapContentSize() + .layout { measurable, constraints -> + if (constraints.maxHeight == Constraints.Infinity) { + layout(0, 0) {} + } else { + val placeable = measurable.measure(constraints) + layout(placeable.width, placeable.height) { + placeable.place(0, 0) + } + } + } + ) { + Icon( + imageVector = if (isFavourite) Icons.Filled.Favorite else Icons.Outlined.FavoriteBorder, + contentDescription = "Favourite" + ) + } + } } } } From 4c5170d000a5f2dbf379d4a636424b5b4d9b167b Mon Sep 17 00:00:00 2001 From: Joren Date: Mon, 29 Apr 2024 20:31:40 +0200 Subject: [PATCH 2/8] Profile page is not needed for our purpose --- app/src/main/java/com/ti/mobpo/ui/Navigation.kt | 14 +------------- .../main/java/com/ti/mobpo/ui/screens/Profile.kt | 9 --------- 2 files changed, 1 insertion(+), 22 deletions(-) delete mode 100644 app/src/main/java/com/ti/mobpo/ui/screens/Profile.kt diff --git a/app/src/main/java/com/ti/mobpo/ui/Navigation.kt b/app/src/main/java/com/ti/mobpo/ui/Navigation.kt index af60fa4..af1b488 100644 --- a/app/src/main/java/com/ti/mobpo/ui/Navigation.kt +++ b/app/src/main/java/com/ti/mobpo/ui/Navigation.kt @@ -34,8 +34,6 @@ import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController import com.ti.mobpo.ui.screens.Favourites import com.ti.mobpo.ui.screens.PokeSearchScreen -import com.ti.mobpo.ui.screens.Profile - @Composable fun Navigation() { @@ -51,13 +49,7 @@ fun Navigation() { selectedIcon = Icons.Filled.Favorite, unselectedItem = Icons.Outlined.FavoriteBorder, route = Screen.Favourites.route - ), - BottomNavigationItem( - title = "Profile", - selectedIcon = Icons.Filled.Person, - unselectedItem = Icons.Outlined.Person, - route = Screen.Profile.route - ), + ) ) var selectedItemIndex by rememberSaveable { @@ -121,10 +113,6 @@ fun Navigation() { route = Screen.Favourites.route) { Favourites() } - composable( - route = Screen.Profile.route) { - Profile() - } } } ) diff --git a/app/src/main/java/com/ti/mobpo/ui/screens/Profile.kt b/app/src/main/java/com/ti/mobpo/ui/screens/Profile.kt deleted file mode 100644 index cf3f3b8..0000000 --- a/app/src/main/java/com/ti/mobpo/ui/screens/Profile.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.ti.mobpo.ui.screens - -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable - -@Composable -fun Profile(){ - Text("Profile Page") -} \ No newline at end of file From 9b0d0e2beae23e1246ea797fbf627d7fdd97c805 Mon Sep 17 00:00:00 2001 From: Joren Date: Mon, 29 Apr 2024 20:32:31 +0200 Subject: [PATCH 3/8] Remove unused routes --- app/src/main/java/com/ti/mobpo/ui/Screen.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/src/main/java/com/ti/mobpo/ui/Screen.kt b/app/src/main/java/com/ti/mobpo/ui/Screen.kt index a564e79..d6e109b 100644 --- a/app/src/main/java/com/ti/mobpo/ui/Screen.kt +++ b/app/src/main/java/com/ti/mobpo/ui/Screen.kt @@ -2,7 +2,5 @@ package com.ti.mobpo.ui sealed class Screen (val route: String) { object PokeSearch : Screen("default_screen") - object TopNav : Screen("navigation_bar") object Favourites : Screen("favourites_page") - object Profile : Screen("profile_page") } \ No newline at end of file From 6fea30789d455ab37fea6af88a7c7ab9f33dc515 Mon Sep 17 00:00:00 2001 From: Joren Date: Mon, 29 Apr 2024 21:26:35 +0200 Subject: [PATCH 4/8] Add favourite button --- app/src/main/java/com/ti/mobpo/model/PokeModels.kt | 3 ++- .../main/java/com/ti/mobpo/ui/PokeSearchViewModel.kt | 10 ++++++++++ .../main/java/com/ti/mobpo/ui/screens/PokeSearch.kt | 10 ++++++---- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/ti/mobpo/model/PokeModels.kt b/app/src/main/java/com/ti/mobpo/model/PokeModels.kt index 81c8799..4312980 100644 --- a/app/src/main/java/com/ti/mobpo/model/PokeModels.kt +++ b/app/src/main/java/com/ti/mobpo/model/PokeModels.kt @@ -15,7 +15,8 @@ data class PokemonDetails( val id: Int, val name: String, val types: List, - @SerializedName("sprites") val sprites: Sprites + @SerializedName("sprites") val sprites: Sprites, + var isFavorite: Boolean = false ) data class Type( diff --git a/app/src/main/java/com/ti/mobpo/ui/PokeSearchViewModel.kt b/app/src/main/java/com/ti/mobpo/ui/PokeSearchViewModel.kt index a36b747..df6a2a7 100644 --- a/app/src/main/java/com/ti/mobpo/ui/PokeSearchViewModel.kt +++ b/app/src/main/java/com/ti/mobpo/ui/PokeSearchViewModel.kt @@ -39,4 +39,14 @@ class PokeSearchViewModel : ViewModel() { val parts = url.split("/") return parts[parts.size - 2].toInt() } + + fun toggleFavorite(pokemonId: Int) { + _pokemonDetails.value = _pokemonDetails.value?.map { pokemon -> + if (pokemon.id == pokemonId) { + pokemon.copy(isFavorite = !pokemon.isFavorite) + } else { + pokemon + } + } + } } diff --git a/app/src/main/java/com/ti/mobpo/ui/screens/PokeSearch.kt b/app/src/main/java/com/ti/mobpo/ui/screens/PokeSearch.kt index d91ef3e..ae04f55 100644 --- a/app/src/main/java/com/ti/mobpo/ui/screens/PokeSearch.kt +++ b/app/src/main/java/com/ti/mobpo/ui/screens/PokeSearch.kt @@ -80,7 +80,9 @@ fun PokeSearch(pokeSearchViewModel: PokeSearchViewModel) { horizontalArrangement = Arrangement.spacedBy(8.dp) ) { items(items = results, key = { pokemon -> pokemon.id }) { pokemon -> - PokemonCard(pokemon) + PokemonCard(pokemon, toggleFavorite = { pokemonId -> + pokeSearchViewModel.toggleFavorite(pokemonId) + }) } } } else { @@ -92,7 +94,7 @@ fun PokeSearch(pokeSearchViewModel: PokeSearchViewModel) { @Composable -fun PokemonCard(pokemon: PokemonDetails) { +fun PokemonCard(pokemon: PokemonDetails, toggleFavorite: (Int) -> Unit) { var isFavourite by remember { mutableStateOf(false) } Card( shape = MaterialTheme.shapes.medium, @@ -128,7 +130,7 @@ fun PokemonCard(pokemon: PokemonDetails) { overflow = TextOverflow.Ellipsis ) IconButton( - onClick = { isFavourite = !isFavourite }, + onClick = { toggleFavorite(pokemon.id) }, modifier = Modifier.wrapContentSize() .layout { measurable, constraints -> if (constraints.maxHeight == Constraints.Infinity) { @@ -142,7 +144,7 @@ fun PokemonCard(pokemon: PokemonDetails) { } ) { Icon( - imageVector = if (isFavourite) Icons.Filled.Favorite else Icons.Outlined.FavoriteBorder, + imageVector = if (pokemon.isFavorite) Icons.Filled.Favorite else Icons.Outlined.FavoriteBorder, contentDescription = "Favourite" ) } From b5573783b9f8975f7a067f8db9a85a6d6ec15a04 Mon Sep 17 00:00:00 2001 From: Joren Date: Mon, 29 Apr 2024 21:40:55 +0200 Subject: [PATCH 5/8] Add roop deps --- app/build.gradle.kts | 6 ++++++ build.gradle.kts | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 796bb03..47d87af 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,6 +1,7 @@ plugins { alias(libs.plugins.androidApplication) alias(libs.plugins.jetbrainsKotlinAndroid) + id("com.google.devtools.ksp") version "1.9.20-1.0.14" } android { @@ -69,4 +70,9 @@ dependencies { implementation("com.squareup.retrofit2:retrofit:2.11.0") implementation("com.squareup.retrofit2:converter-gson:2.11.0") implementation("io.coil-kt:coil-compose:2.6.0") + + implementation("androidx.room:room-runtime:${rootProject.extra["room_version"]}") + implementation("androidx.core:core-ktx:1.12.0") + ksp("androidx.room:room-compiler:${rootProject.extra["room_version"]}") + implementation("androidx.room:room-ktx:${rootProject.extra["room_version"]}") } \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index a0985ef..a2062cf 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,4 +2,10 @@ plugins { alias(libs.plugins.androidApplication) apply false alias(libs.plugins.jetbrainsKotlinAndroid) apply false +} + +buildscript { + extra.apply { + set("room_version", "2.6.0") + } } \ No newline at end of file From 6abf4100f22f41282141bf60bba583cf72e5b007 Mon Sep 17 00:00:00 2001 From: Joren Date: Mon, 29 Apr 2024 22:04:12 +0200 Subject: [PATCH 6/8] Add Favouite, Dao and database --- .../main/java/com/ti/mobpo/data/Favourite.kt | 14 +++++++++++ .../java/com/ti/mobpo/data/FavouriteDao.kt | 19 +++++++++++++++ .../java/com/ti/mobpo/data/PokemonDatabase.kt | 23 +++++++++++++++++++ 3 files changed, 56 insertions(+) create mode 100644 app/src/main/java/com/ti/mobpo/data/Favourite.kt create mode 100644 app/src/main/java/com/ti/mobpo/data/FavouriteDao.kt create mode 100644 app/src/main/java/com/ti/mobpo/data/PokemonDatabase.kt diff --git a/app/src/main/java/com/ti/mobpo/data/Favourite.kt b/app/src/main/java/com/ti/mobpo/data/Favourite.kt new file mode 100644 index 0000000..ac23ca9 --- /dev/null +++ b/app/src/main/java/com/ti/mobpo/data/Favourite.kt @@ -0,0 +1,14 @@ +package com.ti.mobpo.data + +import androidx.room.Entity +import androidx.room.PrimaryKey +import com.ti.mobpo.model.Sprites +import com.ti.mobpo.model.Type + +@Entity(tableName = "favourites") +data class Favourite( + @PrimaryKey val id: Int, + val name: String, + val types: List, + val sprites: Sprites +) \ No newline at end of file diff --git a/app/src/main/java/com/ti/mobpo/data/FavouriteDao.kt b/app/src/main/java/com/ti/mobpo/data/FavouriteDao.kt new file mode 100644 index 0000000..5d59e40 --- /dev/null +++ b/app/src/main/java/com/ti/mobpo/data/FavouriteDao.kt @@ -0,0 +1,19 @@ +package com.ti.mobpo.data + +import androidx.room.Dao +import androidx.room.Delete +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query + +@Dao +interface FavouriteDao { + @Insert(onConflict = OnConflictStrategy.IGNORE) + suspend fun insert(favourite: Favourite) + + @Delete + suspend fun delete(favourite: Favourite) + + @Query("SELECT id FROM favourites") + suspend fun getAllFavoriteIds(): List +} diff --git a/app/src/main/java/com/ti/mobpo/data/PokemonDatabase.kt b/app/src/main/java/com/ti/mobpo/data/PokemonDatabase.kt new file mode 100644 index 0000000..24b5509 --- /dev/null +++ b/app/src/main/java/com/ti/mobpo/data/PokemonDatabase.kt @@ -0,0 +1,23 @@ +package com.ti.mobpo.data + +import android.content.Context +import androidx.room.Database +import androidx.room.Room +import androidx.room.RoomDatabase + +@Database(entities = [Favourite::class], version = 1, exportSchema = false) +abstract class PokemonDatabase : RoomDatabase() { + abstract fun FavouriteDao(): FavouriteDao + + companion object { + @Volatile + private var Instance: PokemonDatabase? = null + fun getDatabase(context: Context): PokemonDatabase { + return Instance ?: synchronized(this) { + Room.databaseBuilder(context, PokemonDatabase::class.java, "favorites_database") + .build() + .also { Instance = it } + } + } + } +} From ec25b03e98971a48ba7253594eac68b68cf5da9e Mon Sep 17 00:00:00 2001 From: Joren Date: Mon, 29 Apr 2024 22:32:19 +0200 Subject: [PATCH 7/8] Add repo, .. --- .../java/com/ti/mobpo/data/AppContainer.kt | 16 +++++++++++++ .../main/java/com/ti/mobpo/data/Favourite.kt | 4 +--- .../java/com/ti/mobpo/data/FavouriteDao.kt | 3 ++- .../com/ti/mobpo/data/FavouritesRepository.kt | 23 +++++++++++++++++++ .../mobpo/data/OfflineFavouritesRepository.kt | 16 +++++++++++++ .../main/java/com/ti/mobpo/ui/Navigation.kt | 3 +-- .../ui/{ => pokesearch}/PokeSearchUiState.kt | 2 +- .../{ => pokesearch}/PokeSearchViewModel.kt | 2 +- .../com/ti/mobpo/ui/screens/PokeSearch.kt | 4 +--- 9 files changed, 62 insertions(+), 11 deletions(-) create mode 100644 app/src/main/java/com/ti/mobpo/data/AppContainer.kt create mode 100644 app/src/main/java/com/ti/mobpo/data/FavouritesRepository.kt create mode 100644 app/src/main/java/com/ti/mobpo/data/OfflineFavouritesRepository.kt rename app/src/main/java/com/ti/mobpo/ui/{ => pokesearch}/PokeSearchUiState.kt (63%) rename app/src/main/java/com/ti/mobpo/ui/{ => pokesearch}/PokeSearchViewModel.kt (98%) diff --git a/app/src/main/java/com/ti/mobpo/data/AppContainer.kt b/app/src/main/java/com/ti/mobpo/data/AppContainer.kt new file mode 100644 index 0000000..210067a --- /dev/null +++ b/app/src/main/java/com/ti/mobpo/data/AppContainer.kt @@ -0,0 +1,16 @@ +package com.ti.mobpo.data + +import android.content.Context + + +interface AppContainer { + val favouritesRepository: FavouritesRepository +} + + +class AppDataContainer(private val context: Context) : AppContainer { + + override val favouritesRepository: FavouritesRepository by lazy { + OfflineFavouritesRepository(PokemonDatabase.getDatabase(context).FavouriteDao()) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ti/mobpo/data/Favourite.kt b/app/src/main/java/com/ti/mobpo/data/Favourite.kt index ac23ca9..eec9166 100644 --- a/app/src/main/java/com/ti/mobpo/data/Favourite.kt +++ b/app/src/main/java/com/ti/mobpo/data/Favourite.kt @@ -8,7 +8,5 @@ import com.ti.mobpo.model.Type @Entity(tableName = "favourites") data class Favourite( @PrimaryKey val id: Int, - val name: String, - val types: List, - val sprites: Sprites + val name: String ) \ No newline at end of file diff --git a/app/src/main/java/com/ti/mobpo/data/FavouriteDao.kt b/app/src/main/java/com/ti/mobpo/data/FavouriteDao.kt index 5d59e40..a3b46a9 100644 --- a/app/src/main/java/com/ti/mobpo/data/FavouriteDao.kt +++ b/app/src/main/java/com/ti/mobpo/data/FavouriteDao.kt @@ -5,6 +5,7 @@ import androidx.room.Delete import androidx.room.Insert import androidx.room.OnConflictStrategy import androidx.room.Query +import kotlinx.coroutines.flow.Flow @Dao interface FavouriteDao { @@ -15,5 +16,5 @@ interface FavouriteDao { suspend fun delete(favourite: Favourite) @Query("SELECT id FROM favourites") - suspend fun getAllFavoriteIds(): List + fun getAllFavoriteIds(): Flow> } diff --git a/app/src/main/java/com/ti/mobpo/data/FavouritesRepository.kt b/app/src/main/java/com/ti/mobpo/data/FavouritesRepository.kt new file mode 100644 index 0000000..316a2c4 --- /dev/null +++ b/app/src/main/java/com/ti/mobpo/data/FavouritesRepository.kt @@ -0,0 +1,23 @@ +package com.ti.mobpo.data + +import kotlinx.coroutines.flow.Flow + +/** + * Repository that provides insert, update, delete, and retrieve of [Item] from a given data source. + */ +interface FavouritesRepository { + /** + * Retrieve all the items from the the given data source. + */ + fun getAllFavoriteIds(): Flow> + + /** + * Insert item in the data source + */ + suspend fun insertItem(item: Favourite) + + /** + * Delete item from the data source + */ + suspend fun deleteItem(item: Favourite) +} diff --git a/app/src/main/java/com/ti/mobpo/data/OfflineFavouritesRepository.kt b/app/src/main/java/com/ti/mobpo/data/OfflineFavouritesRepository.kt new file mode 100644 index 0000000..62a98a8 --- /dev/null +++ b/app/src/main/java/com/ti/mobpo/data/OfflineFavouritesRepository.kt @@ -0,0 +1,16 @@ +package com.ti.mobpo.data + +import kotlinx.coroutines.flow.Flow + +class OfflineFavouritesRepository(private val favouriteDao: FavouriteDao) : FavouritesRepository { + override fun getAllFavoriteIds(): Flow> { + return favouriteDao.getAllFavoriteIds() + } + override suspend fun insertItem(item: Favourite) { + favouriteDao.insert(item) + } + + override suspend fun deleteItem(item: Favourite) { + favouriteDao.delete(item) + } +} diff --git a/app/src/main/java/com/ti/mobpo/ui/Navigation.kt b/app/src/main/java/com/ti/mobpo/ui/Navigation.kt index af1b488..3510682 100644 --- a/app/src/main/java/com/ti/mobpo/ui/Navigation.kt +++ b/app/src/main/java/com/ti/mobpo/ui/Navigation.kt @@ -8,10 +8,8 @@ import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Favorite import androidx.compose.material.icons.filled.Home -import androidx.compose.material.icons.filled.Person import androidx.compose.material.icons.outlined.FavoriteBorder import androidx.compose.material.icons.outlined.Home -import androidx.compose.material.icons.outlined.Person import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.NavigationBar @@ -32,6 +30,7 @@ import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController +import com.ti.mobpo.ui.pokesearch.PokeSearchViewModel import com.ti.mobpo.ui.screens.Favourites import com.ti.mobpo.ui.screens.PokeSearchScreen diff --git a/app/src/main/java/com/ti/mobpo/ui/PokeSearchUiState.kt b/app/src/main/java/com/ti/mobpo/ui/pokesearch/PokeSearchUiState.kt similarity index 63% rename from app/src/main/java/com/ti/mobpo/ui/PokeSearchUiState.kt rename to app/src/main/java/com/ti/mobpo/ui/pokesearch/PokeSearchUiState.kt index 2bd3d0f..5012bf5 100644 --- a/app/src/main/java/com/ti/mobpo/ui/PokeSearchUiState.kt +++ b/app/src/main/java/com/ti/mobpo/ui/pokesearch/PokeSearchUiState.kt @@ -1,3 +1,3 @@ -package com.ti.mobpo.ui +package com.ti.mobpo.ui.pokesearch data class PokeSearchUiState(val searchQuery: String = "") diff --git a/app/src/main/java/com/ti/mobpo/ui/PokeSearchViewModel.kt b/app/src/main/java/com/ti/mobpo/ui/pokesearch/PokeSearchViewModel.kt similarity index 98% rename from app/src/main/java/com/ti/mobpo/ui/PokeSearchViewModel.kt rename to app/src/main/java/com/ti/mobpo/ui/pokesearch/PokeSearchViewModel.kt index df6a2a7..febd8b3 100644 --- a/app/src/main/java/com/ti/mobpo/ui/PokeSearchViewModel.kt +++ b/app/src/main/java/com/ti/mobpo/ui/pokesearch/PokeSearchViewModel.kt @@ -1,4 +1,4 @@ -package com.ti.mobpo.ui +package com.ti.mobpo.ui.pokesearch import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope diff --git a/app/src/main/java/com/ti/mobpo/ui/screens/PokeSearch.kt b/app/src/main/java/com/ti/mobpo/ui/screens/PokeSearch.kt index ae04f55..511ba28 100644 --- a/app/src/main/java/com/ti/mobpo/ui/screens/PokeSearch.kt +++ b/app/src/main/java/com/ti/mobpo/ui/screens/PokeSearch.kt @@ -1,7 +1,6 @@ package com.ti.mobpo.ui.screens -import androidx.compose.foundation.Image -import com.ti.mobpo.ui.PokeSearchViewModel +import com.ti.mobpo.ui.pokesearch.PokeSearchViewModel import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -48,7 +47,6 @@ import coil.compose.AsyncImage import coil.request.ImageRequest import com.ti.mobpo.capitalizeFirstLetterAfterHyphens import com.ti.mobpo.ui.theme.MobileSecurityTheme -import java.util.Locale @Composable fun PokeSearchScreen(pokeSearchViewModel: PokeSearchViewModel) { From 77d70569629b196f48304cff34c39f2223ea7bdb Mon Sep 17 00:00:00 2001 From: Joren Date: Mon, 29 Apr 2024 23:19:14 +0200 Subject: [PATCH 8/8] Implement view model provider, local database --- app/src/main/AndroidManifest.xml | 1 + .../java/com/ti/mobpo/AppViewModelProvider.kt | 21 +++++++++++++++++++ app/src/main/java/com/ti/mobpo/PokeSearch.kt | 19 +++++++++++++++++ .../java/com/ti/mobpo/data/FavouriteDao.kt | 3 +++ .../com/ti/mobpo/data/FavouritesRepository.kt | 6 ++++++ .../mobpo/data/OfflineFavouritesRepository.kt | 4 ++++ .../main/java/com/ti/mobpo/ui/Navigation.kt | 9 +++----- .../ui/pokesearch/PokeSearchViewModel.kt | 19 +++++++++++++++-- .../com/ti/mobpo/ui/screens/PokeSearch.kt | 5 +++-- 9 files changed, 77 insertions(+), 10 deletions(-) create mode 100644 app/src/main/java/com/ti/mobpo/AppViewModelProvider.kt create mode 100644 app/src/main/java/com/ti/mobpo/PokeSearch.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 8df0ded..9a329ab 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -4,6 +4,7 @@ > + + @Query("SELECT EXISTS(SELECT 1 FROM favourites WHERE id = :id LIMIT 1)") + suspend fun isFavourite(id: Int): Boolean } diff --git a/app/src/main/java/com/ti/mobpo/data/FavouritesRepository.kt b/app/src/main/java/com/ti/mobpo/data/FavouritesRepository.kt index 316a2c4..4083c1b 100644 --- a/app/src/main/java/com/ti/mobpo/data/FavouritesRepository.kt +++ b/app/src/main/java/com/ti/mobpo/data/FavouritesRepository.kt @@ -20,4 +20,10 @@ interface FavouritesRepository { * Delete item from the data source */ suspend fun deleteItem(item: Favourite) + + /** + * Check if the item is a favourite + */ + + suspend fun isFavourite(id: Int): Boolean } diff --git a/app/src/main/java/com/ti/mobpo/data/OfflineFavouritesRepository.kt b/app/src/main/java/com/ti/mobpo/data/OfflineFavouritesRepository.kt index 62a98a8..6f4b000 100644 --- a/app/src/main/java/com/ti/mobpo/data/OfflineFavouritesRepository.kt +++ b/app/src/main/java/com/ti/mobpo/data/OfflineFavouritesRepository.kt @@ -13,4 +13,8 @@ class OfflineFavouritesRepository(private val favouriteDao: FavouriteDao) : Favo override suspend fun deleteItem(item: Favourite) { favouriteDao.delete(item) } + + override suspend fun isFavourite(id: Int): Boolean { + return favouriteDao.isFavourite(id) + } } diff --git a/app/src/main/java/com/ti/mobpo/ui/Navigation.kt b/app/src/main/java/com/ti/mobpo/ui/Navigation.kt index 3510682..e09a3c7 100644 --- a/app/src/main/java/com/ti/mobpo/ui/Navigation.kt +++ b/app/src/main/java/com/ti/mobpo/ui/Navigation.kt @@ -20,6 +20,7 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue @@ -30,7 +31,6 @@ import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController -import com.ti.mobpo.ui.pokesearch.PokeSearchViewModel import com.ti.mobpo.ui.screens.Favourites import com.ti.mobpo.ui.screens.PokeSearchScreen @@ -61,8 +61,6 @@ fun Navigation() { val updatedSelectedIndex = items.indexOfFirst { it.route == currentRoute } selectedItemIndex = rememberUpdatedState(updatedSelectedIndex).value - val pokeSearchViewModel: PokeSearchViewModel = viewModel() - Scaffold ( bottomBar = { NavigationBar { @@ -106,7 +104,7 @@ fun Navigation() { ExitTransition.None }) { composable(route = Screen.PokeSearch.route) { - PokeSearchScreen(pokeSearchViewModel) + PokeSearchScreen() } composable( route = Screen.Favourites.route) { @@ -124,7 +122,6 @@ fun Navigation() { @Composable fun NavigationPreview() { Surface { - val pokeSearchViewModel: PokeSearchViewModel = viewModel() - PokeSearchScreen(pokeSearchViewModel) + PokeSearchScreen() } } \ No newline at end of file diff --git a/app/src/main/java/com/ti/mobpo/ui/pokesearch/PokeSearchViewModel.kt b/app/src/main/java/com/ti/mobpo/ui/pokesearch/PokeSearchViewModel.kt index febd8b3..5bdc5a6 100644 --- a/app/src/main/java/com/ti/mobpo/ui/pokesearch/PokeSearchViewModel.kt +++ b/app/src/main/java/com/ti/mobpo/ui/pokesearch/PokeSearchViewModel.kt @@ -2,6 +2,8 @@ package com.ti.mobpo.ui.pokesearch import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.ti.mobpo.data.Favourite +import com.ti.mobpo.data.FavouritesRepository import com.ti.mobpo.model.PokemonDetails import com.ti.mobpo.network.PokeApi import kotlinx.coroutines.flow.MutableStateFlow @@ -12,7 +14,7 @@ import java.io.IOException -class PokeSearchViewModel : ViewModel() { +class PokeSearchViewModel(private val favouritesRepository: FavouritesRepository) : ViewModel() { private val service = PokeApi.retrofitService; private val _pokemonDetails = MutableStateFlow?>(null) @@ -26,7 +28,8 @@ class PokeSearchViewModel : ViewModel() { val detailsList = mutableListOf() for (pokemonSpecies in filteredList) { val details = service.getPokemonDetails(extractPokemonId(pokemonSpecies.url)) - detailsList.add(details) + val isFavorite = favouritesRepository.isFavourite(details.id) + detailsList.add(details.copy(isFavorite = isFavorite)) } _pokemonDetails.value = detailsList } catch (e: IOException) { @@ -48,5 +51,17 @@ class PokeSearchViewModel : ViewModel() { pokemon } } + viewModelScope.launch { + if (_pokemonDetails.value != null) { + val pokemon = _pokemonDetails.value!!.find { it.id == pokemonId } + pokemon?.let { + if (it.isFavorite) { + favouritesRepository.insertItem(Favourite(it.id, it.name)) + } else { + favouritesRepository.deleteItem(Favourite(it.id, it.name)) + } + } + } + } } } diff --git a/app/src/main/java/com/ti/mobpo/ui/screens/PokeSearch.kt b/app/src/main/java/com/ti/mobpo/ui/screens/PokeSearch.kt index 511ba28..a2aa2a7 100644 --- a/app/src/main/java/com/ti/mobpo/ui/screens/PokeSearch.kt +++ b/app/src/main/java/com/ti/mobpo/ui/screens/PokeSearch.kt @@ -45,12 +45,13 @@ import androidx.lifecycle.viewmodel.compose.viewModel import com.ti.mobpo.model.PokemonDetails import coil.compose.AsyncImage import coil.request.ImageRequest +import com.ti.mobpo.AppViewModelProvider import com.ti.mobpo.capitalizeFirstLetterAfterHyphens import com.ti.mobpo.ui.theme.MobileSecurityTheme @Composable -fun PokeSearchScreen(pokeSearchViewModel: PokeSearchViewModel) { - PokeSearch(pokeSearchViewModel) +fun PokeSearchScreen(viewModel: PokeSearchViewModel = viewModel(factory = AppViewModelProvider.Factory)) { + PokeSearch(viewModel) } @Composable