Skip to content

Commit 63ac757

Browse files
committed
Refactor: RoutineList MviViewModel 제거
1 parent 75cd566 commit 63ac757

5 files changed

Lines changed: 119 additions & 157 deletions

File tree

presentation/src/main/java/com/threegap/bitnagil/presentation/routinelist/RoutineListScreen.kt

Lines changed: 17 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -18,32 +18,31 @@ import androidx.compose.ui.Modifier
1818
import androidx.compose.ui.tooling.preview.Preview
1919
import androidx.compose.ui.unit.dp
2020
import androidx.hilt.navigation.compose.hiltViewModel
21-
import androidx.lifecycle.compose.collectAsStateWithLifecycle
2221
import com.threegap.bitnagil.designsystem.BitnagilTheme
2322
import com.threegap.bitnagil.designsystem.component.block.BitnagilTopBar
24-
import com.threegap.bitnagil.presentation.common.flow.collectAsEffect
2523
import com.threegap.bitnagil.presentation.common.toast.GlobalBitnagilToast
2624
import com.threegap.bitnagil.presentation.routinelist.component.template.DeleteConfirmBottomSheet
2725
import com.threegap.bitnagil.presentation.routinelist.component.template.EditConfirmBottomSheet
2826
import com.threegap.bitnagil.presentation.routinelist.component.template.EmptyRoutineListView
2927
import com.threegap.bitnagil.presentation.routinelist.component.template.RoutineDetailsCard
3028
import com.threegap.bitnagil.presentation.routinelist.component.template.WeeklyDatePicker
31-
import com.threegap.bitnagil.presentation.routinelist.model.RoutineListIntent
3229
import com.threegap.bitnagil.presentation.routinelist.model.RoutineListSideEffect
3330
import com.threegap.bitnagil.presentation.routinelist.model.RoutineListState
3431
import com.threegap.bitnagil.presentation.routinelist.model.RoutineUiModel
32+
import org.orbitmvi.orbit.compose.collectAsState
33+
import org.orbitmvi.orbit.compose.collectSideEffect
3534
import java.time.LocalDate
3635

