Skip to content

Commit 24c4215

Browse files
Merge pull request #131 from Next-Room/feature/NR-130
NR-130 힌트 사용 기능 리뉴얼
2 parents 0593024 + df78e0b commit 24c4215

File tree

17 files changed

+362
-208
lines changed

17 files changed

+362
-208
lines changed

data/src/main/java/com/nextroom/nextroom/data/db/GameStateDao.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,7 @@ interface GameStateDao {
1717

1818
@Query("DELETE FROM $GAME_STATE_TABLE")
1919
suspend fun deleteGameState()
20+
21+
@Query("UPDATE $GAME_STATE_TABLE SET usedHints = :usedHints")
22+
suspend fun updateUsedHints(usedHints: Set<Int>)
2023
}

data/src/main/java/com/nextroom/nextroom/data/repository/GameStateRepositoryImpl.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,8 @@ class GameStateRepositoryImpl @Inject constructor(
4848
override suspend fun getGameState(): GameState? {
4949
return gameStateDao.getGameState()?.toDomain()
5050
}
51+
52+
override suspend fun updateUsedHints(usedHints: Set<Int>) {
53+
gameStateDao.updateUsedHints(usedHints)
54+
}
5155
}

domain/src/main/java/com/nextroom/nextroom/domain/repository/GameStateRepository.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,6 @@ interface GameStateRepository {
1818
suspend fun finishGame(onFinished: () -> Unit = {})
1919

2020
suspend fun getGameState(): GameState?
21+
22+
suspend fun updateUsedHints(usedHints: Set<Int>)
2123
}

