Skip to content

Commit 75bfb01

Browse files
committed
refactor(sample): replace emoji glyphs with self-defined vector icons
1 parent 03a39b9 commit 75bfb01

7 files changed

Lines changed: 238 additions & 31 deletions

File tree

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
/*
2+
* Copyright (C) 2026 compose-jindong
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.github.compose.jindong.sample.components
17+
18+
import androidx.compose.ui.graphics.Color
19+
import androidx.compose.ui.graphics.SolidColor
20+
import androidx.compose.ui.graphics.StrokeCap
21+
import androidx.compose.ui.graphics.StrokeJoin
22+
import androidx.compose.ui.graphics.vector.ImageVector
23+
import androidx.compose.ui.graphics.vector.path
24+
import androidx.compose.ui.unit.dp
25+
26+
/**
27+
* Self-defined [ImageVector]s for the harness, replacing emoji/text glyphs that render as inconsistent
28+
* color emoji on iOS. All icons live on a 24x24 viewport; filled icons (Play) tint via the [Icon]
29+
* `tint`, stroked icons (chevrons, close, theme) use a 2px stroke also driven by `tint`.
30+
*
31+
* Built lazily and memoized so the path is allocated once per call site.
32+
*/
33+
object JindongIcons {
34+
35+
/** Solid right-pointing play triangle. */
36+
val Play: ImageVector
37+
get() = cache("play") {
38+
filled {
39+
moveTo(8f, 5f)
40+
lineTo(19f, 12f)
41+
lineTo(8f, 19f)
42+
close()
43+
}
44+
}
45+
46+
/** Crescent moon (light theme indicator: tap to go dark). */
47+
val Moon: ImageVector
48+
get() = cache("moon") {
49+
filled {
50+
// Crescent = outer circle minus an offset circle, expressed as a single subpath.
51+
moveTo(21f, 14.5f)
52+
arcToRelative(8f, 8f, 0f, true, true, -9.5f, -11f)
53+
arcToRelative(6.2f, 6.2f, 0f, false, false, 9.5f, 11f)
54+
close()
55+
}
56+
}
57+
58+
/** Sun with rays (dark theme indicator: tap to go light). */
59+
val Sun: ImageVector
60+
get() = cache("sun") {
61+
stroked {
62+
// Core
63+
moveTo(12f, 8.2f)
64+
arcToRelative(3.8f, 3.8f, 0f, true, true, 0f, 7.6f)
65+
arcToRelative(3.8f, 3.8f, 0f, true, true, 0f, -7.6f)
66+
close()
67+
// Rays
68+
moveTo(12f, 2.5f)
69+
lineTo(12f, 4.5f)
70+
moveTo(12f, 19.5f)
71+
lineTo(12f, 21.5f)
72+
moveTo(2.5f, 12f)
73+
lineTo(4.5f, 12f)
74+
moveTo(19.5f, 12f)
75+
lineTo(21.5f, 12f)
76+
moveTo(5.2f, 5.2f)
77+
lineTo(6.6f, 6.6f)
78+
moveTo(17.4f, 17.4f)
79+
lineTo(18.8f, 18.8f)
80+
moveTo(18.8f, 5.2f)
81+
lineTo(17.4f, 6.6f)
82+
moveTo(6.6f, 17.4f)
83+
lineTo(5.2f, 18.8f)
84+
}
85+
}
86+
87+
/** Left chevron (back). */
88+
val ChevronLeft: ImageVector
89+
get() = cache("chevron-left") {
90+
stroked {
91+
moveTo(15f, 5f)
92+
lineTo(8f, 12f)
93+
lineTo(15f, 19f)
94+
}
95+
}
96+
97+
/** Right chevron (navigation affordance). */
98+
val ChevronRight: ImageVector
99+
get() = cache("chevron-right") {
100+
stroked {
101+
moveTo(9f, 5f)
102+
lineTo(16f, 12f)
103+
lineTo(9f, 19f)
104+
}
105+
}
106+
107+
/** Down caret (accordion expand indicator). */
108+
val CaretDown: ImageVector
109+
get() = cache("caret-down") {
110+
stroked {
111+
moveTo(5f, 9f)
112+
lineTo(12f, 16f)
113+
lineTo(19f, 9f)
114+
}
115+
}
116+
117+
/** Close / delete (X). */
118+
val Close: ImageVector
119+
get() = cache("close") {
120+
stroked {
121+
moveTo(6f, 6f)
122+
lineTo(18f, 18f)
123+
moveTo(18f, 6f)
124+
lineTo(6f, 18f)
125+
}
126+
}
127+
}
128+
129+
private val iconCache = HashMap<String, ImageVector>()
130+
131+
private fun cache(name: String, build: ImageVector.Builder.() -> Unit): ImageVector = iconCache.getOrPut(name) {
132+
ImageVector.Builder(
133+
name = name,
134+
defaultWidth = 24.dp,
135+
defaultHeight = 24.dp,
136+
viewportWidth = 24f,
137+
viewportHeight = 24f,
138+
).apply(build).build()
139+
}
140+
141+
/** Adds a filled path tinted by the [Icon] color (fillAlpha 1). */
142+
private fun ImageVector.Builder.filled(pathBuilder: androidx.compose.ui.graphics.vector.PathBuilder.() -> Unit) {
143+
path(
144+
fill = SolidColor(Color.Black),
145+
stroke = null,
146+
pathBuilder = pathBuilder,
147+
)
148+
}
149+
150+
/** Adds a 2px stroked path tinted by the [Icon] color (no fill). */
151+
private fun ImageVector.Builder.stroked(pathBuilder: androidx.compose.ui.graphics.vector.PathBuilder.() -> Unit) {
152+
path(
153+
fill = null,
154+
stroke = SolidColor(Color.Black),
155+
strokeLineWidth = 2f,
156+
strokeLineCap = StrokeCap.Round,
157+
strokeLineJoin = StrokeJoin.Round,
158+
pathBuilder = pathBuilder,
159+
)
160+
}

