HomeViewModel.kt

package com.gyleedev.githubsearch.feature.home

import android.os.Build
import androidx.annotation.RequiresExtension
import androidx.lifecycle.viewModelScope
import androidx.paging.cachedIn
import com.gyleedev.githubsearch.domain.model.SearchStatus
import com.gyleedev.githubsearch.domain.model.UserModel
import com.gyleedev.githubsearch.domain.usecase.FetchUserUseCase
import com.gyleedev.githubsearch.domain.usecase.GetUserWithFlowUseCase
import com.gyleedev.githubsearch.domain.usecase.GetUsersUseCase
import com.gyleedev.ui.BaseViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import javax.inject.Inject

/*
    BaseViewModel의 에러처리법 보고 에러처리 개선안 고안
    1. UserList 가져오는거
    2. Search 했을 때 query 가지고 debounce 로 가져오는거
    3. web fetch 해서 User 가져오는거
 */
@HiltViewModel
class HomeViewModel @Inject constructor(
    getUsersUseCase: GetUsersUseCase,
    private val getUserWithFlowUseCase: GetUserWithFlowUseCase,
    private val fetchUserUseCase: FetchUserUseCase,
) : BaseViewModel() {
    private val searchQuery = MutableStateFlow("")
    private val isLoading = MutableStateFlow(false)
    private val isSearchActivated = MutableStateFlow(false)
    private val showRequestAuthDialog = MutableStateFlow(false)

    @OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class)
    private val searchedUser: StateFlow<UserModel?> = searchQuery
        .debounce(300L)
        .distinctUntilChanged()
        .flatMapLatest { query ->
            if (query.isBlank()) {
                flowOf(null)
            } else {
                getUserWithFlowUseCase(query)
            }
        }.stateIn(
            scope = viewModelScope,
            started = SharingStarted.WhileSubscribed(stopTimeoutMillis = 5000),
            initialValue = null,
        )

    val users = getUsersUseCase().cachedIn(viewModelScope)

    val uiState = combine(searchQuery, searchedUser, isLoading, isSearchActivated, showRequestAuthDialog) { query, user, isLoading, searchActivated, showAuth ->
        val searchResult = if (user == null) {
            SearchUiState.Empty
        } else {
            SearchUiState.Success(
                login = user.login,
                avatar = user.avatar,
                name = user.name,
                bio = user.bio,
            )
        }
        HomeUiState.Success(
            searchQuery = query,
            isLoading = isLoading,
            searchState = searchResult,
            isSearchActive = searchActivated,
            showRequestAuthDialog = showAuth,
        )
    }.stateIn(
        scope = viewModelScope,
        started = SharingStarted.WhileSubscribed(stopTimeoutMillis = 5_000),
        initialValue = HomeUiState.Loading,
    )

    @RequiresExtension(extension = Build.VERSION_CODES.S, version = 7)
    fun searchUser(id: String) {
        viewModelScope.launch(exceptionHandler) {
            isLoading.emit(true)
            val status = fetchUserUseCase(id)
            if (status == SearchStatus.NEED_AUTHENTICATION) {
                changeDialogState(true)
            }
            isLoading.emit(false)
        }
    }

    @RequiresExtension(extension = Build.VERSION_CODES.S, version = 7)
    fun updateSearchId(id: String) {
        viewModelScope.launch(exceptionHandler) {
            searchQuery.emit(id)
        }
    }

    @RequiresExtension(extension = Build.VERSION_CODES.S, version = 7)
    fun changeSearchBarState(state: Boolean) {
        viewModelScope.launch(exceptionHandler) {
            isSearchActivated.emit(state)
        }
    }

    @RequiresExtension(extension = Build.VERSION_CODES.S, version = 7)
    fun changeDialogState(state: Boolean) {
        viewModelScope.launch(exceptionHandler) {
            showRequestAuthDialog.emit(state)
        }
    }
}