3736
@Composable
3837
fun RoutineListScreenContainer(
38+
viewModel: RoutineListViewModel = hiltViewModel(),
3939
navigateToBack: () -> Unit,
4040
navigateToEditRoutine: (String, Boolean) -> Unit,
4141
navigateToAddRoutine: () -> Unit,
42-
viewModel: RoutineListViewModel = hiltViewModel(),
4342
) {
44-
val uiState by viewModel.stateFlow.collectAsStateWithLifecycle()
43+
val uiState by viewModel.collectAsState()
4544

46-
viewModel.sideEffectFlow.collectAsEffect { sideEffect ->
45+
viewModel.collectSideEffect { sideEffect ->
4746
when (sideEffect) {
4847
is RoutineListSideEffect.ShowToast -> GlobalBitnagilToast.showCheck(sideEffect.message)
4948
is RoutineListSideEffect.NavigateToBack -> navigateToBack()
@@ -58,37 +57,31 @@ fun RoutineListScreenContainer(
5857
uiState.selectedRoutine?.let { routine ->
5958
DeleteConfirmBottomSheet(
6059
isRepeatRoutine = routine.repeatDay.isNotEmpty(),
61-
onDismissRequest = { viewModel.sendIntent(RoutineListIntent.HideDeleteConfirmBottomSheet) },
60+
onDismissRequest = viewModel::hideDeleteConfirmBottomSheet,
6261
onDeleteToday = viewModel::deleteRoutineForToday,
6362
onDeleteAll = viewModel::deleteRoutineCompletely,
64-
onCancel = { viewModel.sendIntent(RoutineListIntent.HideDeleteConfirmBottomSheet) },
63+
onCancel = viewModel::hideDeleteConfirmBottomSheet,
6564
)
6665
}
6766
}
6867

6968
if (uiState.editConfirmBottomSheetVisible) {
7069
uiState.selectedRoutine?.let {
7170
EditConfirmBottomSheet(
72-
onDismissRequest = { viewModel.sendIntent(RoutineListIntent.HideEditConfirmBottomSheet) },
73-
onApplyToday = { viewModel.sendIntent(RoutineListIntent.OnApplyTodayClick) },
74-
onApplyTomorrow = { viewModel.sendIntent(RoutineListIntent.OnApplyTomorrowClick) },
71+
onDismissRequest = viewModel::hideEditConfirmBottomSheet,
72+
onApplyToday = { viewModel.navigateToEditRoutine(true) },
73+
onApplyTomorrow = { viewModel.navigateToEditRoutine(false) },
7574
)
7675
}
7776
}
7877

7978
RoutineListScreen(
8079
uiState = uiState,
81-
onDateSelect = { selectedDate ->
82-
viewModel.sendIntent(RoutineListIntent.OnDateSelect(selectedDate))
83-
},
84-
onShowDeleteConfirmBottomSheet = { routine ->
85-
viewModel.sendIntent(RoutineListIntent.ShowDeleteConfirmBottomSheet(routine))
86-
},
87-
onShowEditConfirmBottomSheet = { routine ->
88-
viewModel.sendIntent(RoutineListIntent.ShowEditConfirmBottomSheet(routine))
89-
},
90-
onRegisterRoutineClick = { viewModel.sendIntent(RoutineListIntent.OnRegisterRoutineClick) },
91-
onBackClick = { viewModel.sendIntent(RoutineListIntent.NavigateToBack) },
80+
onDateSelect = viewModel::updateDate,
81+
onShowDeleteConfirmBottomSheet = viewModel::showDeleteConfirmBottomSheet,
82+
onShowEditConfirmBottomSheet = viewModel::showEditConfirmBottomSheet,
83+
onRegisterRoutineClick = viewModel::navigateToAddRoutine,
84+
onBackClick = viewModel::navigateToBack,
9285
)
9386
}
9487

@@ -100,10 +93,9 @@ private fun RoutineListScreen(
10093
onShowEditConfirmBottomSheet: (RoutineUiModel) -> Unit,
10194
onRegisterRoutineClick: () -> Unit,
10295
onBackClick: () -> Unit,
103-
modifier: Modifier = Modifier,
10496
) {
10597
Column(
106-
modifier = modifier
98+
modifier = Modifier
10799
.fillMaxSize()
108100
.background(BitnagilTheme.colors.coolGray99)
109101
.statusBarsPadding(),
@@ -158,7 +150,7 @@ private fun RoutineListScreen(
158150
@Composable
159151
private fun RoutineListScreenPreview() {
160152
RoutineListScreen(
161-
uiState = RoutineListState(),
153+
uiState = RoutineListState.INIT,
162154
onDateSelect = {},
163155
onShowDeleteConfirmBottomSheet = {},
164156
onShowEditConfirmBottomSheet = {},

presentation/src/main/java/com/threegap/bitnagil/presentation/routinelist/RoutineListViewModel.kt

Lines changed: 83 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,22 @@ package com.threegap.bitnagil.presentation.routinelist
22

33
import android.util.Log
44
import androidx.lifecycle.SavedStateHandle
5+
import androidx.lifecycle.ViewModel
56
import androidx.lifecycle.viewModelScope
67
import com.threegap.bitnagil.domain.routine.usecase.DeleteRoutineForDayUseCase
78
import com.threegap.bitnagil.domain.routine.usecase.DeleteRoutineUseCase
89
import com.threegap.bitnagil.domain.routine.usecase.FetchWeeklyRoutinesUseCase
910
import com.threegap.bitnagil.domain.writeroutine.usecase.GetWriteRoutineEventFlowUseCase
10-
import com.threegap.bitnagil.presentation.common.mviviewmodel.MviViewModel
1111
import com.threegap.bitnagil.presentation.home.util.getCurrentWeekDays
12-
import com.threegap.bitnagil.presentation.routinelist.model.RoutineListIntent
1312
import com.threegap.bitnagil.presentation.routinelist.model.RoutineListSideEffect
1413
import com.threegap.bitnagil.presentation.routinelist.model.RoutineListState
14+
import com.threegap.bitnagil.presentation.routinelist.model.RoutineUiModel
1515
import com.threegap.bitnagil.presentation.routinelist.model.toUiModel
1616
import dagger.hilt.android.lifecycle.HiltViewModel
1717
import kotlinx.coroutines.launch
18-
import org.orbitmvi.orbit.syntax.Syntax
18+
import org.orbitmvi.orbit.Container
19+
import org.orbitmvi.orbit.ContainerHost
20+
import org.orbitmvi.orbit.viewmodel.container
1921
import java.time.LocalDate
2022
import javax.inject.Inject
2123

@@ -26,95 +28,51 @@ class RoutineListViewModel @Inject constructor(
2628
private val deleteRoutineUseCase: DeleteRoutineUseCase,
2729
private val deleteRoutineForDayUseCase: DeleteRoutineForDayUseCase,
2830
private val getWriteRoutineEventFlowUseCase: GetWriteRoutineEventFlowUseCase,
29-
) : MviViewModel<RoutineListState, RoutineListSideEffect, RoutineListIntent>(
30-
savedStateHandle = savedStateHandle,
31-
initState = RoutineListState(
32-
selectedDate = savedStateHandle.get<String>("selectedDate")
33-
?.takeIf { it.isNotBlank() }
34-
?.let { dateString ->
35-
runCatching { LocalDate.parse(dateString) }.getOrNull()
36-
}
37-
?: LocalDate.now(),
38-
),
39-
) {
31+
) : ContainerHost<RoutineListState, RoutineListSideEffect>, ViewModel() {
32+
33+
override val container: Container<RoutineListState, RoutineListSideEffect> = container(initialState = RoutineListState.INIT)
34+
35+
private val selectedDate = savedStateHandle.get<String>("selectedDate")
36+
?.takeIf { it.isNotBlank() }
37+
?.let { dateString ->
38+
runCatching { LocalDate.parse(dateString) }.getOrNull()
39+
}
40+
?: LocalDate.now()
4041

4142
init {
43+
updateDate(selectedDate)
4244
fetchRoutines()
4345
observeRoutineChanges()
4446
}
4547

46-
override suspend fun Syntax<RoutineListState, RoutineListSideEffect>.reduceState(
47-
intent: RoutineListIntent,
48-
state: RoutineListState,
49-
): RoutineListState? {
50-
val newState = when (intent) {
51-
is RoutineListIntent.UpdateLoading -> state.copy(isLoading = intent.isLoading)
52-
is RoutineListIntent.LoadRoutines -> state.copy(routines = intent.routines)
53-
is RoutineListIntent.OnDateSelect -> state.copy(selectedDate = intent.date)
54-
55-
is RoutineListIntent.ShowDeleteConfirmBottomSheet -> {
56-
state.copy(
57-
selectedRoutine = intent.routine,
58-
deleteConfirmBottomSheetVisible = true,
59-
)
60-
}
61-
62-
is RoutineListIntent.ShowEditConfirmBottomSheet -> {
63-
state.copy(
64-
selectedRoutine = intent.routine,
65-
editConfirmBottomSheetVisible = true,
66-
)
67-
}
68-
69-
is RoutineListIntent.HideDeleteConfirmBottomSheet -> state.copy(deleteConfirmBottomSheetVisible = false)
70-
is RoutineListIntent.HideEditConfirmBottomSheet -> state.copy(editConfirmBottomSheetVisible = false)
71-
72-
is RoutineListIntent.NavigateToBack -> {
73-
sendSideEffect(RoutineListSideEffect.NavigateToBack)
74-
null
75-
}
76-
77-
is RoutineListIntent.OnRegisterRoutineClick -> {
78-
sendSideEffect(RoutineListSideEffect.NavigateToAddRoutine)
79-
null
80-
}
48+
fun updateDate(selectedDate: LocalDate) {
49+
intent {
50+
reduce { state.copy(selectedDate = selectedDate) }
51+
}
52+
}
8153

82-
is RoutineListIntent.OnApplyTodayClick -> {
83-
val selectedRoutine = state.selectedRoutine
84-
if (selectedRoutine != null) {
85-
sendSideEffect(
86-
RoutineListSideEffect.NavigateToEditRoutine(
87-
routineId = selectedRoutine.routineId,
88-
updateRoutineFromNowDate = true,
89-
),
90-
)
91-
}
92-
null
93-
}
54+
fun showDeleteConfirmBottomSheet(routine: RoutineUiModel) {
55+
intent {
56+
reduce { state.copy(selectedRoutine = routine, deleteConfirmBottomSheetVisible = true) }
57+
}
58+
}
9459

95-
is RoutineListIntent.OnApplyTomorrowClick -> {
96-
val selectedRoutine = state.selectedRoutine
97-
if (selectedRoutine != null) {
98-
sendSideEffect(
99-
RoutineListSideEffect.NavigateToEditRoutine(
100-
routineId = selectedRoutine.routineId,
101-
updateRoutineFromNowDate = false,
102-
),
103-
)
104-
}
105-
null
106-
}
60+
fun hideDeleteConfirmBottomSheet() {
61+
intent {
62+
reduce { state.copy(deleteConfirmBottomSheetVisible = false) }
63+
}
64+
}
10765

108-
is RoutineListIntent.OnSuccessDeletedRoutine -> {
109-
sendSideEffect(RoutineListSideEffect.ShowToast("삭제가 완료되었습니다."))
110-
state.copy(
111-
isLoading = false,
112-
deleteConfirmBottomSheetVisible = false,
113-
)
114-
}
66+
fun showEditConfirmBottomSheet(routine: RoutineUiModel) {
67+
intent {
68+
reduce { state.copy(selectedRoutine = routine, editConfirmBottomSheetVisible = true) }
11569
}
70+
}
11671

117-
return newState
72+
fun hideEditConfirmBottomSheet() {
73+
intent {
74+
reduce { state.copy(editConfirmBottomSheetVisible = false) }
75+
}
11876
}
11977

12078
private fun observeRoutineChanges() {
@@ -126,55 +84,80 @@ class RoutineListViewModel @Inject constructor(
12684
}
12785

12886
private fun fetchRoutines() {
129-
sendIntent(RoutineListIntent.UpdateLoading(true))
130-
val currentWeek = stateFlow.value.selectedDate.getCurrentWeekDays()
131-
val startDate = currentWeek.first().toString()
132-
val endDate = currentWeek.last().toString()
133-
viewModelScope.launch {
87+
intent {
88+
reduce { state.copy(isLoading = true) }
89+
val currentWeek = state.selectedDate.getCurrentWeekDays()
90+
val startDate = currentWeek.first().toString()
91+
val endDate = currentWeek.last().toString()
13492
fetchWeeklyRoutinesUseCase(startDate, endDate).fold(
135-
onSuccess = { routines ->
136-
sendIntent(RoutineListIntent.LoadRoutines(routines.toUiModel()))
137-
sendIntent(RoutineListIntent.UpdateLoading(false))
93+
onSuccess = { routineSchedule ->
94+
reduce { state.copy(isLoading = false, routines = routineSchedule.toUiModel()) }
13895
},
13996
onFailure = {
14097
Log.e("RoutineListViewModel", "루틴 가져오기 실패: ${it.message}")
141-
sendIntent(RoutineListIntent.UpdateLoading(false))
98+
reduce { state.copy(isLoading = false) }
14299
},
143100
)
144101
}
145102
}
146103

147104
fun deleteRoutineCompletely() {
148-
sendIntent(RoutineListIntent.UpdateLoading(true))
149-
val selectedRoutine = stateFlow.value.selectedRoutine!!
150-
viewModelScope.launch {
105+
intent {
106+
reduce { state.copy(isLoading = true) }
107+
val selectedRoutine = state.selectedRoutine ?: return@intent
151108
deleteRoutineUseCase(selectedRoutine.routineId).fold(
152109
onSuccess = {
153110
fetchRoutines()
154-
sendIntent(RoutineListIntent.OnSuccessDeletedRoutine)
111+
reduce { state.copy(isLoading = false, deleteConfirmBottomSheetVisible = false) }
112+
postSideEffect(RoutineListSideEffect.ShowToast("삭제가 완료되었습니다."))
155113
},
156114
onFailure = {
157115
Log.e("RoutineListViewModel", "루틴 삭제 실패: ${it.message}")
158-
sendIntent(RoutineListIntent.UpdateLoading(false))
116+
reduce { state.copy(isLoading = false) }
159117
},
160118
)
161119
}
162120
}
163121

164122
fun deleteRoutineForToday() {
165-
sendIntent(RoutineListIntent.UpdateLoading(true))
166-
val selectedRoutine = stateFlow.value.selectedRoutine!!
167-
viewModelScope.launch {
123+
intent {
124+
reduce { state.copy(isLoading = true) }
125+
val selectedRoutine = state.selectedRoutine ?: return@intent
168126
deleteRoutineForDayUseCase(selectedRoutine.routineId).fold(
169127
onSuccess = {
170128
fetchRoutines()
171-
sendIntent(RoutineListIntent.OnSuccessDeletedRoutine)
129+
reduce { state.copy(isLoading = false, deleteConfirmBottomSheetVisible = false) }
130+
postSideEffect(RoutineListSideEffect.ShowToast("삭제가 완료되었습니다."))
172131
},
173132
onFailure = {
174133
Log.e("RoutineListViewModel", "루틴 삭제 실패: ${it.message}")
175-
sendIntent(RoutineListIntent.UpdateLoading(false))
134+
reduce { state.copy(isLoading = false) }
176135
},
177136
)
178137
}
179138
}
139+
140+
fun navigateToAddRoutine() {
141+
intent {
142+
postSideEffect(RoutineListSideEffect.NavigateToAddRoutine)
143+
}
144+
}
145+
146+
fun navigateToEditRoutine(updateFromNow: Boolean) {
147+
intent {
148+
val selectedRoutine = state.selectedRoutine ?: return@intent
149+
postSideEffect(
150+
RoutineListSideEffect.NavigateToEditRoutine(
151+
routineId = selectedRoutine.routineId,
152+
updateRoutineFromNowDate = updateFromNow,
153+
),
154+
)
155+
}
156+
}
157+
158+
fun navigateToBack() {
159+
intent {
160+
postSideEffect(RoutineListSideEffect.NavigateToBack)
161+
}
162+
}
180163
}

presentation/src/main/java/com/threegap/bitnagil/presentation/routinelist/model/RoutineListIntent.kt

Lines changed: 0 additions & 19 deletions
This file was deleted.

presentation/src/main/java/com/threegap/bitnagil/presentation/routinelist/model/RoutineListSideEffect.kt

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
package com.threegap.bitnagil.presentation.routinelist.model
22

3-
import com.threegap.bitnagil.presentation.common.mviviewmodel.MviSideEffect
4-
5-
sealed interface RoutineListSideEffect : MviSideEffect {
3+
sealed interface RoutineListSideEffect {
64
data object NavigateToBack : RoutineListSideEffect
75
data object NavigateToAddRoutine : RoutineListSideEffect
86
data class NavigateToEditRoutine(val routineId: String, val updateRoutineFromNowDate: Boolean) : RoutineListSideEffect

0 commit comments

Comments
 (0)