Skip to content

Commit a0ce38b

Browse files
committed
Refactor: HomeViewModel 단순화
1 parent efae7ac commit a0ce38b

2 files changed

Lines changed: 53 additions & 206 deletions

File tree

presentation/src/main/java/com/threegap/bitnagil/presentation/screen/home/HomeViewModel.kt

Lines changed: 53 additions & 201 deletions
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,22 @@ package com.threegap.bitnagil.presentation.screen.home
33
import android.util.Log
44
import androidx.lifecycle.ViewModel
55
import com.threegap.bitnagil.domain.emotion.usecase.ObserveDailyEmotionUseCase
6-
import com.threegap.bitnagil.domain.onboarding.usecase.GetOnBoardingRecommendRoutineEventFlowUseCase
7-
import com.threegap.bitnagil.domain.routine.model.RoutineCompletionInfo
8-
import com.threegap.bitnagil.domain.routine.model.RoutineCompletionInfos
9-
import com.threegap.bitnagil.domain.routine.usecase.FetchWeeklyRoutinesUseCase
10-
import com.threegap.bitnagil.domain.routine.usecase.GetWriteRoutineEventFlowUseCase
11-
import com.threegap.bitnagil.domain.routine.usecase.RoutineCompletionUseCase
12-
import com.threegap.bitnagil.domain.routine.usecase.ToggleRoutineUseCase
6+
import com.threegap.bitnagil.domain.routine.model.ToggleStrategy
7+
import com.threegap.bitnagil.domain.routine.usecase.ApplyRoutineToggleUseCase
8+
import com.threegap.bitnagil.domain.routine.usecase.ObserveWeeklyRoutinesUseCase
139
import com.threegap.bitnagil.domain.user.usecase.ObserveUserProfileUseCase
1410
import com.threegap.bitnagil.presentation.screen.home.contract.HomeSideEffect
1511
import com.threegap.bitnagil.presentation.screen.home.contract.HomeState
16-
import com.threegap.bitnagil.presentation.screen.home.model.ToggleStrategy
1712
import com.threegap.bitnagil.presentation.screen.home.model.toUiModel
1813
import com.threegap.bitnagil.presentation.screen.home.util.getCurrentWeekDays
1914
import dagger.hilt.android.lifecycle.HiltViewModel
15+
import kotlinx.coroutines.ExperimentalCoroutinesApi
2016
import kotlinx.coroutines.FlowPreview
21-
import kotlinx.coroutines.coroutineScope
22-
import kotlinx.coroutines.flow.MutableSharedFlow
17+
import kotlinx.coroutines.flow.catch
2318
import kotlinx.coroutines.flow.debounce
2419
import kotlinx.coroutines.flow.distinctUntilChanged
25-
import kotlinx.coroutines.flow.drop
20+
import kotlinx.coroutines.flow.flatMapLatest
2621
import kotlinx.coroutines.flow.map
27-
import kotlinx.coroutines.launch
2822
import org.orbitmvi.orbit.Container
2923
import org.orbitmvi.orbit.ContainerHost
3024
import org.orbitmvi.orbit.viewmodel.container
@@ -33,145 +27,110 @@ import javax.inject.Inject
3327

