@@ -3,28 +3,22 @@ package com.threegap.bitnagil.presentation.screen.home
33import android.util.Log
44import androidx.lifecycle.ViewModel
55import 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
139import com.threegap.bitnagil.domain.user.usecase.ObserveUserProfileUseCase
1410import com.threegap.bitnagil.presentation.screen.home.contract.HomeSideEffect
1511import com.threegap.bitnagil.presentation.screen.home.contract.HomeState
16- import com.threegap.bitnagil.presentation.screen.home.model.ToggleStrategy
1712import com.threegap.bitnagil.presentation.screen.home.model.toUiModel
1813import com.threegap.bitnagil.presentation.screen.home.util.getCurrentWeekDays
1914import dagger.hilt.android.lifecycle.HiltViewModel
15+ import kotlinx.coroutines.ExperimentalCoroutinesApi
2016import kotlinx.coroutines.FlowPreview
21- import kotlinx.coroutines.coroutineScope
22- import kotlinx.coroutines.flow.MutableSharedFlow
17+ import kotlinx.coroutines.flow.catch
2318import kotlinx.coroutines.flow.debounce
2419import kotlinx.coroutines.flow.distinctUntilChanged
25- import kotlinx.coroutines.flow.drop
20+ import kotlinx.coroutines.flow.flatMapLatest
2621import kotlinx.coroutines.flow.map
27- import kotlinx.coroutines.launch
2822import org.orbitmvi.orbit.Container
2923import org.orbitmvi.orbit.ContainerHost
3024import org.orbitmvi.orbit.viewmodel.container
@@ -33,145 +27,110 @@ import javax.inject.Inject
3327
3428@HiltViewModel
3529class 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}
0 commit comments