presentation/src/main/java/com/nextroom/nextroom/presentation/extension/Fragment.kt

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ import androidx.core.view.ViewCompat
66
import androidx.core.view.WindowInsetsCompat
77
import androidx.core.view.updatePadding
88
import androidx.fragment.app.Fragment
9+
import androidx.fragment.app.viewModels
910
import androidx.lifecycle.Lifecycle
11+
import androidx.lifecycle.ViewModel
12+
import androidx.lifecycle.ViewModelProvider
1013
import com.google.android.material.snackbar.Snackbar
1114
import com.nextroom.nextroom.presentation.common.NRSnackbar
1215
import com.nextroom.nextroom.presentation.util.Logger
@@ -111,3 +114,34 @@ fun Fragment.enableFullScreen(
111114
fun Fragment.disableFullScreen() {
112115
(requireActivity() as? WindowInsetsManager)?.disableFullScreen()
113116
}
117+
118+
/**
119+
* AssistedInject를 사용하는 ViewModel을 쉽게 생성하기 위한 확장 함수
120+
*
121+
* ## 사용 예시
122+
* ```kotlin
123+
* @Inject
124+
* lateinit var viewModelFactory: HintViewModel.Factory
125+
*
126+
* private val gameSharedViewModel: GameSharedViewModel by hiltNavGraphViewModels(R.id.game_navigation)
127+
*
128+
* override val viewModel: HintViewModel by assistedViewModel {
129+
* viewModelFactory.create(gameSharedViewModel)
130+
* }
131+
* ```
132+
*
133+
* @param factory ViewModel을 생성하는 람다 함수
134+
* @return ViewModel의 Lazy 인스턴스
135+
*/
136+
inline fun <reified VM : ViewModel> Fragment.assistedViewModel(
137+
crossinline factory: () -> VM
138+
): Lazy<VM> {
139+
return viewModels {
140+
object : ViewModelProvider.Factory {
141+
override fun <T : ViewModel> create(modelClass: Class<T>): T {
142+
@Suppress("UNCHECKED_CAST")
143+
return factory() as T
144+
}
145+
}
146+
}
147+
}

presentation/src/main/java/com/nextroom/nextroom/presentation/model/Hint.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ data class Hint(
77
val progress: Int = 0,
88
val hint: String = "",
99
val answer: String = "",
10-
val answerOpened: Boolean = false,
1110
val hintImageUrlList: List<String> = emptyList(),
1211
val answerImageUrlList: List<String> = emptyList()
1312
) : Serializable
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
package com.nextroom.nextroom.presentation.ui.hint
22

33
sealed interface HintEvent {
4-
data object OpenAnswer : HintEvent
54
data object NetworkError : HintEvent
65
data object UnknownError : HintEvent
76
data class ClientError(val message: String) : HintEvent
7+
data object HintLimitExceed : HintEvent
88
}

presentation/src/main/java/com/nextroom/nextroom/presentation/ui/hint/HintFragment.kt

Lines changed: 14 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,14 @@ import androidx.compose.ui.Modifier
1212
import androidx.compose.ui.platform.ComposeView
1313
import androidx.compose.ui.platform.ViewCompositionStrategy
1414
import androidx.core.os.bundleOf
15-
import androidx.fragment.app.viewModels
1615
import androidx.hilt.navigation.fragment.hiltNavGraphViewModels
1716
import androidx.navigation.fragment.findNavController
1817
import com.google.firebase.analytics.FirebaseAnalytics
1918
import com.nextroom.nextroom.domain.model.SubscribeStatus
2019
import com.nextroom.nextroom.presentation.NavGraphDirections
2120
import com.nextroom.nextroom.presentation.R
2221
import com.nextroom.nextroom.presentation.base.ComposeBaseViewModelFragment
22+
import com.nextroom.nextroom.presentation.extension.assistedViewModel
2323
import com.nextroom.nextroom.presentation.extension.enableFullScreen
2424
import com.nextroom.nextroom.presentation.extension.repeatOnStarted
2525
import com.nextroom.nextroom.presentation.extension.safeNavigate
@@ -30,13 +30,21 @@ import com.nextroom.nextroom.presentation.ui.hint.compose.HintTimerToolbar
3030
import com.nextroom.nextroom.presentation.ui.main.GameSharedViewModel
3131
import dagger.hilt.android.AndroidEntryPoint
3232
import kotlinx.coroutines.launch
33+
import javax.inject.Inject
3334

3435
@AndroidEntryPoint
3536
class HintFragment : ComposeBaseViewModelFragment<HintViewModel>() {
3637
override val screenName: String = "hint"
37-
override val viewModel: HintViewModel by viewModels()
38+
39+
@Inject
40+
lateinit var viewModelFactory: HintViewModel.Factory
41+
3842
private val gameSharedViewModel: GameSharedViewModel by hiltNavGraphViewModels(R.id.game_navigation)
3943

44+
override val viewModel: HintViewModel by assistedViewModel {
45+
viewModelFactory.create(gameSharedViewModel)
46+
}
47+
4048
override fun onCreateView(
4149
inflater: LayoutInflater,
4250
container: ViewGroup?,
@@ -58,9 +66,10 @@ class HintFragment : ComposeBaseViewModelFragment<HintViewModel>() {
5866

5967
HintScreen(
6068
state = state,
61-
onAnswerButtonClick = ::handleAnswerButton,
6269
onHintImageClick = ::navigateToHintImageViewer,
63-
onAnswerImageClick = ::navigateToAnswerImageViewer
70+
onAnswerImageClick = ::navigateToAnswerImageViewer,
71+
onHintOpenClick = { viewModel.tryOpenHint(state.hint.id) },
72+
onAnswerOpenClick = { gameSharedViewModel.addOpenedAnswerId(state.hint.id) }
6473
)
6574
}
6675
}
@@ -76,30 +85,12 @@ class HintFragment : ComposeBaseViewModelFragment<HintViewModel>() {
7685

7786
override fun initSubscribe() {
7887
viewLifecycleOwner.repeatOnStarted {
79-
launch {
80-
gameSharedViewModel.currentHint.collect { hint ->
81-
hint?.let { viewModel.setHint(it) }
82-
}
83-
}
84-
launch {
85-
gameSharedViewModel.subscribeStatus.collect { subscribeStatus ->
86-
viewModel.setSubscribeStatus(subscribeStatus)
87-
}
88-
}
8988
launch {
9089
viewModel.uiEvent.collect(::handleEvent)
9190
}
9291
}
9392
}
9493

95-
private fun handleAnswerButton() {
96-
if (viewModel.uiState.value.hint.answerOpened) {
97-
gotoHome()
98-
} else {
99-
viewModel.openAnswer()
100-
}
101-
}
102-
10394
private fun navigateToHintImageViewer(position: Int) {
10495
val state = viewModel.uiState.value
10596
if (state.userSubscribeStatus == SubscribeStatus.Subscribed) {
@@ -135,10 +126,10 @@ class HintFragment : ComposeBaseViewModelFragment<HintViewModel>() {
135126

136127
private fun handleEvent(event: HintEvent) {
137128
when (event) {
138-
HintEvent.OpenAnswer -> viewModel.openAnswer()
139129
is HintEvent.NetworkError -> snackbar(R.string.error_network)
140130
is HintEvent.UnknownError -> snackbar(R.string.error_something)
141131
is HintEvent.ClientError -> snackbar(event.message)
132+
is HintEvent.HintLimitExceed -> snackbar(R.string.game_hint_limit_exceed)
142133
}
143134
}
144135

presentation/src/main/java/com/nextroom/nextroom/presentation/ui/hint/HintState.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,8 @@ data class HintState(
77
val loading: Boolean = false,
88
val hint: Hint = Hint(),
99
val userSubscribeStatus: SubscribeStatus = SubscribeStatus.Default,
10-
val networkDisconnectedCount: Int = 0
10+
val networkDisconnectedCount: Int = 0,
11+
val isHintOpened: Boolean = false,
12+
val isAnswerOpened: Boolean = false,
13+
val totalHintCount: Int = -1
1114
)
Lines changed: 36 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,43 @@
11
package com.nextroom.nextroom.presentation.ui.hint
22

3-
import com.nextroom.nextroom.domain.model.SubscribeStatus
43
import com.nextroom.nextroom.domain.repository.DataStoreRepository
54
import com.nextroom.nextroom.domain.repository.TimerRepository
65
import com.nextroom.nextroom.presentation.base.NewBaseViewModel
7-
import com.nextroom.nextroom.presentation.model.Hint
8-
import dagger.hilt.android.lifecycle.HiltViewModel
6+
import com.nextroom.nextroom.presentation.ui.main.GameSharedViewModel
7+
import dagger.assisted.Assisted
8+
import dagger.assisted.AssistedFactory
9+
import dagger.assisted.AssistedInject
910
import kotlinx.coroutines.flow.MutableSharedFlow
1011
import kotlinx.coroutines.flow.MutableStateFlow
12+
import kotlinx.coroutines.flow.SharingStarted
1113
import kotlinx.coroutines.flow.asSharedFlow
12-
import kotlinx.coroutines.flow.asStateFlow
14+
import kotlinx.coroutines.flow.combine
15+
import kotlinx.coroutines.flow.stateIn
1316
import kotlinx.coroutines.launch
14-
import javax.inject.Inject
1517

16-
@HiltViewModel
17-
class HintViewModel @Inject constructor(
18+
class HintViewModel @AssistedInject constructor(
1819
private val timerRepository: TimerRepository,
1920
private val dataStoreRepository: DataStoreRepository,
21+
@Assisted private val gameSharedViewModel: GameSharedViewModel
2022
) : NewBaseViewModel() {
2123

2224
private val _uiState = MutableStateFlow(HintState())
23-
val uiState = _uiState.asStateFlow()
25+
val uiState = combine(
26+
_uiState,
27+
gameSharedViewModel.state
28+
) { state, gameSharedState ->
29+
state.copy(
30+
hint = gameSharedState.currentHint ?: state.hint,
31+
userSubscribeStatus = gameSharedState.subscribeStatus,
32+
isHintOpened = (gameSharedState.currentHint?.id ?: state.hint.id) in gameSharedState.openedHintIds,
33+
isAnswerOpened = (gameSharedState.currentHint?.id ?: state.hint.id) in gameSharedState.openedAnswerIds,
34+
totalHintCount = gameSharedState.totalHintCount
35+
)
36+
}.stateIn(
37+
baseViewModelScope,
38+
SharingStarted.Lazily,
39+
_uiState.value
40+
)
2441

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

44-
fun setHint(hint: Hint) {
45-
_uiState.value = _uiState.value.copy(
46-
hint = hint.copy(answerOpened = _uiState.value.hint.answerOpened)
47-
)
48-
}
61+
fun tryOpenHint(hintId: Int) {
62+
val openedCount = gameSharedViewModel.getOpenedHintCount()
63+
val openableHintCount = uiState.value.totalHintCount
4964

50-
fun setSubscribeStatus(subscribeStatus: SubscribeStatus) {
51-
_uiState.value = _uiState.value.copy(userSubscribeStatus = subscribeStatus)
65+
if (openableHintCount == -1 || openedCount < openableHintCount) {
66+
gameSharedViewModel.addOpenedHintId(hintId)
67+
} else {
68+
_uiEvent.tryEmit(HintEvent.HintLimitExceed)
69+
}
5270
}
5371

54-
fun openAnswer() {
55-
_uiState.value = _uiState.value.copy(
56-
hint = _uiState.value.hint.copy(answerOpened = true)
57-
)
72+
@AssistedFactory
73+
interface Factory {
74+
fun create(gameSharedViewModel: GameSharedViewModel): HintViewModel
5875
}
5976
}

0 commit comments

Comments
 (0)