package com.streamflixreborn.streamflix.fragments.genre import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.streamflixreborn.streamflix.database.AppDatabase import com.streamflixreborn.streamflix.models.Genre import com.streamflixreborn.streamflix.models.Movie import com.streamflixreborn.streamflix.models.TvShow import com.streamflixreborn.streamflix.utils.ParentalControlUtils import com.streamflixreborn.streamflix.utils.UserPreferences import com.streamflixreborn.streamflix.utils.ProviderChangeNotifier import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.emitAll import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.transformLatest import kotlinx.coroutines.launch class GenreViewModel(private val id: String, database: AppDatabase) : ViewModel() { private val _state = MutableStateFlow(State.Loading) init { // Listen for provider changes and reload data viewModelScope.launch { ProviderChangeNotifier.providerChangeFlow.collect { getGenre(id) } } } @OptIn(ExperimentalCoroutinesApi::class) val state: Flow = combine( _state, _state.transformLatest { state -> when (state) { is State.SuccessLoading -> { val movies = state.genre.shows .filterIsInstance() if (movies.isEmpty()) { emit(emptyList()) } else { emitAll(database.movieDao().getByIds(movies.map { it.id })) } } else -> emit(emptyList()) } }, _state.transformLatest { state -> when (state) { is State.SuccessLoading -> { val tvShows = state.genre.shows .filterIsInstance() if (tvShows.isEmpty()) { emit(emptyList()) } else { emitAll(database.tvShowDao().getByIds(tvShows.map { it.id })) } } else -> emit(emptyList()) } }, ) { state, moviesDb, tvShowsDb -> when (state) { is State.SuccessLoading -> { val moviesById = moviesDb.associateBy { it.id } val tvShowsById = tvShowsDb.associateBy { it.id } State.SuccessLoading( genre = state.genre.copy( shows = state.genre.shows.map { item -> when (item) { is Movie -> moviesById[item.id] ?.takeIf { !item.isSame(it) } ?.let { item.copy().merge(it) } ?: item is TvShow -> tvShowsById[item.id] ?.takeIf { !item.isSame(it) } ?.let { item.copy().merge(it) } ?: item } } ), hasMore = state.hasMore ) } else -> state } }.flowOn(Dispatchers.IO) private var page = 1 sealed class State { data object Loading : State() data object LoadingMore : State() data class SuccessLoading(val genre: Genre, val hasMore: Boolean) : State() data class FailedLoading(val error: Exception) : State() } init { getGenre(id) } fun getGenre(id: String) = viewModelScope.launch(Dispatchers.IO) { _state.emit(State.Loading) try { val genre = UserPreferences.currentProvider!!.getGenre(id).let { it.copy(shows = ParentalControlUtils.filterShows(it.shows)) } page = 1 _state.emit(State.SuccessLoading(genre, true)) } catch (e: Exception) { Log.e("GenreViewModel", "getGenre: ", e) _state.emit(State.FailedLoading(e)) } } fun loadMoreGenreShows() = viewModelScope.launch(Dispatchers.IO) { val currentState = _state.value if (currentState is State.SuccessLoading) { _state.emit(State.LoadingMore) try { val genre = UserPreferences.currentProvider!!.getGenre(id, page + 1).let { it.copy(shows = ParentalControlUtils.filterShows(it.shows)) } page += 1 _state.emit( State.SuccessLoading( genre = Genre( id = genre.id, name = genre.name, shows = currentState.genre.shows + genre.shows, ), hasMore = genre.shows.isNotEmpty(), ) ) } catch (e: Exception) { Log.e("GenreViewModel", "loadMoreGenreShows: ", e) _state.emit(State.FailedLoading(e)) } } } }