sample/shared/src/commonMain/kotlin/io/github/compose/jindong/sample/components/JindongScaffold.kt

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import androidx.compose.foundation.layout.windowInsetsPadding
3838
import androidx.compose.foundation.rememberScrollState
3939
import androidx.compose.foundation.shape.RoundedCornerShape
4040
import androidx.compose.foundation.verticalScroll
41+
import androidx.compose.material3.Icon
4142
import androidx.compose.material3.Text
4243
import androidx.compose.runtime.Composable
4344
import androidx.compose.ui.Alignment
@@ -50,7 +51,6 @@ import androidx.compose.ui.semantics.contentDescription
5051
import androidx.compose.ui.semantics.semantics
5152
import androidx.compose.ui.text.font.FontWeight
5253
import androidx.compose.ui.unit.dp
53-
import androidx.compose.ui.unit.sp
5454
import io.github.compose.jindong.sample.nav.Screen
5555
import io.github.compose.jindong.sample.theme.Dimens
5656
import io.github.compose.jindong.sample.theme.JindongTheme
@@ -149,7 +149,12 @@ private fun AppBar(
149149
.semantics { contentDescription = "Back" },
150150
contentAlignment = Alignment.Center,
151151
) {
152-
Text(text = "", fontSize = 20.sp, color = colors.text2)
152+
Icon(
153+
imageVector = JindongIcons.ChevronLeft,
154+
contentDescription = "Back",
155+
tint = colors.text2,
156+
modifier = Modifier.size(22.dp),
157+
)
153158
}
154159
}
155160

@@ -203,7 +208,12 @@ private fun ThemeToggle(
203208
.border(Dimens.stroke, colors.border, RoundedCornerShape(Dimens.radiusIcon)),
204209
contentAlignment = Alignment.Center,
205210
) {
206-
Text(text = if (isDark) "" else "", fontSize = 13.sp, color = colors.text2)
211+
Icon(
212+
imageVector = if (isDark) JindongIcons.Sun else JindongIcons.Moon,
213+
contentDescription = null,
214+
tint = colors.text2,
215+
modifier = Modifier.size(15.dp),
216+
)
207217
}
208218
}
209219
}

sample/shared/src/commonMain/kotlin/io/github/compose/jindong/sample/components/PlayButton.kt

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,15 @@ package io.github.compose.jindong.sample.components
1717

1818
import androidx.compose.foundation.background
1919
import androidx.compose.foundation.clickable
20+
import androidx.compose.foundation.layout.Arrangement
2021
import androidx.compose.foundation.layout.Box
22+
import androidx.compose.foundation.layout.Row
2123
import androidx.compose.foundation.layout.defaultMinSize
2224
import androidx.compose.foundation.layout.fillMaxWidth
2325
import androidx.compose.foundation.layout.padding
26+
import androidx.compose.foundation.layout.size
2427
import androidx.compose.foundation.shape.RoundedCornerShape
28+
import androidx.compose.material3.Icon
2529
import androidx.compose.material3.Text
2630
import androidx.compose.runtime.Composable
2731
import androidx.compose.ui.Alignment
@@ -32,14 +36,15 @@ import io.github.compose.jindong.sample.theme.Dimens
3236
import io.github.compose.jindong.sample.theme.JindongTheme
3337