3428
@HiltViewModel
3529
class HomeViewModel @Inject constructor(
36-
private val fetchWeeklyRoutinesUseCase: FetchWeeklyRoutinesUseCase,
30+
private val observeWeeklyRoutinesUseCase: ObserveWeeklyRoutinesUseCase,
3731
private val observeUserProfileUseCase: ObserveUserProfileUseCase,
3832
private val observeDailyEmotionUseCase: ObserveDailyEmotionUseCase,
39-
private val routineCompletionUseCase: RoutineCompletionUseCase,
40-
private val getWriteRoutineEventFlowUseCase: GetWriteRoutineEventFlowUseCase,
41-
private val getOnBoardingRecommendRoutineEventFlowUseCase: GetOnBoardingRecommendRoutineEventFlowUseCase,
42-
private val toggleRoutineUseCase: ToggleRoutineUseCase,
33+
private val applyRoutineToggleUseCase: ApplyRoutineToggleUseCase,
4334
) : ContainerHost<HomeState, HomeSideEffect>, ViewModel() {
4435

4536
override val container: Container<HomeState, HomeSideEffect> =
4637
container(
4738
initialState = HomeState.INIT,
4839
buildSettings = { repeatOnSubscribedStopTimeout = 5_000L },
40+
onCreate = {
41+
observeDailyEmotion()
42+
observeUserProfile()
43+
observeWeeklyRoutines()
44+
},
4945
)
5046

51-
private val pendingChangesByDate = mutableMapOf<String, MutableMap<String, RoutineCompletionInfo>>()
52-
private val routineSyncTrigger = MutableSharedFlow<String>(extraBufferCapacity = 64)
53-
54-
init {
55-
initialize()
56-
observeDailyEmotion()
57-
}
58-
5947
private fun observeDailyEmotion() {
6048
intent {
6149
repeatOnSubscription {
6250
observeDailyEmotionUseCase().collect { result ->
6351
result.fold(
64-
onSuccess = { dailyEmotion ->
65-
reduce { state.copy(dailyEmotion = dailyEmotion.toUiModel()) }
66-
},
52+
onSuccess = { reduce { state.copy(dailyEmotion = it.toUiModel()) } },
6753
onFailure = {},
6854
)
6955
}
7056
}
7157
}
7258
}
7359

74-
fun selectDate(data: LocalDate) {
60+
private fun observeUserProfile() {
7561
intent {
76-
val previousDateKey = state.selectedDate.toString()
77-
if (pendingChangesByDate.containsKey(previousDateKey)) {
78-
syncRoutineChangesForDate(previousDateKey)
62+
repeatOnSubscription {
63+
observeUserProfileUseCase().collect { result ->
64+
result.fold(
65+
onSuccess = { reduce { state.copy(userNickname = it.nickname) } },
66+
onFailure = {},
67+
)
68+
}
7969
}
80-
81-
reduce { state.copy(selectedDate = data) }
8270
}
8371
}
8472

85-
fun getNextWeek() {
73+
@OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class)
74+
private fun observeWeeklyRoutines() {
8675
intent {
87-
val currentDateKey = state.selectedDate.toString()
88-
if (pendingChangesByDate.containsKey(currentDateKey)) {
89-
syncRoutineChangesForDate(currentDateKey)
76+
repeatOnSubscription {
77+
container.stateFlow
78+
.map { it.currentWeeks }
79+
.distinctUntilChanged()
80+
.debounce(300L)
81+
.flatMapLatest { weeks ->
82+
observeWeeklyRoutinesUseCase(weeks.first().toString(), weeks.last().toString())
83+
}
84+
.catch { e -> Log.e("HomeViewModel", "루틴 가져오기 실패: ${e.message}") }
85+
.collect { reduce { state.copy(routineSchedule = it.toUiModel()) } }
9086
}
87+
}
88+
}
9189

90+
fun selectDate(date: LocalDate) {
91+
intent {
92+
reduce { state.copy(selectedDate = date) }
93+
}
94+
}
95+
96+
fun getNextWeek() {
97+
intent {
9298
val newWeek = state.selectedDate.plusWeeks(1).getCurrentWeekDays()
9399
reduce { state.copy(currentWeeks = newWeek, selectedDate = newWeek.first()) }
94100
}
95101
}
96102

97103
fun getPreviousWeek() {
98104
intent {
99-
val currentDateKey = state.selectedDate.toString()
100-
if (pendingChangesByDate.containsKey(currentDateKey)) {
101-
syncRoutineChangesForDate(currentDateKey)
102-
}
103-
104105
val newWeek = state.selectedDate.minusWeeks(1).getCurrentWeekDays()
105106
reduce { state.copy(currentWeeks = newWeek, selectedDate = newWeek.first()) }
106107
}
107108
}
108109

109110
fun toggleRoutine(routineId: String) {
110111
intent {
111-
updateRoutineState(routineId, ToggleStrategy.Main)
112+
applyToggle(routineId, ToggleStrategy.Main)
112113
}
113114
}
114115

115116
fun toggleSubRoutine(routineId: String, subRoutineIndex: Int) {
116117
intent {
117-
updateRoutineState(routineId, ToggleStrategy.Sub(subRoutineIndex))
118+
applyToggle(routineId, ToggleStrategy.Sub(subRoutineIndex))
118119
}
119120
}
120121

121-
private suspend fun updateRoutineState(routineId: String, strategy: ToggleStrategy) {
122+
private suspend fun applyToggle(routineId: String, strategy: ToggleStrategy) {
122123
subIntent {
123124
val dateKey = state.selectedDate.toString()
124-
val dailySchedule = state.routineSchedule.dailyRoutines[dateKey] ?: return@subIntent
125-
val routine = dailySchedule.routines.find { it.id == routineId } ?: return@subIntent
126-
127-
val toggledState = when (strategy) {
128-
is ToggleStrategy.Main -> {
129-
toggleRoutineUseCase.toggleMainRoutine(
130-
isCompleted = routine.isCompleted,
131-
subRoutineStates = routine.subRoutineCompletionStates,
132-
)
133-
}
134-
135-
is ToggleStrategy.Sub -> {
136-
toggleRoutineUseCase.toggleSubRoutine(
137-
index = strategy.index,
138-
subRoutineStates = routine.subRoutineCompletionStates,
139-
)
140-
}
141-
} ?: return@subIntent
142-
143-
val updatedRoutines = dailySchedule.routines.map { routine ->
144-
if (routine.id == routineId) {
145-
routine.copy(
146-
isCompleted = toggledState.isCompleted,
147-
subRoutineCompletionStates = toggledState.subRoutinesIsCompleted,
148-
)
149-
} else {
150-
routine
151-
}
152-
}
125+
val routine = state.selectedDateRoutines.find { it.id == routineId } ?: return@subIntent
153126

154-
val updatedDailySchedule = dailySchedule.copy(
155-
routines = updatedRoutines,
156-
isAllCompleted = updatedRoutines.all { it.isCompleted },
157-
)
158-
159-
val newSchedule = state.routineSchedule.copy(
160-
dailyRoutines = state.routineSchedule.dailyRoutines + (dateKey to updatedDailySchedule),
161-
)
162-
163-
reduce { state.copy(routineSchedule = newSchedule) }
164-
165-
val change = RoutineCompletionInfo(
127+
applyRoutineToggleUseCase(
128+
dateKey = dateKey,
166129
routineId = routineId,
167-
routineCompleteYn = toggledState.isCompleted,
168-
subRoutineCompleteYn = toggledState.subRoutinesIsCompleted,
130+
isCompleted = routine.isCompleted,
131+
subRoutineCompletionStates = routine.subRoutineCompletionStates,
132+
strategy = strategy,
169133
)
170-
171-
val dateChanges = pendingChangesByDate.getOrPut(dateKey) { mutableMapOf() }
172-
dateChanges[routineId] = change
173-
174-
routineSyncTrigger.emit(dateKey)
175134
}
176135
}
177136

@@ -199,111 +158,4 @@ class HomeViewModel @Inject constructor(
199158
postSideEffect(HomeSideEffect.NavigateToRoutineList(selectedDate))
200159
}
201160
}
202-
203-
private fun initialize() {
204-
intent {
205-
coroutineScope {
206-
launch { observeUserProfile() }
207-
launch { fetchWeeklyRoutines(state.currentWeeks) }
208-
launch { observeWriteRoutineEvent() }
209-
launch { observeRecommendRoutineEvent() }
210-
launch { observeWeekChanges() }
211-
launch { observeRoutineUpdates() }
212-
}
213-
}
214-
}
215-
216-
private suspend fun observeWriteRoutineEvent() {
217-
subIntent {
218-
getWriteRoutineEventFlowUseCase().collect {
219-
fetchWeeklyRoutines(state.currentWeeks)
220-
}
221-
}
222-
}
223-
224-
private suspend fun observeRecommendRoutineEvent() {
225-
subIntent {
226-
getOnBoardingRecommendRoutineEventFlowUseCase().collect {
227-
fetchWeeklyRoutines(state.currentWeeks)
228-
}
229-
}
230-
}
231-
232-
@OptIn(FlowPreview::class)
233-
private suspend fun observeWeekChanges() {
234-
subIntent {
235-
container.stateFlow
236-
.map { it.currentWeeks }
237-
.distinctUntilChanged()
238-
.drop(1)
239-
.debounce(500L)
240-
.collect { newWeeks ->
241-
fetchWeeklyRoutines(newWeeks)
242-
}
243-
}
244-
}
245-
246-
@OptIn(FlowPreview::class)
247-
private suspend fun observeRoutineUpdates() {
248-
subIntent {
249-
routineSyncTrigger
250-
.debounce(500L)
251-
.collect { dateKey ->
252-
syncRoutineChangesForDate(dateKey)
253-
}
254-
}
255-
}
256-
257-
private fun observeUserProfile() {
258-
intent {
259-
repeatOnSubscription {
260-
reduce { state.copy(loadingCount = state.loadingCount + 1) }
261-
observeUserProfileUseCase().collect { result ->
262-
result.fold(
263-
onSuccess = {
264-
reduce { state.copy(userNickname = it.nickname, loadingCount = state.loadingCount - 1) }
265-
},
266-
onFailure = {
267-
Log.e("HomeViewModel", "유저 정보 가져오기 실패: ${it.message}")
268-
reduce { state.copy(loadingCount = state.loadingCount - 1) }
269-
},
270-
)
271-
}
272-
}
273-
}
274-
}
275-
276-
private suspend fun fetchWeeklyRoutines(currentWeeks: List<LocalDate>) {
277-
subIntent {
278-
reduce { state.copy(loadingCount = state.loadingCount + 1) }
279-
val startDate = currentWeeks.first().toString()
280-
val endDate = currentWeeks.last().toString()
281-
fetchWeeklyRoutinesUseCase(startDate, endDate).fold(
282-
onSuccess = {
283-
reduce { state.copy(routineSchedule = it.toUiModel(), loadingCount = state.loadingCount - 1) }
284-
},
285-
onFailure = {
286-
Log.e("HomeViewModel", "루틴 가져오기 실패: ${it.message}")
287-
reduce { state.copy(loadingCount = state.loadingCount - 1) }
288-
},
289-
)
290-
}
291-
}
292-
293-
private fun syncRoutineChangesForDate(dateKey: String) {
294-
intent {
295-
val dateChanges = pendingChangesByDate.remove(dateKey)
296-
if (dateChanges.isNullOrEmpty()) return@intent
297-
298-
val changesToSync = dateChanges.values.toList()
299-
val syncRequest = RoutineCompletionInfos(routineCompletionInfos = changesToSync)
300-
301-
routineCompletionUseCase(syncRequest).fold(
302-
onSuccess = {},
303-
onFailure = { error ->
304-
fetchWeeklyRoutines(state.currentWeeks)
305-
},
306-
)
307-
}
308-
}
309161
}

presentation/src/main/java/com/threegap/bitnagil/presentation/screen/home/contract/HomeState.kt

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,17 @@ import com.threegap.bitnagil.presentation.screen.home.util.getCurrentWeekDays
77
import java.time.LocalDate
88

99
data class HomeState(
10-
val loadingCount: Int,
1110
val userNickname: String,
1211
val dailyEmotion: DailyEmotionUiModel,
1312
val selectedDate: LocalDate,
1413
val currentWeeks: List<LocalDate>,
1514
val routineSchedule: RoutineScheduleUiModel,
1615
) {
17-
val isLoading: Boolean
18-
get() = loadingCount > 0
19-
2016
val selectedDateRoutines: List<RoutineUiModel>
2117
get() = routineSchedule.dailyRoutines[selectedDate.toString()]?.routines ?: emptyList()
2218

2319
companion object {
2420
val INIT = HomeState(
25-
loadingCount = 0,
2621
userNickname = "",
2722
dailyEmotion = DailyEmotionUiModel.INIT,
2823
selectedDate = LocalDate.now(),

0 commit comments

Comments
 (0)