Skip to content

Commit f87fe3d

Browse files
authored
Merge pull request #89 from Nexters/feature/add-keyword-component-FD-298
[FD-298] keyword component 추가
2 parents 56d251e + 0522319 commit f87fe3d

9 files changed

Lines changed: 326 additions & 196 deletions

File tree

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package com.nexters.fooddiary.core.ui.component
2+
3+
import androidx.compose.foundation.BorderStroke
4+
import androidx.compose.foundation.layout.PaddingValues
5+
import androidx.compose.foundation.shape.RoundedCornerShape
6+
import androidx.compose.material3.Button
7+
import androidx.compose.material3.ButtonColors
8+
import androidx.compose.material3.ButtonDefaults
9+
import androidx.compose.material3.Text
10+
import androidx.compose.runtime.Composable
11+
import androidx.compose.ui.Modifier
12+
import androidx.compose.ui.graphics.Color
13+
import androidx.compose.ui.text.font.FontWeight
14+
import androidx.compose.ui.unit.dp
15+
import com.nexters.fooddiary.core.ui.theme.AppTypography
16+
import com.nexters.fooddiary.core.ui.theme.White
17+
18+
@Composable
19+
fun CommonCircleButton(
20+
modifier: Modifier = Modifier,
21+
onClick: () -> Unit,
22+
buttonText: String,
23+
contentColor: Color = White,
24+
border: BorderStroke? = null,
25+
buttonColors: ButtonColors = ButtonDefaults.buttonColors(),
26+
) {
27+
Button(
28+
onClick = onClick,
29+
modifier = modifier,
30+
shape = RoundedCornerShape(999.dp),
31+
border = border,
32+
colors = buttonColors,
33+
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 12.dp),
34+
) {
35+
Text(
36+
text = buttonText,
37+
style = AppTypography.p14.copy(fontWeight = FontWeight.Medium),
38+
color = contentColor,
39+
)
40+
}
41+
}
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
package com.nexters.fooddiary.core.ui.component
2+
3+
import androidx.compose.foundation.Image
4+
import androidx.compose.foundation.background
5+
import androidx.compose.foundation.clickable
6+
import androidx.compose.foundation.layout.Arrangement
7+
import androidx.compose.foundation.layout.Box
8+
import androidx.compose.foundation.layout.Column
9+
import androidx.compose.foundation.layout.FlowRow
10+
import androidx.compose.foundation.layout.Row
11+
import androidx.compose.foundation.layout.fillMaxWidth
12+
import androidx.compose.foundation.layout.padding
13+
import androidx.compose.foundation.layout.size
14+
import androidx.compose.foundation.shape.CircleShape
15+
import androidx.compose.foundation.shape.RoundedCornerShape
16+
import androidx.compose.material.icons.Icons
17+
import androidx.compose.material.icons.filled.Add
18+
import androidx.compose.material3.Icon
19+
import androidx.compose.material3.Text
20+
import androidx.compose.runtime.Composable
21+
import androidx.compose.runtime.key
22+
import androidx.compose.ui.Alignment
23+
import androidx.compose.ui.Modifier
24+
import androidx.compose.ui.draw.clip
25+
import androidx.compose.ui.graphics.Color
26+
import androidx.compose.ui.res.painterResource
27+
import androidx.compose.ui.text.font.FontWeight
28+
import androidx.compose.ui.tooling.preview.Preview
29+
import androidx.compose.ui.unit.dp
30+
import com.nexters.fooddiary.core.ui.R.drawable
31+
import com.nexters.fooddiary.core.ui.theme.AppTypography
32+
import com.nexters.fooddiary.core.ui.theme.Gray050
33+
import com.nexters.fooddiary.core.ui.theme.Gray300
34+
import com.nexters.fooddiary.core.ui.theme.Gray400
35+
import com.nexters.fooddiary.core.ui.theme.Sd800
36+
import com.nexters.fooddiary.core.ui.theme.SdBase
37+
38+
private val KeywordChipShape = RoundedCornerShape(999.dp)
39+
40+
@Composable
41+
fun KeywordChip(
42+
text: String,
43+
modifier: Modifier = Modifier,
44+
selected: Boolean = false,
45+
onClick: (() -> Unit)? = null,
46+
selectedContainerColor: Color = Gray050,
47+
selectedContentColor: Color = Color.Black,
48+
unselectedContainerColor: Color = Sd800,
49+
unselectedContentColor: Color = Gray400,
50+
trailingContent: @Composable (() -> Unit)? = null,
51+
) {
52+
val containerColor = if (selected) selectedContainerColor else unselectedContainerColor
53+
val contentColor = if (selected) selectedContentColor else unselectedContentColor
54+
55+
Row(
56+
modifier = modifier
57+
.background(color = containerColor, shape = KeywordChipShape)
58+
.let { base ->
59+
if (onClick == null) base else base.clickable(onClick = onClick)
60+
}
61+
.padding(start = 14.dp, end = 14.dp, top = 8.dp, bottom = 8.dp),
62+
verticalAlignment = Alignment.CenterVertically,
63+
horizontalArrangement = Arrangement.spacedBy(8.dp),
64+
) {
65+
Text(
66+
text = text,
67+
style = AppTypography.p14,
68+
color = contentColor,
69+
)
70+
trailingContent?.invoke()
71+
}
72+
}
73+
74+
@Composable
75+
fun KeywordChipGroup(
76+
keywords: Collection<String>,
77+
modifier: Modifier = Modifier,
78+
selectedKeywords: Set<String> = emptySet(),
79+
onKeywordClick: ((String) -> Unit)? = null,
80+
horizontalSpacing: Int = 8,
81+
verticalSpacing: Int = 8,
82+
selectedContainerColor: Color = Gray050,
83+
selectedContentColor: Color = Color.Black,
84+
unselectedContainerColor: Color = Sd800,
85+
unselectedContentColor: Color = Gray400,
86+
) {
87+
FlowRow(
88+
modifier = modifier,
89+
horizontalArrangement = Arrangement.spacedBy(horizontalSpacing.dp),
90+
verticalArrangement = Arrangement.spacedBy(verticalSpacing.dp),
91+
) {
92+
keywords.forEach { keyword ->
93+
key(keyword) {
94+
KeywordChip(
95+
text = keyword,
96+
selected = selectedKeywords.contains(keyword),
97+
onClick = onKeywordClick?.let { click -> { click(keyword) } },
98+
selectedContainerColor = selectedContainerColor,
99+
selectedContentColor = selectedContentColor,
100+
unselectedContainerColor = unselectedContainerColor,
101+
unselectedContentColor = unselectedContentColor,
102+
)
103+
}
104+
}
105+
}
106+
}
107+
108+
@Composable
109+
fun EditableKeywordChipGroup(
110+
keywords: List<String>,
111+
onAddClick: () -> Unit,
112+
onKeywordRemove: (String) -> Unit,
113+
removeContentDescription: String,
114+
addContentDescription: String,
115+
modifier: Modifier = Modifier,
116+
horizontalSpacing: Int = 8,
117+
verticalSpacing: Int = 4,
118+
) {
119+
val removePainter = painterResource(drawable.ic_circle_close)
120+
121+
FlowRow(
122+
modifier = modifier.fillMaxWidth(),
123+
horizontalArrangement = Arrangement.spacedBy(horizontalSpacing.dp),
124+
verticalArrangement = Arrangement.spacedBy(verticalSpacing.dp),
125+
) {
126+
keywords.forEach { keyword ->
127+
key(keyword) {
128+
KeywordChip(
129+
text = keyword,
130+
trailingContent = {
131+
Image(
132+
painter = removePainter,
133+
contentDescription = removeContentDescription,
134+
modifier = Modifier
135+
.size(18.dp)
136+
.clickable { onKeywordRemove(keyword) },
137+
)
138+
},
139+
)
140+
}
141+
}
142+
Box(
143+
modifier = Modifier
144+
.size(34.dp)
145+
.clip(CircleShape)
146+
.background(Sd800)
147+
.clickable(onClick = onAddClick),
148+
contentAlignment = Alignment.Center,
149+
) {
150+
Icon(
151+
imageVector = Icons.Default.Add,
152+
contentDescription = addContentDescription,
153+
tint = Gray400,
154+
)
155+
}
156+
}
157+
}
158+
159+
@Composable
160+
fun TasteKeywordSection(
161+
title: String,
162+
keywords: List<String>,
163+
modifier: Modifier = Modifier,
164+
selectedKeywords: Set<String> = emptySet(),
165+
onKeywordClick: ((String) -> Unit)? = null,
166+
) {
167+
Row(
168+
modifier = modifier
169+
.background(
170+
color = Color.White.copy(alpha = 0.02f),
171+
shape = RoundedCornerShape(16.dp),
172+
)
173+
.padding(horizontal = 16.dp, vertical = 24.dp),
174+
) {
175+
Column(
176+
verticalArrangement = Arrangement.spacedBy(32.dp),
177+
) {
178+
Text(
179+
text = title,
180+
style = AppTypography.p15.copy(fontWeight = FontWeight.SemiBold),
181+
color = Gray050,
182+
)
183+
KeywordChipGroup(
184+
keywords = keywords,
185+
selectedKeywords = selectedKeywords,
186+
onKeywordClick = onKeywordClick,
187+
horizontalSpacing = 8,
188+
verticalSpacing = 8,
189+
unselectedContainerColor = Sd800,
190+
unselectedContentColor = Gray400,
191+
)
192+
}
193+
}
194+
}
195+
196+
@Preview(showBackground = true, backgroundColor = 0xFF191821)
197+
@Composable
198+
private fun TasteKeywordSectionPreview() {
199+
Row(
200+
modifier = Modifier
201+
.background(SdBase)
202+
.padding(16.dp),
203+
) {
204+
TasteKeywordSection(
205+
title = "나의 입맛과\n가장 잘 어울리는 키워드",
206+
keywords = listOf("#집밥", "#중식", "#튀김", "#소면"),
207+
)
208+
}
209+
}
210+
211+
@Preview(showBackground = true, backgroundColor = 0xFF191821)
212+
@Composable
213+
private fun KeywordChipGroupSelectedPreview() {
214+
KeywordChipGroup(
215+
keywords = listOf("한식", "일식", "중식", "양식"),
216+
selectedKeywords = setOf("중식"),
217+
unselectedContentColor = Gray300,
218+
)
219+
}

gradle/libs.versions.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ hiltNavigationCompose = "1.3.0"
1717
retrofit = "3.0.0"
1818
okhttp = "5.3.2"
1919
kotlinxSerialization = "1.9.0"
20+
kotlinxImmutable = "0.3.8"
2021
coroutines = "1.10.2"
2122
mavericks = "3.0.12"
2223
ksp = "2.2.21-2.0.5"
@@ -88,6 +89,7 @@ okhttp-mockwebserver = { group = "com.squareup.okhttp3", name = "mockwebserver",
8889

8990
# Kotlinx Serialization
9091
kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinxSerialization" }
92+
kotlinx-collections-immutable = { group = "org.jetbrains.kotlinx", name = "kotlinx-collections-immutable", version.ref = "kotlinxImmutable" }
9193

9294
# Coroutines
9395
kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "coroutines" }

presentation/modify/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ dependencies {
4646
implementation(libs.androidx.lifecycle.viewmodel.compose)
4747
implementation(libs.androidx.navigation.compose)
4848
implementation(libs.kotlinx.serialization.core)
49+
implementation(libs.kotlinx.collections.immutable)
4950

5051
implementation(libs.coil.compose)
5152
implementation(libs.coil.network.okhttp)

0 commit comments

Comments
 (0)