3438
/**
35-
* Full-width primary action button. Inverted ink/white surface ([JindongColors.btnBg] /
36-
* [JindongColors.btnText]) — not accent — per the handoff. Wrapped to a >= 48dp hit area.
39+
* Full-width primary action button: a [JindongIcons.Play] glyph plus [text] label. Inverted ink/white
40+
* surface ([JindongColors.btnBg] / [JindongColors.btnText]) — not accent — per the handoff. Wrapped
41+
* to a >= 48dp hit area.
3742
*/
3843
@Composable
3944
fun PlayButton(
4045
onClick: () -> Unit,
4146
modifier: Modifier = Modifier,
42-
text: String = "Play",
47+
text: String = "Play",
4348
) {
4449
Box(
4550
modifier =
@@ -52,10 +57,21 @@ fun PlayButton(
5257
.padding(14.dp),
5358
contentAlignment = Alignment.Center,
5459
) {
55-
Text(
56-
text = text,
57-
style = JindongTheme.typography.cardTitle,
58-
color = JindongTheme.colors.btnText,
59-
)
60+
Row(
61+
horizontalArrangement = Arrangement.spacedBy(6.dp),
62+
verticalAlignment = Alignment.CenterVertically,
63+
) {
64+
Icon(
65+
imageVector = JindongIcons.Play,
66+
contentDescription = null,
67+
tint = JindongTheme.colors.btnText,
68+
modifier = Modifier.size(15.dp),
69+
)
70+
Text(
71+
text = text,
72+
style = JindongTheme.typography.cardTitle,
73+
color = JindongTheme.colors.btnText,
74+
)
75+
}
6076
}
6177
}

sample/shared/src/commonMain/kotlin/io/github/compose/jindong/sample/screens/HomeScreen.kt

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,10 @@ import androidx.compose.foundation.layout.Column
2424
import androidx.compose.foundation.layout.Row
2525
import androidx.compose.foundation.layout.fillMaxWidth
2626
import androidx.compose.foundation.layout.padding
27+
import androidx.compose.foundation.layout.size
2728
import androidx.compose.foundation.layout.widthIn
2829
import androidx.compose.foundation.shape.RoundedCornerShape
30+
import androidx.compose.material3.Icon
2931
import androidx.compose.material3.Text
3032
import androidx.compose.runtime.Composable
3133
import androidx.compose.ui.Alignment
@@ -39,10 +41,10 @@ import androidx.compose.ui.text.buildAnnotatedString
3941
import androidx.compose.ui.text.font.FontWeight
4042
import androidx.compose.ui.text.withStyle
4143
import androidx.compose.ui.unit.dp
42-
import androidx.compose.ui.unit.sp
4344
import io.github.compose.jindong.HapticPlatform
4445
import io.github.compose.jindong.rememberHapticCapabilities
4546
import io.github.compose.jindong.sample.components.CapabilityBadge
47+
import io.github.compose.jindong.sample.components.JindongIcons
4648
import io.github.compose.jindong.sample.components.MonoLabel
4749
import io.github.compose.jindong.sample.components.ScreenDescription
4850
import io.github.compose.jindong.sample.components.SectionCard
@@ -209,7 +211,12 @@ private fun ModuleRow(
209211
)
210212
}
211213
}
212-
Text(text = "", fontSize = 16.sp, color = colors.border2)
214+
Icon(
215+
imageVector = JindongIcons.ChevronRight,
216+
contentDescription = null,
217+
tint = colors.border2,
218+
modifier = Modifier.size(16.dp),
219+
)
213220
}
214221
}
215222

sample/shared/src/commonMain/kotlin/io/github/compose/jindong/sample/screens/IntensityLabScreen.kt

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import androidx.compose.foundation.layout.padding
2929
import androidx.compose.foundation.layout.size
3030
import androidx.compose.foundation.layout.width
3131
import androidx.compose.foundation.shape.RoundedCornerShape
32+
import androidx.compose.material3.Icon
3233
import androidx.compose.material3.Slider
3334
import androidx.compose.material3.SliderDefaults
3435
import androidx.compose.material3.Text
@@ -56,6 +57,7 @@ import io.github.compose.jindong.dsl.Delay
5657
import io.github.compose.jindong.dsl.Haptic
5758
import io.github.compose.jindong.dsl.Sequence
5859
import io.github.compose.jindong.sample.components.HapticTimeline
60+
import io.github.compose.jindong.sample.components.JindongIcons
5961
import io.github.compose.jindong.sample.components.PlayButton
6062
import io.github.compose.jindong.sample.components.ScreenDescription
6163
import io.github.compose.jindong.sample.components.TimelineMapper
@@ -116,7 +118,7 @@ fun IntensityLabScreen(modifier: Modifier = Modifier) {
116118

117119
VGap(16.dp)
118120

119-
PlayButton(onClick = { playAll++ }, text = "Play all (ascending)")
121+
PlayButton(onClick = { playAll++ }, text = "Play all (ascending)")
120122

121123
VGap(18.dp)
122124

@@ -266,7 +268,7 @@ private fun CustomCard(
266268
}
267269
}
268270

269-
/** Small bordered "▶" affordance used on the rows and the custom card (>= 48dp hit area). */
271+
/** Small bordered play affordance used on the rows and the custom card (>= 48dp hit area). */
270272
@Composable
271273
private fun PlayChip(onClick: () -> Unit) {
272274
val colors = JindongTheme.colors
@@ -283,10 +285,11 @@ private fun PlayChip(onClick: () -> Unit) {
283285
.padding(horizontal = 14.dp, vertical = 7.dp),
284286
contentAlignment = Alignment.Center,
285287
) {
286-
Text(
287-
text = "",
288-
style = JindongTheme.typography.bodySmall.copy(fontWeight = FontWeight.SemiBold),
289-
color = colors.text2,
288+
Icon(
289+
imageVector = JindongIcons.Play,
290+
contentDescription = "Play",
291+
tint = colors.text2,
292+
modifier = Modifier.size(13.dp),
290293
)
291294
}
292295
}

0 commit comments

Comments
 (0)