Skip to content

Commit c472f16

Browse files
committed
Feat: 제보 카테고리 선택 bottomSheet구현, 제보 히스토리 ViewModel 뼈대 구현 및 적용
1 parent 1533248 commit c472f16

6 files changed

Lines changed: 286 additions & 15 deletions

File tree

presentation/src/main/java/com/threegap/bitnagil/presentation/reporthistory/ReportHistoryScreen.kt

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,31 +20,59 @@ import androidx.compose.foundation.lazy.itemsIndexed
2020
import androidx.compose.foundation.rememberScrollState
2121
import androidx.compose.material3.Text
2222
import androidx.compose.runtime.Composable
23+
import androidx.compose.runtime.getValue
2324
import androidx.compose.ui.Alignment
2425
import androidx.compose.ui.Modifier
2526
import androidx.compose.ui.tooling.preview.Preview
2627
import androidx.compose.ui.unit.dp
28+
import androidx.hilt.navigation.compose.hiltViewModel
2729
import com.threegap.bitnagil.designsystem.BitnagilTheme
2830
import com.threegap.bitnagil.designsystem.R
2931
import com.threegap.bitnagil.designsystem.component.atom.BitnagilChip
3032
import com.threegap.bitnagil.designsystem.component.atom.BitnagilIcon
3133
import com.threegap.bitnagil.designsystem.component.block.BitnagilTopBar
3234
import com.threegap.bitnagil.designsystem.modifier.clickableWithoutRipple
3335
import com.threegap.bitnagil.presentation.reporthistory.component.block.ReportHistoryItem
36+
import com.threegap.bitnagil.presentation.reporthistory.component.template.ReportCategoryBottomSheet
3437
import com.threegap.bitnagil.presentation.reporthistory.model.ReportCategory
3538
import com.threegap.bitnagil.presentation.reporthistory.model.ReportHistoriesPerDayUiModel
3639
import com.threegap.bitnagil.presentation.reporthistory.model.ReportHistoryState
3740
import com.threegap.bitnagil.presentation.reporthistory.model.ReportHistoryUiModel
3841
import com.threegap.bitnagil.presentation.reporthistory.model.ReportProcess
42+
import org.orbitmvi.orbit.compose.collectAsState
3943

