Merge branch 'simple_sub_system' into 'master'
Simple sub system See merge request ti/2023-2024/s4/mobile-security/students/joren-schipman/pokesearch!3
This commit is contained in:
commit
6b7cd89f86
@ -1,11 +1,11 @@
|
|||||||
package com.ti.mobpo
|
package com.ti.mobpo
|
||||||
|
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.createSavedStateHandle
|
|
||||||
import androidx.lifecycle.viewmodel.CreationExtras
|
import androidx.lifecycle.viewmodel.CreationExtras
|
||||||
import androidx.lifecycle.viewmodel.initializer
|
import androidx.lifecycle.viewmodel.initializer
|
||||||
import androidx.lifecycle.viewmodel.viewModelFactory
|
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||||
import com.ti.mobpo.ui.pokesearch.PokeSearchViewModel
|
import com.ti.mobpo.ui.viewmodels.FavouritesViewModel
|
||||||
|
import com.ti.mobpo.ui.viewmodels.PokeSearchViewModel
|
||||||
|
|
||||||
object AppViewModelProvider {
|
object AppViewModelProvider {
|
||||||
val Factory = viewModelFactory {
|
val Factory = viewModelFactory {
|
||||||
@ -13,9 +13,17 @@ object AppViewModelProvider {
|
|||||||
PokeSearchViewModel(
|
PokeSearchViewModel(
|
||||||
pokesearchApplication().appContainer.favouritesRepository
|
pokesearchApplication().appContainer.favouritesRepository
|
||||||
)
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
initializer {
|
||||||
|
FavouritesViewModel(
|
||||||
|
pokesearchApplication().appContainer.favouritesRepository, pokesearchApplication().featureManager
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun CreationExtras.pokesearchApplication(): PokeSearch =
|
fun CreationExtras.pokesearchApplication(): PokeSearch =
|
||||||
(this[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as PokeSearch)
|
(this[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as PokeSearch)
|
||||||
|
|
||||||
|
@ -3,17 +3,26 @@ package com.ti.mobpo
|
|||||||
import android.app.Application
|
import android.app.Application
|
||||||
import com.ti.mobpo.data.AppContainer
|
import com.ti.mobpo.data.AppContainer
|
||||||
import com.ti.mobpo.data.AppDataContainer
|
import com.ti.mobpo.data.AppDataContainer
|
||||||
|
import com.ti.mobpo.ui.util.FeatureManager
|
||||||
|
|
||||||
class PokeSearch : Application() {
|
class PokeSearch : Application() {
|
||||||
lateinit var appContainer: AppContainer
|
lateinit var appContainer: AppContainer
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
lateinit var featureManager: FeatureManager
|
||||||
|
private set
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
appContainer = createAppContainer()
|
appContainer = createAppContainer()
|
||||||
|
featureManager = createFeatrureManager()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createAppContainer(): AppContainer {
|
private fun createAppContainer(): AppContainer {
|
||||||
return AppDataContainer(this)
|
return AppDataContainer(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun createFeatrureManager(): FeatureManager {
|
||||||
|
return FeatureManager(this)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,6 @@ package com.ti.mobpo.data
|
|||||||
|
|
||||||
import androidx.room.Entity
|
import androidx.room.Entity
|
||||||
import androidx.room.PrimaryKey
|
import androidx.room.PrimaryKey
|
||||||
import com.ti.mobpo.model.Sprites
|
|
||||||
import com.ti.mobpo.model.Type
|
|
||||||
|
|
||||||
@Entity(tableName = "favourites")
|
@Entity(tableName = "favourites")
|
||||||
data class Favourite(
|
data class Favourite(
|
||||||
|
@ -14,9 +14,8 @@ interface FavouriteDao {
|
|||||||
|
|
||||||
@Delete
|
@Delete
|
||||||
suspend fun delete(favourite: Favourite)
|
suspend fun delete(favourite: Favourite)
|
||||||
|
@Query("SELECT * from favourites ORDER BY id ASC")
|
||||||
@Query("SELECT id FROM favourites")
|
fun getAllItems(): Flow<List<Favourite>>
|
||||||
fun getAllFavoriteIds(): Flow<List<Int>>
|
|
||||||
|
|
||||||
@Query("SELECT EXISTS(SELECT 1 FROM favourites WHERE id = :id LIMIT 1)")
|
@Query("SELECT EXISTS(SELECT 1 FROM favourites WHERE id = :id LIMIT 1)")
|
||||||
suspend fun isFavourite(id: Int): Boolean
|
suspend fun isFavourite(id: Int): Boolean
|
||||||
|
@ -9,7 +9,7 @@ interface FavouritesRepository {
|
|||||||
/**
|
/**
|
||||||
* Retrieve all the items from the the given data source.
|
* Retrieve all the items from the the given data source.
|
||||||
*/
|
*/
|
||||||
fun getAllFavoriteIds(): Flow<List<Int>>
|
fun getAllItems(): Flow<List<Favourite>>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Insert item in the data source
|
* Insert item in the data source
|
||||||
|
@ -3,8 +3,8 @@ package com.ti.mobpo.data
|
|||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
class OfflineFavouritesRepository(private val favouriteDao: FavouriteDao) : FavouritesRepository {
|
class OfflineFavouritesRepository(private val favouriteDao: FavouriteDao) : FavouritesRepository {
|
||||||
override fun getAllFavoriteIds(): Flow<List<Int>> {
|
override fun getAllItems(): Flow<List<Favourite>> {
|
||||||
return favouriteDao.getAllFavoriteIds()
|
return favouriteDao.getAllItems()
|
||||||
}
|
}
|
||||||
override suspend fun insertItem(item: Favourite) {
|
override suspend fun insertItem(item: Favourite) {
|
||||||
favouriteDao.insert(item)
|
favouriteDao.insert(item)
|
||||||
|
5
app/src/main/java/com/ti/mobpo/model/FavouriteUiState.kt
Normal file
5
app/src/main/java/com/ti/mobpo/model/FavouriteUiState.kt
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
package com.ti.mobpo.model
|
||||||
|
|
||||||
|
import com.ti.mobpo.data.Favourite
|
||||||
|
|
||||||
|
data class FavouriteUiState(val favourites: List<Favourite> = listOf())
|
@ -20,7 +20,6 @@ import androidx.compose.material3.Text
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.rememberUpdatedState
|
import androidx.compose.runtime.rememberUpdatedState
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
@ -31,8 +30,11 @@ import androidx.navigation.compose.NavHost
|
|||||||
import androidx.navigation.compose.composable
|
import androidx.navigation.compose.composable
|
||||||
import androidx.navigation.compose.currentBackStackEntryAsState
|
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
import com.ti.mobpo.ui.screens.Favourites
|
import com.ti.mobpo.AppViewModelProvider
|
||||||
|
import com.ti.mobpo.ui.screens.FavoritesScreen
|
||||||
import com.ti.mobpo.ui.screens.PokeSearchScreen
|
import com.ti.mobpo.ui.screens.PokeSearchScreen
|
||||||
|
import com.ti.mobpo.ui.viewmodels.FavouritesViewModel
|
||||||
|
import com.ti.mobpo.ui.viewmodels.PokeSearchViewModel
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun Navigation() {
|
fun Navigation() {
|
||||||
@ -61,6 +63,9 @@ fun Navigation() {
|
|||||||
val updatedSelectedIndex = items.indexOfFirst { it.route == currentRoute }
|
val updatedSelectedIndex = items.indexOfFirst { it.route == currentRoute }
|
||||||
selectedItemIndex = rememberUpdatedState(updatedSelectedIndex).value
|
selectedItemIndex = rememberUpdatedState(updatedSelectedIndex).value
|
||||||
|
|
||||||
|
val pokeSearchVM = viewModel<PokeSearchViewModel>(factory = AppViewModelProvider.Factory)
|
||||||
|
val favoritesVM = viewModel<FavouritesViewModel>(factory = AppViewModelProvider.Factory)
|
||||||
|
|
||||||
Scaffold (
|
Scaffold (
|
||||||
bottomBar = {
|
bottomBar = {
|
||||||
NavigationBar {
|
NavigationBar {
|
||||||
@ -104,11 +109,11 @@ fun Navigation() {
|
|||||||
ExitTransition.None
|
ExitTransition.None
|
||||||
}) {
|
}) {
|
||||||
composable(route = Screen.PokeSearch.route) {
|
composable(route = Screen.PokeSearch.route) {
|
||||||
PokeSearchScreen()
|
PokeSearchScreen(pokeSearchVM)
|
||||||
}
|
}
|
||||||
composable(
|
composable(
|
||||||
route = Screen.Favourites.route) {
|
route = Screen.Favourites.route) {
|
||||||
Favourites()
|
FavoritesScreen(favoritesVM)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -122,6 +127,6 @@ fun Navigation() {
|
|||||||
@Composable
|
@Composable
|
||||||
fun NavigationPreview() {
|
fun NavigationPreview() {
|
||||||
Surface {
|
Surface {
|
||||||
PokeSearchScreen()
|
PokeSearchScreen(viewModel(factory = AppViewModelProvider.Factory))
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,3 +0,0 @@
|
|||||||
package com.ti.mobpo.ui.pokesearch
|
|
||||||
|
|
||||||
data class PokeSearchUiState(val searchQuery: String = "")
|
|
@ -1,10 +1,89 @@
|
|||||||
package com.ti.mobpo.ui.screens
|
package com.ti.mobpo.ui.screens
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.lazy.grid.GridCells
|
||||||
|
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.ti.mobpo.ui.viewmodels.FavouritesViewModel
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.grid.items
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun Favourites() {
|
fun FavoritesScreen(viewModel: FavouritesViewModel) {
|
||||||
println("Favourites")
|
Favorites(viewModel)
|
||||||
Text("Favourites Page")
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun Favorites(favoritesViewModel: FavouritesViewModel) {
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
favoritesViewModel.loadFavourites()
|
||||||
|
}
|
||||||
|
|
||||||
|
val favorites by favoritesViewModel.pokemonDetails.collectAsState()
|
||||||
|
|
||||||
|
Column(
|
||||||
|
verticalArrangement = Arrangement.Top,
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(20.dp)
|
||||||
|
) {
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
// Show AlertDialog if access check failed
|
||||||
|
if (favoritesViewModel.accessCheckFailed.value) {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = {
|
||||||
|
// Dismiss the dialog
|
||||||
|
favoritesViewModel.accessCheckFailed.value = false
|
||||||
|
},
|
||||||
|
title = {
|
||||||
|
Text("Access Denied")
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
Text("You do not have access to this feature.")
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
favoritesViewModel.accessCheckFailed.value = false
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text("OK")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
favorites?.let { favoritesList ->
|
||||||
|
if (favoritesList.isNotEmpty()) {
|
||||||
|
LazyVerticalGrid(
|
||||||
|
columns = GridCells.Adaptive(150.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
|
) {
|
||||||
|
items(items = favoritesList, key = { pokemon -> pokemon.id }) { pokemon ->
|
||||||
|
PokemonCard(pokemon, toggleFavorite = { pokemonId ->
|
||||||
|
favoritesViewModel.toggleFavorite(pokemonId)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Text("No favorites found")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,6 +1,6 @@
|
|||||||
package com.ti.mobpo.ui.screens
|
package com.ti.mobpo.ui.screens
|
||||||
|
|
||||||
import com.ti.mobpo.ui.pokesearch.PokeSearchViewModel
|
import com.ti.mobpo.ui.viewmodels.PokeSearchViewModel
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
@ -19,6 +19,7 @@ import androidx.compose.material.icons.filled.Favorite
|
|||||||
import androidx.compose.material.icons.outlined.FavoriteBorder
|
import androidx.compose.material.icons.outlined.FavoriteBorder
|
||||||
import androidx.compose.material3.Card
|
import androidx.compose.material3.Card
|
||||||
import androidx.compose.material3.CardDefaults
|
import androidx.compose.material3.CardDefaults
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
@ -26,9 +27,6 @@ import androidx.compose.material3.Text
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
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.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
@ -45,18 +43,18 @@ import androidx.lifecycle.viewmodel.compose.viewModel
|
|||||||
import com.ti.mobpo.model.PokemonDetails
|
import com.ti.mobpo.model.PokemonDetails
|
||||||
import coil.compose.AsyncImage
|
import coil.compose.AsyncImage
|
||||||
import coil.request.ImageRequest
|
import coil.request.ImageRequest
|
||||||
import com.ti.mobpo.AppViewModelProvider
|
|
||||||
import com.ti.mobpo.capitalizeFirstLetterAfterHyphens
|
import com.ti.mobpo.capitalizeFirstLetterAfterHyphens
|
||||||
import com.ti.mobpo.ui.theme.MobileSecurityTheme
|
import com.ti.mobpo.ui.theme.MobileSecurityTheme
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun PokeSearchScreen(viewModel: PokeSearchViewModel = viewModel(factory = AppViewModelProvider.Factory)) {
|
fun PokeSearchScreen(viewModel: PokeSearchViewModel) {
|
||||||
PokeSearch(viewModel)
|
PokeSearch(viewModel)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun PokeSearch(pokeSearchViewModel: PokeSearchViewModel) {
|
fun PokeSearch(pokeSearchViewModel: PokeSearchViewModel) {
|
||||||
val searchResults by pokeSearchViewModel.pokemonDetails.collectAsState()
|
val searchResults by pokeSearchViewModel.pokemonDetails.collectAsState()
|
||||||
|
val isLoading by pokeSearchViewModel.isLoading.collectAsState()
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
verticalArrangement = Arrangement.Top,
|
verticalArrangement = Arrangement.Top,
|
||||||
@ -71,6 +69,9 @@ fun PokeSearch(pokeSearchViewModel: PokeSearchViewModel) {
|
|||||||
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
if(isLoading) {
|
||||||
|
CircularProgressIndicator()
|
||||||
|
} else {
|
||||||
searchResults?.let { results ->
|
searchResults?.let { results ->
|
||||||
if (results.isNotEmpty()) {
|
if (results.isNotEmpty()) {
|
||||||
LazyVerticalGrid(
|
LazyVerticalGrid(
|
||||||
@ -90,11 +91,11 @@ fun PokeSearch(pokeSearchViewModel: PokeSearchViewModel) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun PokemonCard(pokemon: PokemonDetails, toggleFavorite: (Int) -> Unit) {
|
fun PokemonCard(pokemon: PokemonDetails, toggleFavorite: (Int) -> Unit) {
|
||||||
var isFavourite by remember { mutableStateOf(false) }
|
|
||||||
Card(
|
Card(
|
||||||
shape = MaterialTheme.shapes.medium,
|
shape = MaterialTheme.shapes.medium,
|
||||||
elevation = CardDefaults.cardElevation(defaultElevation = 8.dp),
|
elevation = CardDefaults.cardElevation(defaultElevation = 8.dp),
|
||||||
@ -130,7 +131,8 @@ fun PokemonCard(pokemon: PokemonDetails, toggleFavorite: (Int) -> Unit) {
|
|||||||
)
|
)
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = { toggleFavorite(pokemon.id) },
|
onClick = { toggleFavorite(pokemon.id) },
|
||||||
modifier = Modifier.wrapContentSize()
|
modifier = Modifier
|
||||||
|
.wrapContentSize()
|
||||||
.layout { measurable, constraints ->
|
.layout { measurable, constraints ->
|
||||||
if (constraints.maxHeight == Constraints.Infinity) {
|
if (constraints.maxHeight == Constraints.Infinity) {
|
||||||
layout(0, 0) {}
|
layout(0, 0) {}
|
||||||
|
18
app/src/main/java/com/ti/mobpo/ui/util/FeatureManager.kt
Normal file
18
app/src/main/java/com/ti/mobpo/ui/util/FeatureManager.kt
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package com.ti.mobpo.ui.util
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
|
||||||
|
class FeatureManager(private val context: Context) {
|
||||||
|
private val PREFS_NAME = "FeaturePrefs"
|
||||||
|
private val KEY_PAID_FEATURE_ENABLED = "paid_feature_enabled"
|
||||||
|
|
||||||
|
fun hasAccessToPaidFeature(): Boolean {
|
||||||
|
val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
||||||
|
return prefs.getBoolean(KEY_PAID_FEATURE_ENABLED, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setPaidFeatureEnabled(enabled: Boolean) {
|
||||||
|
val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
||||||
|
prefs.edit().putBoolean(KEY_PAID_FEATURE_ENABLED, enabled).apply()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,82 @@
|
|||||||
|
package com.ti.mobpo.ui.viewmodels
|
||||||
|
|
||||||
|
import androidx.compose.runtime.MutableState
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
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.FavouriteUiState
|
||||||
|
import com.ti.mobpo.model.PokemonDetails
|
||||||
|
import com.ti.mobpo.network.PokeApi
|
||||||
|
import com.ti.mobpo.ui.util.FeatureManager
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
|
class FavouritesViewModel(private val favouritesRepository: FavouritesRepository,
|
||||||
|
private val featureManager: FeatureManager
|
||||||
|
) : ViewModel() {
|
||||||
|
|
||||||
|
private val service = PokeApi.retrofitService
|
||||||
|
private val _pokemonDetails = MutableStateFlow<List<PokemonDetails>?>(null)
|
||||||
|
val pokemonDetails: StateFlow<List<PokemonDetails>?> = _pokemonDetails.asStateFlow()
|
||||||
|
val accessCheckFailed: MutableState<Boolean> = mutableStateOf(false)
|
||||||
|
|
||||||
|
fun loadFavourites() {
|
||||||
|
// featureManager.setPaidFeatureEnabled(false) enable and disable acccess
|
||||||
|
// Ugly workaround to make sure all the favourites are loaded before displaying them
|
||||||
|
viewModelScope.launch {
|
||||||
|
if(!featureManager.hasAccessToPaidFeature()) {
|
||||||
|
accessCheckFailed.value = true
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
val favouritesList: StateFlow<FavouriteUiState> =
|
||||||
|
favouritesRepository.getAllItems().map { FavouriteUiState(it) }
|
||||||
|
.stateIn(
|
||||||
|
scope = viewModelScope,
|
||||||
|
started = SharingStarted.WhileSubscribed(5_000L),
|
||||||
|
initialValue = FavouriteUiState()
|
||||||
|
)
|
||||||
|
favouritesList.collect { state ->
|
||||||
|
val detailsList = mutableListOf<PokemonDetails>()
|
||||||
|
for (favourite in state.favourites) {
|
||||||
|
val details = service.getPokemonDetails(favourite.id)
|
||||||
|
detailsList.add(details.copy(isFavorite = true))
|
||||||
|
}
|
||||||
|
_pokemonDetails.value = detailsList
|
||||||
|
}
|
||||||
|
} catch (e: IOException) {
|
||||||
|
/* Handle error */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toggleFavorite(pokemonId: Int) {
|
||||||
|
_pokemonDetails.value = _pokemonDetails.value?.map { pokemon ->
|
||||||
|
if (pokemon.id == pokemonId) {
|
||||||
|
pokemon.copy(isFavorite = !pokemon.isFavorite)
|
||||||
|
} else {
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,11 @@
|
|||||||
package com.ti.mobpo.ui.pokesearch
|
package com.ti.mobpo.ui.viewmodels
|
||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.ti.mobpo.data.Favourite
|
import com.ti.mobpo.data.Favourite
|
||||||
import com.ti.mobpo.data.FavouritesRepository
|
import com.ti.mobpo.data.FavouritesRepository
|
||||||
import com.ti.mobpo.model.PokemonDetails
|
import com.ti.mobpo.model.PokemonDetails
|
||||||
|
import com.ti.mobpo.model.PokemonSpecies
|
||||||
import com.ti.mobpo.network.PokeApi
|
import com.ti.mobpo.network.PokeApi
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
@ -13,27 +14,56 @@ import kotlinx.coroutines.launch
|
|||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
|
||||||
|
|
||||||
|
private const val SHOW_LIMIT = 20
|
||||||
class PokeSearchViewModel(private val favouritesRepository: FavouritesRepository) : ViewModel() {
|
class PokeSearchViewModel(private val favouritesRepository: FavouritesRepository) : ViewModel() {
|
||||||
private val service = PokeApi.retrofitService;
|
private val service = PokeApi.retrofitService
|
||||||
|
|
||||||
private val _pokemonDetails = MutableStateFlow<List<PokemonDetails>?>(null)
|
private val _pokemonDetails = MutableStateFlow<List<PokemonDetails>?>(null)
|
||||||
val pokemonDetails: StateFlow<List<PokemonDetails>?> = _pokemonDetails.asStateFlow()
|
val pokemonDetails: StateFlow<List<PokemonDetails>?> = _pokemonDetails.asStateFlow()
|
||||||
|
private val _initialPokemonList = MutableStateFlow<List<PokemonSpecies>?>(null)
|
||||||
|
|
||||||
fun search(query: String) {
|
private val _isLoading = MutableStateFlow(false)
|
||||||
|
val isLoading: StateFlow<Boolean> = _isLoading.asStateFlow()
|
||||||
|
init {
|
||||||
|
fetchPokemonSpecies()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun fetchPokemonSpecies() {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
try {
|
try {
|
||||||
val response = service.getPokemon()
|
val response = service.getPokemon()
|
||||||
val filteredList = response.results.filter { it.name.contains(query, ignoreCase = true) }
|
_initialPokemonList.value = response.results.sortedBy { it.name.lowercase() }
|
||||||
|
} catch (e: IOException) {
|
||||||
|
/*TODO*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun search(query: String) {
|
||||||
|
_isLoading.value = true
|
||||||
|
viewModelScope.launch {
|
||||||
|
try {
|
||||||
|
val firstIndex = _initialPokemonList.value?.indexOfFirst { it.name.startsWith(query, ignoreCase = true) }
|
||||||
|
val lastIndex = _initialPokemonList.value?.indexOfLast { it.name.startsWith(query, ignoreCase = true) }
|
||||||
|
|
||||||
val detailsList = mutableListOf<PokemonDetails>()
|
val detailsList = mutableListOf<PokemonDetails>()
|
||||||
for (pokemonSpecies in filteredList) {
|
|
||||||
|
if (firstIndex != null && lastIndex != null) {
|
||||||
|
val endIndex = minOf(firstIndex + SHOW_LIMIT, lastIndex + 1)
|
||||||
|
val startIndex = maxOf(firstIndex, endIndex - SHOW_LIMIT)
|
||||||
|
|
||||||
|
for (index in startIndex until endIndex) {
|
||||||
|
val pokemonSpecies = _initialPokemonList.value!![index]
|
||||||
val details = service.getPokemonDetails(extractPokemonId(pokemonSpecies.url))
|
val details = service.getPokemonDetails(extractPokemonId(pokemonSpecies.url))
|
||||||
val isFavorite = favouritesRepository.isFavourite(details.id)
|
val isFavorite = favouritesRepository.isFavourite(details.id)
|
||||||
detailsList.add(details.copy(isFavorite = isFavorite))
|
detailsList.add(details.copy(isFavorite = isFavorite))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
_pokemonDetails.value = detailsList
|
_pokemonDetails.value = detailsList
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
/* Handle error */
|
/* Handle error */
|
||||||
|
} finally {
|
||||||
|
_isLoading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user