Skip to content

Commit c7caa58

Browse files
committed
Merge branch 'develop' into BOOK-95-feature/#29
# Conflicts: # feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterPresenter.kt # feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterUi.kt # feature/record/src/main/res/values/strings.xml
2 parents 34d9234 + 1d183ca commit c7caa58

File tree

12 files changed

+705
-136
lines changed

12 files changed

+705
-136
lines changed

core/designsystem/src/main/kotlin/com/ninecraft/booket/core/designsystem/RecordStep.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
package com.ninecraft.booket.core.designsystem
22

33
enum class RecordStep {
4-
REGISTER,
5-
APPRECIATION,
4+
QUOTE,
65
EMOTION,
6+
IMPRESSION,
77
;
88

99
val value: Int get() = ordinal

core/designsystem/src/main/kotlin/com/ninecraft/booket/core/designsystem/component/RecordProgressBar.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ fun RecordProgressBar(
3030
.height(6.dp)
3131
.clip(RoundedCornerShape(ReedTheme.radius.full))
3232
.background(
33-
color = if (currentStep.value == index) {
33+
color = if (index <= currentStep.ordinal) {
3434
ReedTheme.colors.bgPrimary
3535
} else {
3636
ReedTheme.colors.bgDisabled

core/network/src/main/kotlin/com/ninecraft/booket/core/network/service/ReedService.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,6 @@ interface ReedService {
5959
@Query("status") status: String? = null,
6060
@Query("page") page: Int,
6161
@Query("size") size: Int,
62-
@Query("sort") sort: String = "date_desc",
62+
@Query("sort") sort: String = "CREATED_DATE_DESC",
6363
): LibraryResponse
6464
}
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: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
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(verticalAlignment = Alignment.Bottom) {
56+
Text(
57+
text = stringResource(R.string.impression_guide_blank),
58+
color = Color(0xFFD6D6D6),
59+
style = ReedTheme.typography.label1SemiBold,
60+
)
61+
Text(
62+
text = impressionText,
63+
color = ReedTheme.colors.contentPrimary,
64+
style = ReedTheme.typography.label1SemiBold,
65+
)
66+
}
67+
}
68+
}
69+
70+
@ComponentPreview
71+
@Composable
72+
private fun ImpressionGuideBoxPreview() {
73+
ReedTheme {
74+
ImpressionGuideBox(
75+
onClick = {},
76+
impressionText = "에서 위로 받았다",
77+
)
78+
}
79+
}

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

Lines changed: 82 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import androidx.compose.runtime.Composable
66
import androidx.compose.runtime.getValue
77
import androidx.compose.runtime.mutableStateOf
88
import androidx.compose.runtime.setValue
9+
import com.ninecraft.booket.core.designsystem.RecordStep
910
import com.ninecraft.booket.feature.screens.OcrScreen
1011
import com.ninecraft.booket.feature.screens.RecordScreen
1112
import com.slack.circuit.codegen.annotations.CircuitInject
@@ -17,15 +18,33 @@ import dagger.assisted.Assisted
1718
import dagger.assisted.AssistedFactory
1819
import dagger.assisted.AssistedInject
1920
import dagger.hilt.android.components.ActivityRetainedComponent
21+
import kotlinx.collections.immutable.toPersistentList
2022

2123
class RecordRegisterPresenter @AssistedInject constructor(
2224
@Assisted private val navigator: Navigator,
23-
) : Presenter<RecordUiState> {
25+
) : Presenter<RecordRegisterUiState> {
2426

2527
@Composable
26-
override fun present(): RecordUiState {
28+
override fun present(): RecordRegisterUiState {
29+
var currentStep by rememberRetained { mutableStateOf(RecordStep.QUOTE) }
2730
val recordPageState = rememberTextFieldState()
2831
val recordSentenceState = rememberTextFieldState()
32+
val impressionGuideList by rememberRetained {
33+
mutableStateOf(
34+
listOf(
35+
"에서 위로 받았다",
36+
"이 마음에 남았다",
37+
"에서 작가의 의도가 궁금하다",
38+
"에 대한 다른 사람들의 생각이 궁금하다",
39+
"에서 크게 공감이 된다",
40+
"을 보고 예전 기억이 났다",
41+
"에서 문장에 머물렀다",
42+
).toPersistentList(),
43+
)
44+
}
45+
var selectedImpressionGuide by rememberRetained { mutableStateOf("") }
46+
val impressionState = rememberTextFieldState()
47+
var isImpressionGuideBottomSheetVisible by rememberRetained { mutableStateOf(false) }
2948
var isExitDialogVisible by rememberRetained { mutableStateOf(false) }
3049
val ocrNavigator = rememberAnsweringNavigator<OcrScreen.OcrResult>(navigator) { result ->
3150
recordSentenceState.edit {
@@ -37,7 +56,19 @@ class RecordRegisterPresenter @AssistedInject constructor(
3756
fun handleEvent(event: RecordRegisterUiEvent) {
3857
when (event) {
3958
is RecordRegisterUiEvent.OnBackButtonClick -> {
40-
isExitDialogVisible = true
59+
when (currentStep) {
60+
RecordStep.QUOTE -> {
61+
isExitDialogVisible = true
62+
}
63+
64+
RecordStep.EMOTION -> {
65+
currentStep = RecordStep.QUOTE
66+
}
67+
68+
RecordStep.IMPRESSION -> {
69+
currentStep = RecordStep.EMOTION
70+
}
71+
}
4172
}
4273

4374
is RecordRegisterUiEvent.OnClearClick -> {
@@ -56,14 +87,60 @@ class RecordRegisterPresenter @AssistedInject constructor(
5687
ocrNavigator.goTo(OcrScreen)
5788
}
5889

59-
is RecordRegisterUiEvent.OnNextButtonClick -> {}
90+
is RecordRegisterUiEvent.OnSelectEmotion -> {}
91+
92+
is RecordRegisterUiEvent.OnImpressionGuideButtonClick -> {
93+
selectedImpressionGuide = ""
94+
impressionState.edit {
95+
replace(0, length, "")
96+
}
97+
isImpressionGuideBottomSheetVisible = true
98+
}
99+
100+
is RecordRegisterUiEvent.OnSelectImpressionGuide -> {
101+
val index = event.index
102+
if (index in impressionGuideList.indices) {
103+
selectedImpressionGuide = impressionGuideList[index]
104+
impressionState.edit {
105+
replace(0, length, "")
106+
append(selectedImpressionGuide)
107+
}
108+
}
109+
}
110+
111+
is RecordRegisterUiEvent.OnSelectionConfirmed -> {}
112+
113+
is RecordRegisterUiEvent.OnImpressionGuideBottomSheetDismiss -> {
114+
isImpressionGuideBottomSheetVisible = false
115+
}
116+
117+
is RecordRegisterUiEvent.OnNextButtonClick -> {
118+
when (currentStep) {
119+
RecordStep.QUOTE -> {
120+
currentStep = RecordStep.EMOTION
121+
}
122+
123+
RecordStep.EMOTION -> {
124+
currentStep = RecordStep.IMPRESSION
125+
}
126+
127+
RecordStep.IMPRESSION -> {
128+
// TODO: (기록 완료 API 성공 시) 기록 상세 화면 이동
129+
}
130+
}
131+
}
60132
}
61133
}
62134

63-
return RecordUiState(
135+
return RecordRegisterUiState(
136+
currentStep = currentStep,
64137
recordPageState = recordPageState,
65138
recordSentenceState = recordSentenceState,
139+
impressionState = impressionState,
140+
impressionGuideList = impressionGuideList,
141+
isImpressionGuideBottomSheetVisible = isImpressionGuideBottomSheetVisible,
66142
isExitDialogVisible = isExitDialogVisible,
143+
selectedImpressionGuide = selectedImpressionGuide,
67144
eventSink = ::handleEvent,
68145
)
69146
}

0 commit comments

Comments
 (0)