4044
@Composable
41-
fun ReportHistoryScreenContainer() {
45+
fun ReportHistoryScreenContainer(
46+
viewModel: ReportHistoryViewModel = hiltViewModel(),
47+
navigateToBack: () -> Unit,
48+
navigateToReportDetail: (String) -> Unit,
49+
) {
50+
val state by viewModel.collectAsState()
51+
52+
if (state.showSelectReportCategoryBottomSheet)
53+
ReportCategoryBottomSheet(
54+
selectedCategory = state.selectedReportCategory,
55+
onDismiss = viewModel::hideReportCategoryBottomSheet,
56+
onSelected = viewModel::selectReportCategory
57+
)
58+
59+
ReportHistoryScreen(
60+
state = state,
61+
onClickPreviousButton = navigateToBack,
62+
onClickReportProcessChip = viewModel::selectReportProcess,
63+
onClickReportCategoryButton = viewModel::showReportCategoryBottomSheet,
64+
onClickReportItem = navigateToReportDetail
65+
)
4266
}
4367

4468
@Composable
4569
private fun ReportHistoryScreen(
4670
modifier: Modifier = Modifier,
4771
state: ReportHistoryState,
72+
onClickPreviousButton: () -> Unit,
73+
onClickReportProcessChip: (ReportProcess) -> Unit,
74+
onClickReportCategoryButton: () -> Unit,
75+
onClickReportItem: (String) -> Unit,
4876
) {
4977
val horizontalScreenState = rememberScrollState()
5078

@@ -57,7 +85,7 @@ private fun ReportHistoryScreen(
5785
BitnagilTopBar(
5886
title = "내 제보 기록",
5987
showBackButton = true,
60-
onBackClick = {},
88+
onBackClick = onClickPreviousButton,
6189
)
6290

6391
Spacer(modifier = Modifier.height(16.dp))
@@ -72,7 +100,9 @@ private fun ReportHistoryScreen(
72100
BitnagilChip(
73101
title = reportProcessWithCount.process.title,
74102
isSelected = state.selectedReportProcess == reportProcessWithCount.process,
75-
onCategorySelected = {},
103+
onCategorySelected = {
104+
onClickReportProcessChip(reportProcessWithCount.process)
105+
},
76106
count = if (reportProcessWithCount.count == 0) null else reportProcessWithCount.count,
77107
)
78108
}
@@ -83,14 +113,16 @@ private fun ReportHistoryScreen(
83113
Spacer(modifier = Modifier.height(24.dp))
84114

85115
Box(
86-
modifier = Modifier.fillMaxWidth().weight(1f),
116+
modifier = Modifier
117+
.fillMaxWidth()
118+
.weight(1f),
87119
) {
88120
if (state.filteredReportHistoriesPerDays.isNotEmpty())
89121
LazyColumn(
90122
modifier = Modifier.fillMaxSize(),
91123
contentPadding = PaddingValues(horizontal = 16.dp),
92124
) {
93-
state.reportHistoriesPerDays.forEach { reportHistoriesPerDay ->
125+
state.filteredReportHistoriesPerDays.forEach { reportHistoriesPerDay ->
94126
stickyHeader {
95127
Box(
96128
modifier = Modifier
@@ -110,7 +142,9 @@ private fun ReportHistoryScreen(
110142
ReportHistoryItem(
111143
modifier = Modifier.padding(bottom = if (index == reportHistoriesPerDay.reports.lastIndex) 24.dp else 10.dp),
112144
report = report,
113-
onClick = {},
145+
onClick = {
146+
onClickReportItem(report.id)
147+
},
114148
)
115149
}
116150
}
@@ -132,7 +166,7 @@ private fun ReportHistoryScreen(
132166
modifier = Modifier
133167
.height(40.dp)
134168
.align(Alignment.TopEnd)
135-
.clickableWithoutRipple { },
169+
.clickableWithoutRipple(onClick = onClickReportCategoryButton),
136170
) {
137171
Text(
138172
text = state.selectedReportCategory?.title ?: "카테고리",
@@ -183,6 +217,10 @@ private fun ReportHistoryScreenPreview() {
183217
)
184218
},
185219
),
220+
onClickPreviousButton = {},
221+
onClickReportProcessChip = {},
222+
onClickReportCategoryButton = {},
223+
onClickReportItem = {},
186224
)
187225
}
188226
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package com.threegap.bitnagil.presentation.reporthistory
2+
3+
import androidx.lifecycle.ViewModel
4+
import com.threegap.bitnagil.presentation.reporthistory.model.ReportCategory
5+
import com.threegap.bitnagil.presentation.reporthistory.model.ReportHistoriesPerDayUiModel
6+
import com.threegap.bitnagil.presentation.reporthistory.model.ReportHistorySideEffect
7+
import com.threegap.bitnagil.presentation.reporthistory.model.ReportHistoryState
8+
import com.threegap.bitnagil.presentation.reporthistory.model.ReportHistoryUiModel
9+
import com.threegap.bitnagil.presentation.reporthistory.model.ReportProcess
10+
import dagger.hilt.android.lifecycle.HiltViewModel
11+
import org.orbitmvi.orbit.Container
12+
import org.orbitmvi.orbit.ContainerHost
13+
import org.orbitmvi.orbit.viewmodel.container
14+
import javax.inject.Inject
15+
16+
@HiltViewModel
17+
class ReportHistoryViewModel @Inject constructor() : ContainerHost<ReportHistoryState, ReportHistorySideEffect>, ViewModel() {
18+
override val container: Container<ReportHistoryState, ReportHistorySideEffect> = container(initialState = ReportHistoryState.Init)
19+
20+
init {
21+
loadReportHistories()
22+
}
23+
24+
private fun loadReportHistories() = intent {
25+
reduce {
26+
state.copy(
27+
reportHistoriesPerDays = List(10) {
28+
ReportHistoriesPerDayUiModel(
29+
date = java.time.LocalDate.now(),
30+
reports = listOf(
31+
ReportHistoryUiModel(
32+
id = "1",
33+
title = "제보 1",
34+
imageUrl = "-",
35+
location = "서울특별시 성북구 안암로 106",
36+
process = ReportProcess.Reported,
37+
category = ReportCategory.Amenities,
38+
),
39+
ReportHistoryUiModel(
40+
id = "1",
41+
title = "제보 1",
42+
imageUrl = "-",
43+
location = "서울특별시 성북구 안암로 106",
44+
process = ReportProcess.Progress,
45+
category = ReportCategory.Amenities,
46+
),
47+
),
48+
)
49+
},
50+
)
51+
}
52+
}
53+
54+
fun selectReportCategory(reportCategory: ReportCategory) = intent {
55+
val currentSelectedReportCategory = state.selectedReportCategory
56+
57+
reduce {
58+
state.copy(
59+
selectedReportCategory = if (currentSelectedReportCategory == reportCategory) null else reportCategory
60+
)
61+
}
62+
}
63+
64+
fun selectReportProcess(reportProcess: ReportProcess) = intent {
65+
reduce {
66+
state.copy(
67+
selectedReportProcess = reportProcess
68+
)
69+
}
70+
}
71+
72+
fun showReportCategoryBottomSheet() = intent {
73+
reduce {
74+
state.copy(
75+
showSelectReportCategoryBottomSheet = true
76+
)
77+
}
78+
}
79+
80+
fun hideReportCategoryBottomSheet() = intent {
81+
reduce {
82+
state.copy(
83+
showSelectReportCategoryBottomSheet = false
84+
)
85+
}
86+
}
87+
}
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
package com.threegap.bitnagil.presentation.reporthistory.component.template
2+
3+
import androidx.annotation.DrawableRes
4+
import androidx.compose.foundation.layout.Arrangement
5+
import androidx.compose.foundation.layout.Column
6+
import androidx.compose.foundation.layout.Row
7+
import androidx.compose.foundation.layout.Spacer
8+
import androidx.compose.foundation.layout.fillMaxWidth
9+
import androidx.compose.foundation.layout.padding
10+
import androidx.compose.foundation.layout.width
11+
import androidx.compose.material3.ExperimentalMaterial3Api
12+
import androidx.compose.material3.HorizontalDivider
13+
import androidx.compose.material3.ModalBottomSheet
14+
import androidx.compose.material3.Text
15+
import androidx.compose.material3.rememberModalBottomSheetState
16+
import androidx.compose.runtime.Composable
17+
import androidx.compose.runtime.rememberCoroutineScope
18+
import androidx.compose.ui.Alignment
19+
import androidx.compose.ui.Modifier
20+
import androidx.compose.ui.tooling.preview.Preview
21+
import androidx.compose.ui.unit.dp
22+
import com.threegap.bitnagil.designsystem.BitnagilTheme
23+
import com.threegap.bitnagil.designsystem.R
24+
import com.threegap.bitnagil.designsystem.component.atom.BitnagilIcon
25+
import com.threegap.bitnagil.designsystem.modifier.clickableWithoutRipple
26+
import com.threegap.bitnagil.domain.recommendroutine.model.RecommendLevel
27+
import com.threegap.bitnagil.presentation.reporthistory.model.ReportCategory
28+
import kotlinx.coroutines.launch
29+
30+
@OptIn(ExperimentalMaterial3Api::class)
31+
@Composable
32+
fun ReportCategoryBottomSheet(
33+
selectedCategory: ReportCategory?,
34+
onDismiss: () -> Unit,
35+
onSelected: (ReportCategory) -> Unit,
36+
modifier: Modifier = Modifier,
37+
) {
38+
val sheetState = rememberModalBottomSheetState()
39+
val coroutineScope = rememberCoroutineScope()
40+
41+
ModalBottomSheet(
42+
sheetState = sheetState,
43+
onDismissRequest = onDismiss,
44+
containerColor = BitnagilTheme.colors.white,
45+
contentColor = BitnagilTheme.colors.white,
46+
modifier = modifier,
47+
) {
48+
Column(
49+
modifier = Modifier.padding(start = 16.dp, end = 16.dp, bottom = 28.dp),
50+
) {
51+
ReportCategory.entries.forEachIndexed { index, category ->
52+
ReportCategoryItem(
53+
icon = category.iconResourceId,
54+
title = category.title,
55+
description = category.description,
56+
isSelected = selectedCategory == category,
57+
onClick = {
58+
onSelected(category)
59+
coroutineScope
60+
.launch { sheetState.hide() }
61+
.invokeOnCompletion {
62+
if (!sheetState.isVisible) onDismiss()
63+
}
64+
},
65+
)
66+
67+
if (index < RecommendLevel.entries.size) {
68+
HorizontalDivider(
69+
color = BitnagilTheme.colors.coolGray97,
70+
)
71+
}
72+
}
73+
}
74+
}
75+
}
76+
77+
@Composable
78+
private fun ReportCategoryItem(
79+
title: String,
80+
description: String,
81+
@DrawableRes icon: Int,
82+
isSelected: Boolean,
83+
onClick: () -> Unit,
84+
modifier: Modifier = Modifier,
85+
) {
86+
Row(
87+
modifier = modifier
88+
.clickableWithoutRipple { onClick() }
89+
.padding(vertical = 14.dp)
90+
.fillMaxWidth(),
91+
verticalAlignment = Alignment.CenterVertically,
92+
horizontalArrangement = Arrangement.Center,
93+
) {
94+
BitnagilIcon(id = icon, tint = null)
95+
96+
Spacer(modifier = Modifier.width(14.dp))
97+
98+
Column {
99+
Text(
100+
text = title,
101+
color = BitnagilTheme.colors.coolGray10,
102+
style = BitnagilTheme.typography.body1Medium,
103+
)
104+
105+
Text(
106+
text = description,
107+
color = BitnagilTheme.colors.coolGray70,
108+
style = BitnagilTheme.typography.body2Medium,
109+
)
110+
}
111+
112+
Spacer(modifier = Modifier.weight(1f))
113+
114+
if (isSelected) {
115+
BitnagilIcon(
116+
id = R.drawable.ic_check_md,
117+
tint = BitnagilTheme.colors.orange500,
118+
)
119+
}
120+
}
121+
}
122+
123+
@Preview
124+
@Composable
125+
private fun ReportCategoryBottomSheetPreview() {
126+
ReportCategoryBottomSheet(
127+
selectedCategory = ReportCategory.WaterFacilities,
128+
onDismiss = {},
129+
onSelected = {},
130+
)
131+
}
132+
133+
@Preview(showBackground = true)
134+
@Composable
135+
private fun ReportCategoryItemPreview() {
136+
ReportCategoryItem(
137+
title = "교통 시설",
138+
description = "어쩌구",
139+
icon = R.drawable.ic_check_md,
140+
isSelected = true,
141+
onClick = {},
142+
)
143+
}
Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.threegap.bitnagil.presentation.reporthistory.model
22

3+
import com.threegap.bitnagil.designsystem.R
4+
35
enum class ReportCategory(
46
val title: String,
57
val description: String,
@@ -8,21 +10,21 @@ enum class ReportCategory(
810
TrafficFacilities(
911
title = "교통 시설",
1012
description = "신호등 고장, 표지판 파손, 횡단보도 등",
11-
iconResourceId = -1,
13+
iconResourceId = R.drawable.ic_check_md,
1214
),
1315
LightingFacilities(
1416
title = "조명 시설",
1517
description = "가로등, 보안등 파손 등",
16-
iconResourceId = -1,
18+
iconResourceId = R.drawable.ic_check_md,
1719
),
1820
WaterFacilities(
1921
title = "상하수도 시설",
2022
description = "맨홀 뚜껑 손상 등",
21-
iconResourceId = -1,
23+
iconResourceId = R.drawable.ic_check_md,
2224
),
2325
Amenities(
2426
title = "편의 시설",
2527
description = "벤치 파손, 휴지통 넘침 등",
26-
iconResourceId = -1,
28+
iconResourceId = R.drawable.ic_check_md,
2729
),
2830
}

0 commit comments

Comments
 (0)