Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,7 @@ interface GameStateDao {

@Query("DELETE FROM $GAME_STATE_TABLE")
suspend fun deleteGameState()

@Query("UPDATE $GAME_STATE_TABLE SET usedHints = :usedHints")
suspend fun updateUsedHints(usedHints: Set<Int>)
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,8 @@ class GameStateRepositoryImpl @Inject constructor(
override suspend fun getGameState(): GameState? {
return gameStateDao.getGameState()?.toDomain()
}

override suspend fun updateUsedHints(usedHints: Set<Int>) {
gameStateDao.updateUsedHints(usedHints)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,6 @@ interface GameStateRepository {
suspend fun finishGame(onFinished: () -> Unit = {})

suspend fun getGameState(): GameState?

suspend fun updateUsedHints(usedHints: Set<Int>)
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.google.android.material.snackbar.Snackbar
import com.nextroom.nextroom.presentation.common.NRSnackbar
import com.nextroom.nextroom.presentation.util.Logger
Expand Down Expand Up @@ -111,3 +114,34 @@ fun Fragment.enableFullScreen(
fun Fragment.disableFullScreen() {
(requireActivity() as? WindowInsetsManager)?.disableFullScreen()
}

/**
* AssistedInject를 사용하는 ViewModel을 쉽게 생성하기 위한 확장 함수
*
* ## 사용 예시
* ```kotlin
* @Inject
* lateinit var viewModelFactory: HintViewModel.Factory
*
* private val gameSharedViewModel: GameSharedViewModel by hiltNavGraphViewModels(R.id.game_navigation)
*
* override val viewModel: HintViewModel by assistedViewModel {
* viewModelFactory.create(gameSharedViewModel)
* }
* ```
*
* @param factory ViewModel을 생성하는 람다 함수
* @return ViewModel의 Lazy 인스턴스
*/
inline fun <reified VM : ViewModel> Fragment.assistedViewModel(
crossinline factory: () -> VM
): Lazy<VM> {
return viewModels {
object : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
@Suppress("UNCHECKED_CAST")
return factory() as T
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ data class Hint(
val progress: Int = 0,
val hint: String = "",
val answer: String = "",
val answerOpened: Boolean = false,
val hintImageUrlList: List<String> = emptyList(),
val answerImageUrlList: List<String> = emptyList()
) : Serializable
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package com.nextroom.nextroom.presentation.ui.hint

sealed interface HintEvent {
data object OpenAnswer : HintEvent
data object NetworkError : HintEvent
data object UnknownError : HintEvent
data class ClientError(val message: String) : HintEvent
data object HintLimitExceed : HintEvent
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.core.os.bundleOf
import androidx.fragment.app.viewModels
import androidx.hilt.navigation.fragment.hiltNavGraphViewModels
import androidx.navigation.fragment.findNavController
import com.google.firebase.analytics.FirebaseAnalytics
import com.nextroom.nextroom.domain.model.SubscribeStatus
import com.nextroom.nextroom.presentation.NavGraphDirections
import com.nextroom.nextroom.presentation.R
import com.nextroom.nextroom.presentation.base.ComposeBaseViewModelFragment
import com.nextroom.nextroom.presentation.extension.assistedViewModel
import com.nextroom.nextroom.presentation.extension.enableFullScreen
import com.nextroom.nextroom.presentation.extension.repeatOnStarted
import com.nextroom.nextroom.presentation.extension.safeNavigate
Expand All @@ -30,13 +30,21 @@ import com.nextroom.nextroom.presentation.ui.hint.compose.HintTimerToolbar
import com.nextroom.nextroom.presentation.ui.main.GameSharedViewModel
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
import javax.inject.Inject

@AndroidEntryPoint
class HintFragment : ComposeBaseViewModelFragment<HintViewModel>() {
override val screenName: String = "hint"
override val viewModel: HintViewModel by viewModels()

@Inject
lateinit var viewModelFactory: HintViewModel.Factory

private val gameSharedViewModel: GameSharedViewModel by hiltNavGraphViewModels(R.id.game_navigation)

override val viewModel: HintViewModel by assistedViewModel {
viewModelFactory.create(gameSharedViewModel)
}

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
Expand All @@ -58,9 +66,10 @@ class HintFragment : ComposeBaseViewModelFragment<HintViewModel>() {

HintScreen(
state = state,
onAnswerButtonClick = ::handleAnswerButton,
onHintImageClick = ::navigateToHintImageViewer,
onAnswerImageClick = ::navigateToAnswerImageViewer
onAnswerImageClick = ::navigateToAnswerImageViewer,
onHintOpenClick = { viewModel.tryOpenHint(state.hint.id) },
onAnswerOpenClick = { gameSharedViewModel.addOpenedAnswerId(state.hint.id) }
)
}
}
Expand All @@ -76,30 +85,12 @@ class HintFragment : ComposeBaseViewModelFragment<HintViewModel>() {

override fun initSubscribe() {
viewLifecycleOwner.repeatOnStarted {
launch {
gameSharedViewModel.currentHint.collect { hint ->
hint?.let { viewModel.setHint(it) }
}
}
launch {
gameSharedViewModel.subscribeStatus.collect { subscribeStatus ->
viewModel.setSubscribeStatus(subscribeStatus)
}
}
launch {
viewModel.uiEvent.collect(::handleEvent)
}
}
}

private fun handleAnswerButton() {
if (viewModel.uiState.value.hint.answerOpened) {
gotoHome()
} else {
viewModel.openAnswer()
}
}

private fun navigateToHintImageViewer(position: Int) {
val state = viewModel.uiState.value
if (state.userSubscribeStatus == SubscribeStatus.Subscribed) {
Expand Down Expand Up @@ -135,10 +126,10 @@ class HintFragment : ComposeBaseViewModelFragment<HintViewModel>() {

private fun handleEvent(event: HintEvent) {
when (event) {
HintEvent.OpenAnswer -> viewModel.openAnswer()
is HintEvent.NetworkError -> snackbar(R.string.error_network)
is HintEvent.UnknownError -> snackbar(R.string.error_something)
is HintEvent.ClientError -> snackbar(event.message)
is HintEvent.HintLimitExceed -> snackbar(R.string.game_hint_limit_exceed)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,8 @@ data class HintState(
val loading: Boolean = false,
val hint: Hint = Hint(),
val userSubscribeStatus: SubscribeStatus = SubscribeStatus.Default,
val networkDisconnectedCount: Int = 0
val networkDisconnectedCount: Int = 0,
val isHintOpened: Boolean = false,
val isAnswerOpened: Boolean = false,
val totalHintCount: Int = -1
)
Original file line number Diff line number Diff line change
@@ -1,26 +1,43 @@
package com.nextroom.nextroom.presentation.ui.hint

import com.nextroom.nextroom.domain.model.SubscribeStatus
import com.nextroom.nextroom.domain.repository.DataStoreRepository
import com.nextroom.nextroom.domain.repository.TimerRepository
import com.nextroom.nextroom.presentation.base.NewBaseViewModel
import com.nextroom.nextroom.presentation.model.Hint
import dagger.hilt.android.lifecycle.HiltViewModel
import com.nextroom.nextroom.presentation.ui.main.GameSharedViewModel
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import javax.inject.Inject

@HiltViewModel
class HintViewModel @Inject constructor(
class HintViewModel @AssistedInject constructor(
private val timerRepository: TimerRepository,
private val dataStoreRepository: DataStoreRepository,
@Assisted private val gameSharedViewModel: GameSharedViewModel
) : NewBaseViewModel() {

private val _uiState = MutableStateFlow(HintState())
val uiState = _uiState.asStateFlow()
val uiState = combine(
_uiState,
gameSharedViewModel.state
) { state, gameSharedState ->
state.copy(
hint = gameSharedState.currentHint ?: state.hint,
userSubscribeStatus = gameSharedState.subscribeStatus,
isHintOpened = (gameSharedState.currentHint?.id ?: state.hint.id) in gameSharedState.openedHintIds,
isAnswerOpened = (gameSharedState.currentHint?.id ?: state.hint.id) in gameSharedState.openedAnswerIds,
totalHintCount = gameSharedState.totalHintCount
)
}.stateIn(
baseViewModelScope,
SharingStarted.Lazily,
_uiState.value
)

private val _uiEvent = MutableSharedFlow<HintEvent>(extraBufferCapacity = 1)
val uiEvent = _uiEvent.asSharedFlow()
Expand All @@ -41,19 +58,19 @@ class HintViewModel @Inject constructor(
_uiState.value = _uiState.value.copy(networkDisconnectedCount = count)
}

fun setHint(hint: Hint) {
_uiState.value = _uiState.value.copy(
hint = hint.copy(answerOpened = _uiState.value.hint.answerOpened)
)
}
fun tryOpenHint(hintId: Int) {
val openedCount = gameSharedViewModel.getOpenedHintCount()
val openableHintCount = uiState.value.totalHintCount

fun setSubscribeStatus(subscribeStatus: SubscribeStatus) {
_uiState.value = _uiState.value.copy(userSubscribeStatus = subscribeStatus)
if (openableHintCount == -1 || openedCount < openableHintCount) {
gameSharedViewModel.addOpenedHintId(hintId)
} else {
_uiEvent.tryEmit(HintEvent.HintLimitExceed)
}
}

fun openAnswer() {
_uiState.value = _uiState.value.copy(
hint = _uiState.value.hint.copy(answerOpened = true)
)
@AssistedFactory
interface Factory {
fun create(gameSharedViewModel: GameSharedViewModel): HintViewModel
}
}
Loading