Skip to content

Commit d6c59f5

Browse files
committed
[BOOK-165] feat: 감상평 가이드 구현
1 parent 6ec7ad0 commit d6c59f5

File tree

6 files changed

+338
-3
lines changed

6 files changed

+338
-3
lines changed
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
package com.ninecraft.booket.feature.record.component
2+
3+
import androidx.compose.foundation.layout.Arrangement
4+
import androidx.compose.foundation.layout.Column
5+
import androidx.compose.foundation.layout.Row
6+
import androidx.compose.foundation.layout.Spacer
7+
import androidx.compose.foundation.layout.fillMaxWidth
8+
import androidx.compose.foundation.layout.height
9+
import androidx.compose.foundation.layout.padding
10+
import androidx.compose.material3.ExperimentalMaterial3Api
11+
import androidx.compose.material3.Icon
12+
import androidx.compose.material3.SheetState
13+
import androidx.compose.material3.SheetValue
14+
import androidx.compose.material3.Text
15+
import androidx.compose.runtime.Composable
16+
import androidx.compose.ui.Alignment
17+
import androidx.compose.ui.Modifier
18+
import androidx.compose.ui.graphics.vector.ImageVector
19+
import androidx.compose.ui.res.stringResource
20+
import androidx.compose.ui.res.vectorResource
21+
import androidx.compose.ui.text.style.TextAlign
22+
import com.ninecraft.booket.core.common.extensions.clickableSingle
23+
import com.ninecraft.booket.core.designsystem.ComponentPreview
24+
import com.ninecraft.booket.core.designsystem.component.button.ReedButton
25+
import com.ninecraft.booket.core.designsystem.component.button.ReedButtonColorStyle
26+
import com.ninecraft.booket.core.designsystem.component.button.largeButtonStyle
27+
import com.ninecraft.booket.core.designsystem.theme.ReedTheme
28+
import com.ninecraft.booket.core.ui.component.ReedBottomSheet
29+
import com.ninecraft.booket.feature.record.R
30+
import kotlinx.collections.immutable.ImmutableList
31+
import kotlinx.collections.immutable.toPersistentList
32+
import com.ninecraft.booket.core.designsystem.R as designR
33+
34+
@OptIn(ExperimentalMaterial3Api::class)
35+
@Composable
36+
fun ImpressionGuideBottomSheet(
37+
onDismissRequest: () -> Unit,
38+
sheetState: SheetState,
39+
impressionGuideList: ImmutableList<String>,
40+
selectedImpressionGuide: String,
41+
onGuideClick: (Int) -> Unit,
42+
onCloseButtonClick: () -> Unit,
43+
onSelectionConfirmButtonClick: () -> Unit,
44+
) {
45+
ReedBottomSheet(
46+
onDismissRequest = {
47+
onDismissRequest()
48+
},
49+
sheetState = sheetState,
50+
) {
51+
Column(
52+
modifier = Modifier
53+
.padding(
54+
start = ReedTheme.spacing.spacing5,
55+
top = ReedTheme.spacing.spacing5,
56+
end = ReedTheme.spacing.spacing5,
57+
),
58+
horizontalAlignment = Alignment.CenterHorizontally,
59+
) {
60+
Row(
61+
modifier = Modifier.fillMaxWidth(),
62+
horizontalArrangement = Arrangement.SpaceBetween,
63+
) {
64+
Text(
65+
text = stringResource(R.string.impression_guide_bottomsheet_title),
66+
color = ReedTheme.colors.contentPrimary,
67+
textAlign = TextAlign.Center,
68+
style = ReedTheme.typography.heading2SemiBold,
69+
)
70+
Icon(
71+
imageVector = ImageVector.vectorResource(designR.drawable.ic_close),
72+
contentDescription = "Close Icon",
73+
modifier = Modifier.clickableSingle {
74+
onCloseButtonClick()
75+
},
76+
)
77+
}
78+
Spacer(modifier = Modifier.height(ReedTheme.spacing.spacing2))
79+
Text(
80+
text = stringResource(R.string.impression_guide_bottomsheet_description),
81+
modifier = Modifier.fillMaxWidth(),
82+
color = ReedTheme.colors.contentPrimary,
83+
style = ReedTheme.typography.label1Medium,
84+
)
85+
Spacer(modifier = Modifier.height(ReedTheme.spacing.spacing5))
86+
Column(
87+
modifier = Modifier.fillMaxWidth(),
88+
verticalArrangement = Arrangement.spacedBy(ReedTheme.spacing.spacing2),
89+
) {
90+
impressionGuideList.forEachIndexed { index, guide ->
91+
ImpressionGuideBox(
92+
onClick = {
93+
onGuideClick(index)
94+
},
95+
impressionText = guide,
96+
isSelected = selectedImpressionGuide == guide
97+
)
98+
}
99+
}
100+
Spacer(modifier = Modifier.height(ReedTheme.spacing.spacing3))
101+
Spacer(modifier = Modifier.height(ReedTheme.spacing.spacing4))
102+
ReedButton(
103+
onClick = {
104+
onSelectionConfirmButtonClick()
105+
},
106+
sizeStyle = largeButtonStyle,
107+
colorStyle = ReedButtonColorStyle.PRIMARY,
108+
modifier = Modifier.fillMaxWidth(),
109+
enabled = selectedImpressionGuide.isNotEmpty(),
110+
text = stringResource(R.string.impression_guide_bottomsheet_selection_confirm),
111+
)
112+
Spacer(modifier = Modifier.height(ReedTheme.spacing.spacing4))
113+
}
114+
}
115+
}
116+
117+
@OptIn(ExperimentalMaterial3Api::class)
118+
@ComponentPreview
119+
@Composable
120+
private fun ImpressionGuideBottomSheetPreview() {
121+
val sheetState = SheetState(
122+
skipPartiallyExpanded = true,
123+
initialValue = SheetValue.Expanded,
124+
positionalThreshold = { 0f },
125+
velocityThreshold = { 0f },
126+
)
127+
val impressionGuideList = listOf(
128+
"에서 위로 받았다",
129+
"이 마음에 남았다",
130+
"에서 작가의 의도가 궁금하다",
131+
"에 대한 다른 사람들의 생각이 궁금하다",
132+
"에서 크게 공감이 된다",
133+
"을 보고 예전 기억이 났다",
134+
"에서 문장에 머물렀다",
135+
).toPersistentList()
136+
137+
ReedTheme {
138+
ImpressionGuideBottomSheet(
139+
onDismissRequest = {},
140+
sheetState = sheetState,
141+
impressionGuideList = impressionGuideList,
142+
selectedImpressionGuide = "",
143+
onGuideClick = {},
144+
onCloseButtonClick = {},
145+
onSelectionConfirmButtonClick = {},
146+
)
147+
}
148+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package com.ninecraft.booket.feature.record.component
2+
3+
import androidx.compose.foundation.background
4+
import androidx.compose.foundation.border
5+
import androidx.compose.foundation.layout.Box
6+
import androidx.compose.foundation.layout.Row
7+
import androidx.compose.foundation.layout.fillMaxWidth
8+
import androidx.compose.foundation.layout.padding
9+
import androidx.compose.foundation.shape.RoundedCornerShape
10+
import androidx.compose.material3.Text
11+
import androidx.compose.runtime.Composable
12+
import androidx.compose.ui.Alignment
13+
import androidx.compose.ui.Modifier
14+
import androidx.compose.ui.draw.clip
15+
import androidx.compose.ui.graphics.Color
16+
import androidx.compose.ui.res.stringResource
17+
import androidx.compose.ui.unit.dp
18+
import com.ninecraft.booket.core.common.extensions.clickableSingle
19+
import com.ninecraft.booket.core.designsystem.ComponentPreview
20+
import com.ninecraft.booket.core.designsystem.theme.ReedTheme
21+
import com.ninecraft.booket.core.designsystem.theme.White
22+
import com.ninecraft.booket.feature.record.R
23+
24+
@Composable
25+
fun ImpressionGuideBox(
26+
onClick: () -> Unit,
27+
impressionText: String,
28+
modifier: Modifier = Modifier,
29+
isSelected: Boolean = false,
30+
) {
31+
val bgColor = if (isSelected) ReedTheme.colors.bgTertiary else White
32+
val borderColor = if (isSelected) ReedTheme.colors.borderBrand else ReedTheme.colors.borderPrimary
33+
34+
Box(
35+
modifier = modifier
36+
.fillMaxWidth()
37+
.background(
38+
color = bgColor,
39+
shape = RoundedCornerShape(ReedTheme.radius.sm),
40+
)
41+
.border(
42+
width = 1.dp,
43+
color = borderColor,
44+
shape = RoundedCornerShape(ReedTheme.radius.sm),
45+
)
46+
.clip(RoundedCornerShape(ReedTheme.radius.sm))
47+
.clickableSingle {
48+
onClick()
49+
}
50+
.padding(
51+
horizontal = ReedTheme.spacing.spacing4,
52+
vertical = ReedTheme.spacing.spacing4,
53+
),
54+
) {
55+
Row(
56+
verticalAlignment = Alignment.Bottom
57+
) {
58+
Text(
59+
text = stringResource(R.string.impression_guide_blank),
60+
color = Color(0xFFD6D6D6),
61+
style = ReedTheme.typography.label1SemiBold,
62+
)
63+
Text(
64+
text = impressionText,
65+
color = ReedTheme.colors.contentPrimary,
66+
style = ReedTheme.typography.label1SemiBold,
67+
)
68+
}
69+
}
70+
}
71+
72+
@ComponentPreview
73+
@Composable
74+
private fun ImpressionGuideBoxPreview() {
75+
ReedTheme {
76+
ImpressionGuideBox(
77+
onClick = {},
78+
impressionText = "에서 위로 받았다",
79+
)
80+
}
81+
}

feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterPresenter.kt

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import dagger.assisted.Assisted
1616
import dagger.assisted.AssistedFactory
1717
import dagger.assisted.AssistedInject
1818
import dagger.hilt.android.components.ActivityRetainedComponent
19+
import kotlinx.collections.immutable.toPersistentList
1920

2021
class RecordRegisterPresenter @AssistedInject constructor(
2122
@Assisted private val navigator: Navigator,
@@ -26,7 +27,22 @@ class RecordRegisterPresenter @AssistedInject constructor(
2627
var currentStep by rememberRetained { mutableStateOf(RecordStep.QUOTE) }
2728
val recordPageState = rememberTextFieldState()
2829
val recordSentenceState = rememberTextFieldState()
30+
val impressionGuideList by rememberRetained {
31+
mutableStateOf(
32+
listOf(
33+
"에서 위로 받았다",
34+
"이 마음에 남았다",
35+
"에서 작가의 의도가 궁금하다",
36+
"에 대한 다른 사람들의 생각이 궁금하다",
37+
"에서 크게 공감이 된다",
38+
"을 보고 예전 기억이 났다",
39+
"에서 문장에 머물렀다",
40+
).toPersistentList(),
41+
)
42+
}
43+
var selectedImpressionGuide by rememberRetained { mutableStateOf("") }
2944
val impressionState = rememberTextFieldState()
45+
var isImpressionGuideBottomSheetVisible by rememberRetained { mutableStateOf(false) }
3046
var isExitDialogVisible by rememberRetained { mutableStateOf(false) }
3147

3248
fun handleEvent(event: RecordRegisterUiEvent) {
@@ -60,6 +76,38 @@ class RecordRegisterPresenter @AssistedInject constructor(
6076
}
6177

6278
is RecordRegisterUiEvent.OnSentenceScanButtonClick -> {}
79+
80+
is RecordRegisterUiEvent.OnSelectEmotion -> {
81+
82+
}
83+
84+
is RecordRegisterUiEvent.OnImpressionGuideButtonClick -> {
85+
selectedImpressionGuide = ""
86+
impressionState.edit {
87+
replace(0, length, "")
88+
}
89+
isImpressionGuideBottomSheetVisible = true
90+
}
91+
92+
is RecordRegisterUiEvent.OnSelectImpressionGuide -> {
93+
val index = event.index
94+
if (index in impressionGuideList.indices) {
95+
selectedImpressionGuide = impressionGuideList[index]
96+
impressionState.edit {
97+
replace(0, length, "")
98+
append(selectedImpressionGuide)
99+
}
100+
}
101+
}
102+
103+
is RecordRegisterUiEvent.OnSelectionConfirmed -> {
104+
105+
}
106+
107+
is RecordRegisterUiEvent.OnImpressionGuideBottomSheetDismiss -> {
108+
isImpressionGuideBottomSheetVisible = false
109+
}
110+
63111
is RecordRegisterUiEvent.OnNextButtonClick -> {
64112
when (currentStep) {
65113
RecordStep.QUOTE -> {
@@ -83,7 +131,10 @@ class RecordRegisterPresenter @AssistedInject constructor(
83131
recordPageState = recordPageState,
84132
recordSentenceState = recordSentenceState,
85133
impressionState = impressionState,
134+
impressionGuideList = impressionGuideList,
135+
isImpressionGuideBottomSheetVisible = isImpressionGuideBottomSheetVisible,
86136
isExitDialogVisible = isExitDialogVisible,
137+
selectedImpressionGuide = selectedImpressionGuide,
87138
eventSink = ::handleEvent,
88139
)
89140
}

feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterUiState.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,18 @@ import androidx.compose.foundation.text.input.TextFieldState
44
import com.ninecraft.booket.core.designsystem.RecordStep
55
import com.slack.circuit.runtime.CircuitUiEvent
66
import com.slack.circuit.runtime.CircuitUiState
7+
import kotlinx.collections.immutable.ImmutableList
8+
import kotlinx.collections.immutable.persistentListOf
79

810
data class RecordRegisterUiState(
911
val currentStep: RecordStep = RecordStep.QUOTE,
1012
val recordPageState: TextFieldState = TextFieldState(),
1113
val recordSentenceState: TextFieldState = TextFieldState(),
14+
val selectedEmotion: String = "",
1215
val impressionState: TextFieldState = TextFieldState(),
16+
val impressionGuideList: ImmutableList<String> = persistentListOf(),
17+
val selectedImpressionGuide: String = "",
18+
val isImpressionGuideBottomSheetVisible: Boolean = false,
1319
val isExitDialogVisible: Boolean = false,
1420
val eventSink: (RecordRegisterUiEvent) -> Unit,
1521
) : CircuitUiState
@@ -19,6 +25,11 @@ sealed interface RecordRegisterUiEvent : CircuitUiEvent {
1925
data object OnClearClick : RecordRegisterUiEvent
2026
data object OnNextButtonClick : RecordRegisterUiEvent
2127
data object OnSentenceScanButtonClick : RecordRegisterUiEvent
28+
data object OnSelectEmotion : RecordRegisterUiEvent
29+
data object OnImpressionGuideButtonClick : RecordRegisterUiEvent
30+
data object OnImpressionGuideBottomSheetDismiss : RecordRegisterUiEvent
31+
data class OnSelectImpressionGuide(val index: Int) : RecordRegisterUiEvent
32+
data object OnSelectionConfirmed : RecordRegisterUiEvent
2233
data object OnExitDialogConfirm : RecordRegisterUiEvent
2334
data object OnExitDialogDismiss : RecordRegisterUiEvent
2435
}

0 commit comments

Comments
 (0)