Skip to content

Commit e9ea930

Browse files
committed
Feat: 루틴 리스트 화면 구현
1 parent 97d3ff3 commit e9ea930

2 files changed

Lines changed: 235 additions & 0 deletions

File tree

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
package com.threegap.bitnagil.presentation.routinelist
2+
3+
import androidx.compose.foundation.background
4+
import androidx.compose.foundation.layout.Arrangement
5+
import androidx.compose.foundation.layout.Column
6+
import androidx.compose.foundation.layout.PaddingValues
7+
import androidx.compose.foundation.layout.Spacer
8+
import androidx.compose.foundation.layout.fillMaxSize
9+
import androidx.compose.foundation.layout.height
10+
import androidx.compose.foundation.layout.padding
11+
import androidx.compose.foundation.layout.statusBarsPadding
12+
import androidx.compose.foundation.lazy.LazyColumn
13+
import androidx.compose.foundation.lazy.items
14+
import androidx.compose.runtime.Composable
15+
import androidx.compose.runtime.getValue
16+
import androidx.compose.ui.Alignment
17+
import androidx.compose.ui.Modifier
18+
import androidx.compose.ui.tooling.preview.Preview
19+
import androidx.compose.ui.unit.dp
20+
import androidx.hilt.navigation.compose.hiltViewModel
21+
import androidx.lifecycle.compose.collectAsStateWithLifecycle
22+
import com.threegap.bitnagil.designsystem.BitnagilTheme
23+
import com.threegap.bitnagil.designsystem.component.block.BitnagilTopBar
24+
import com.threegap.bitnagil.presentation.common.flow.collectAsEffect
25+
import com.threegap.bitnagil.presentation.routinelist.component.template.DeleteConfirmBottomSheet
26+
import com.threegap.bitnagil.presentation.routinelist.component.template.EmptyRoutineListView
27+
import com.threegap.bitnagil.presentation.routinelist.component.template.RoutineDetailsCard
28+
import com.threegap.bitnagil.presentation.routinelist.component.template.WeeklyDatePicker
29+
import com.threegap.bitnagil.presentation.routinelist.model.RoutineListIntent
30+
import com.threegap.bitnagil.presentation.routinelist.model.RoutineListSideEffect
31+
import com.threegap.bitnagil.presentation.routinelist.model.RoutineListState
32+
import com.threegap.bitnagil.presentation.routinelist.model.RoutineUiModel
33+
import java.time.LocalDate
34+
35+
@Composable
36+
fun RoutineListScreenContainer(
37+
navigateToBack: () -> Unit,
38+
viewModel: RoutineListViewModel = hiltViewModel(),
39+
) {
40+
val uiState by viewModel.stateFlow.collectAsStateWithLifecycle()
41+
42+
viewModel.sideEffectFlow.collectAsEffect { sideEffect ->
43+
when (sideEffect) {
44+
is RoutineListSideEffect.NavigateToBack -> navigateToBack()
45+
}
46+
}
47+
48+
if (uiState.deleteConfirmBottomSheetVisible) {
49+
uiState.selectedRoutine?.let { routine ->
50+
DeleteConfirmBottomSheet(
51+
routine = routine,
52+
onDismissRequest = { viewModel.sendIntent(RoutineListIntent.HideDeleteConfirmBottomSheet) },
53+
onDeleteToday = viewModel::deleteRoutineForToday,
54+
onDeleteAll = viewModel::deleteRoutineCompletely,
55+
onCancel = { viewModel.sendIntent(RoutineListIntent.HideDeleteConfirmBottomSheet) },
56+
)
57+
}
58+
}
59+
60+
RoutineListScreen(
61+
uiState = uiState,
62+
onDateSelect = { selectedDate ->
63+
viewModel.sendIntent(RoutineListIntent.OnDateSelect(selectedDate))
64+
},
65+
onShowDeleteConfirmBottomSheet = { routine ->
66+
viewModel.sendIntent(RoutineListIntent.ShowDeleteConfirmBottomSheet(routine))
67+
},
68+
onRegisterRoutineClick = {},
69+
onBackClick = { viewModel.sendIntent(RoutineListIntent.NavigateToBack) },
70+
)
71+
}
72+
73+
@Composable
74+
private fun RoutineListScreen(
75+
uiState: RoutineListState,
76+
onDateSelect: (LocalDate) -> Unit,
77+
onShowDeleteConfirmBottomSheet: (RoutineUiModel) -> Unit,
78+
onRegisterRoutineClick: () -> Unit,
79+
onBackClick: () -> Unit,
80+
modifier: Modifier = Modifier,
81+
) {
82+
Column(
83+
modifier = modifier
84+
.fillMaxSize()
85+
.background(BitnagilTheme.colors.coolGray99)
86+
.statusBarsPadding(),
87+
horizontalAlignment = Alignment.CenterHorizontally,
88+
) {
89+
BitnagilTopBar(
90+
title = "루틴리스트",
91+
showBackButton = true,
92+
onBackClick = onBackClick,
93+
)
94+
95+
Spacer(modifier = Modifier.height(4.dp))
96+
97+
WeeklyDatePicker(
98+
selectedDate = uiState.selectedDate,
99+
weeklyDates = uiState.currentWeekDates,
100+
onDateSelect = onDateSelect,
101+
modifier = Modifier
102+
.padding(vertical = 10.dp, horizontal = 16.dp),
103+
)
104+
105+
if (uiState.selectedDateRoutines.isEmpty()) {
106+
EmptyRoutineListView(
107+
onRegisterRoutineClick = onRegisterRoutineClick,
108+
modifier = Modifier.fillMaxSize(),
109+
)
110+
} else {
111+
LazyColumn(
112+
modifier = Modifier
113+
.fillMaxSize()
114+
.padding(horizontal = 16.dp),
115+
verticalArrangement = Arrangement.spacedBy(12.dp),
116+
contentPadding = PaddingValues(bottom = 10.dp),
117+
) {
118+
items(
119+
items = uiState.selectedDateRoutines,
120+
key = { routine -> routine.routineId },
121+
contentType = { "routine_item" },
122+
) { routine ->
123+
RoutineDetailsCard(
124+
routine = routine,
125+
onDeleteClick = { onShowDeleteConfirmBottomSheet(routine) },
126+
)
127+
}
128+
}
129+
}
130+
}
131+
}
132+
133+
@Preview
134+
@Composable
135+
private fun RoutineListScreenPreview() {
136+
RoutineListScreen(
137+
uiState = RoutineListState(),
138+
onDateSelect = {},
139+
onShowDeleteConfirmBottomSheet = {},
140+
onRegisterRoutineClick = {},
141+
onBackClick = {},
142+
)
143+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package com.threegap.bitnagil.presentation.routinelist
2+
3+
import android.util.Log
4+
import androidx.lifecycle.SavedStateHandle
5+
import androidx.lifecycle.viewModelScope
6+
import com.threegap.bitnagil.domain.routine.usecase.FetchWeeklyRoutinesUseCase
7+
import com.threegap.bitnagil.presentation.common.mviviewmodel.MviViewModel
8+
import com.threegap.bitnagil.presentation.home.util.getCurrentWeekDays
9+
import com.threegap.bitnagil.presentation.routinelist.model.RoutineListIntent
10+
import com.threegap.bitnagil.presentation.routinelist.model.RoutineListSideEffect
11+
import com.threegap.bitnagil.presentation.routinelist.model.RoutineListState
12+
import com.threegap.bitnagil.presentation.routinelist.model.toUiModel
13+
import dagger.hilt.android.lifecycle.HiltViewModel
14+
import kotlinx.coroutines.launch
15+
import org.orbitmvi.orbit.syntax.simple.SimpleSyntax
16+
import java.time.LocalDate
17+
import javax.inject.Inject
18+
19+
@HiltViewModel
20+
class RoutineListViewModel @Inject constructor(
21+
savedStateHandle: SavedStateHandle,
22+
private val fetchWeeklyRoutinesUseCase: FetchWeeklyRoutinesUseCase,
23+
) : MviViewModel<RoutineListState, RoutineListSideEffect, RoutineListIntent>(
24+
savedStateHandle = savedStateHandle,
25+
initState = RoutineListState(
26+
selectedDate = savedStateHandle.get<String>("selectedDate")
27+
?.takeIf { it.isNotBlank() }
28+
?.let { dateString ->
29+
runCatching { LocalDate.parse(dateString) }.getOrNull()
30+
}
31+
?: LocalDate.now()
32+
),
33+
) {
34+
35+
init {
36+
fetchRoutines()
37+
}
38+
39+
override suspend fun SimpleSyntax<RoutineListState, RoutineListSideEffect>.reduceState(
40+
intent: RoutineListIntent,
41+
state: RoutineListState,
42+
): RoutineListState? {
43+
val newState = when (intent) {
44+
is RoutineListIntent.UpdateLoading -> state.copy(isLoading = intent.isLoading)
45+
is RoutineListIntent.LoadRoutines -> state.copy(routines = intent.routines)
46+
is RoutineListIntent.OnDateSelect -> state.copy(selectedDate = intent.date)
47+
48+
is RoutineListIntent.ShowDeleteConfirmBottomSheet -> {
49+
state.copy(
50+
selectedRoutine = intent.routine,
51+
deleteConfirmBottomSheetVisible = true,
52+
)
53+
}
54+
55+
is RoutineListIntent.HideDeleteConfirmBottomSheet -> state.copy(deleteConfirmBottomSheetVisible = false)
56+
57+
is RoutineListIntent.NavigateToBack -> {
58+
sendSideEffect(RoutineListSideEffect.NavigateToBack)
59+
null
60+
}
61+
}
62+
63+
return newState
64+
}
65+
66+
private fun fetchRoutines() {
67+
sendIntent(RoutineListIntent.UpdateLoading(true))
68+
val currentWeek = LocalDate.now().getCurrentWeekDays()
69+
val startDate = currentWeek.first().toString()
70+
val endDate = currentWeek.last().toString()
71+
viewModelScope.launch {
72+
fetchWeeklyRoutinesUseCase(startDate, endDate).fold(
73+
onSuccess = { routines ->
74+
sendIntent(RoutineListIntent.LoadRoutines(routines.toUiModel()))
75+
sendIntent(RoutineListIntent.UpdateLoading(false))
76+
},
77+
onFailure = {
78+
Log.e("RoutineListViewModel", "루틴 가져오기 실패: ${it.message}")
79+
sendIntent(RoutineListIntent.UpdateLoading(false))
80+
},
81+
)
82+
}
83+
}
84+
85+
fun deleteRoutineCompletely() {
86+
viewModelScope.launch {}
87+
}
88+
89+
fun deleteRoutineForToday() {
90+
viewModelScope.launch { }
91+
}
92+
}

0 commit comments

Comments
 (0)