ExploreViewModel.kt

package com.louisfn.somovie.feature.home.explore

import androidx.annotation.AnyThread
import androidx.lifecycle.viewModelScope
import com.louisfn.somovie.core.common.Result
import com.louisfn.somovie.core.common.annotation.DefaultDispatcher
import com.louisfn.somovie.core.common.asFlowResult
import com.louisfn.somovie.core.common.extension.safeCollect
import com.louisfn.somovie.core.common.isError
import com.louisfn.somovie.core.common.isLoading
import com.louisfn.somovie.core.common.onResultError
import com.louisfn.somovie.domain.model.ExploreCategory
import com.louisfn.somovie.domain.model.Movie
import com.louisfn.somovie.domain.usecase.movie.MovieInteractor
import com.louisfn.somovie.ui.common.base.BaseViewModel
import com.louisfn.somovie.ui.common.base.NoneAction
import com.louisfn.somovie.ui.common.error.ErrorsDispatcher
import com.louisfn.somovie.ui.common.model.ImmutableList
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import javax.inject.Inject

@HiltViewModel
internal class ExploreViewModel @Inject constructor(
    @DefaultDispatcher defaultDispatcher: CoroutineDispatcher,
    private val movieInteractor: MovieInteractor,
    private val errorsDispatcher: ErrorsDispatcher,
) : BaseViewModel<NoneAction>(defaultDispatcher) {

    private val refreshResultState = MutableStateFlow<Result<Unit>?>(null)

    val uiState: StateFlow<ExploreUiState> =
        combine(
            movieInteractor.exploreMoviesChanges()
                .map { ImmutableList(it.map { pair -> pair.first to ImmutableList(pair.second) }) },
            refreshResultState,
        ) { movies, refreshResult ->
            createUiState(movies, refreshResult)
        }
            .stateIn(
                scope = viewModelScope,
                started = SharingStarted.WhileSubscribed(),
                initialValue = ExploreUiState.None,
            )

    init {
        refresh()
    }

    @AnyThread
    private fun createUiState(
        moviesByCategory: ImmutableList<Pair<ExploreCategory, ImmutableList<Movie>>>,
        refreshResult: Result<Unit>?,
    ): ExploreUiState = when {
        moviesByCategory.none { it.second.isEmpty() } ->
            ExploreUiState.Explore(moviesByCategory)
        refreshResult.isLoading ->
            ExploreUiState.Loading
        refreshResult.isError ->
            ExploreUiState.Retry
        else ->
            ExploreUiState.None
    }

    @AnyThread
    fun refresh() {
        viewModelScope.launch(defaultDispatcher) {
            asFlowResult { movieInteractor.refreshExploreMovies() }
                .onResultError(errorsDispatcher::dispatch)
                .safeCollect(
                    onEach = refreshResultState::emit,
                    onError = errorsDispatcher::dispatch,
                )
        }
    }
}