Skip to content

Commit 84fe775

Browse files
committed
feat: Icon shape use RoundedPolygon impl
Signed-off-by: Hu Shenghao <dede.hu@qq.com>
1 parent c85767a commit 84fe775

File tree

9 files changed

+145
-116
lines changed

9 files changed

+145
-116
lines changed

app/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ dependencies {
9999
implementation(libs.dionsegijn.konfetti)
100100
implementation(libs.squareup.okio)
101101
implementation(libs.blurhash.android)
102+
implementation(libs.squircle.shape)
102103
debugImplementation(libs.squareup.leakcanary)
103104
implementation(libs.ktor.core)
104105
implementation(libs.ktor.android)

app/src/main/java/com/dede/android_eggs/views/main/compose/EasterEggLogo.kt

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.size
77
import androidx.compose.foundation.lazy.grid.GridCells
88
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
99
import androidx.compose.foundation.lazy.grid.items
10+
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
1011
import androidx.compose.runtime.Composable
1112
import androidx.compose.runtime.DisposableEffect
1213
import androidx.compose.runtime.getValue
@@ -15,7 +16,6 @@ import androidx.compose.runtime.remember
1516
import androidx.compose.runtime.setValue
1617
import androidx.compose.ui.Modifier
1718
import androidx.compose.ui.graphics.Matrix
18-
import androidx.compose.ui.graphics.asComposePath
1919
import androidx.compose.ui.layout.onSizeChanged
2020
import androidx.compose.ui.platform.LocalContext
2121
import androidx.compose.ui.res.painterResource
@@ -27,12 +27,12 @@ import androidx.compose.ui.unit.dp
2727
import com.dede.android_eggs.alterable_adaptive_icon.AlterableAdaptiveIcon
2828
import com.dede.android_eggs.local_provider.currentOutInspectionMode
2929
import com.dede.android_eggs.util.LocalEvent
30-
import com.dede.android_eggs.util.PathInflater
3130
import com.dede.android_eggs.util.Receiver
3231
import com.dede.android_eggs.views.main.util.EasterEggHelp
3332
import com.dede.android_eggs.views.main.util.EasterEggLogoSensorMatrixConvert
34-
import com.dede.android_eggs.views.settings.compose.basic.SettingPrefUtil
3533
import com.dede.android_eggs.views.settings.compose.prefs.IconShapePrefUtil
34+
import com.dede.android_eggs.views.settings.compose.prefs.getIconShapeRoundedPolygon
35+
import com.dede.android_eggs.views.settings.compose.prefs.toShapePlus
3636
import com.dede.basic.isAdaptiveIconDrawable
3737
import com.dede.basic.provider.EasterEgg
3838

@@ -48,28 +48,22 @@ fun PreviewEasterEggLogo() {
4848
}
4949
}
5050

51+
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
5152
@Composable
5253
fun EasterEggLogo(
5354
modifier: Modifier = Modifier,
5455
contentDescription: String? = null,
55-
mask: String? = null,
5656
sensor: Boolean = false,
5757
@DrawableRes res: Int,
5858
) {
5959
val context = LocalContext.current
6060
val isAdaptiveIcon = remember(res) { context.isAdaptiveIconDrawable(res) }
6161
if (isAdaptiveIcon) {
62-
val maskPath = remember(mask) {
63-
mask ?: IconShapePrefUtil.getMaskPath(context)
64-
}
65-
var maskP by remember {
66-
mutableStateOf(PathInflater.inflate(maskPath).asComposePath())
62+
var clipRoundedPolygon by remember {
63+
mutableStateOf(getIconShapeRoundedPolygon(context))
6764
}
6865
LocalEvent.Receiver(IconShapePrefUtil.ACTION_CHANGED) {
69-
val newMaskPath = it.getStringExtra(SettingPrefUtil.EXTRA_VALUE)
70-
if (newMaskPath != null) {
71-
maskP = PathInflater.inflate(newMaskPath).asComposePath()
72-
}
66+
clipRoundedPolygon = getIconShapeRoundedPolygon(context)
7367
}
7468
var foregroundMatrix by remember { mutableStateOf(Matrix()) }
7569
var size by remember { mutableStateOf(IntSize.Zero) }
@@ -100,7 +94,7 @@ fun EasterEggLogo(
10094
}
10195
AlterableAdaptiveIcon(
10296
modifier = modifier.onSizeChanged { size = it },
103-
maskPath = maskP,
97+
clipShape = clipRoundedPolygon.toShapePlus(),
10498
foregroundMatrix = foregroundMatrix,
10599
res = res,
106100
)

app/src/main/java/com/dede/android_eggs/views/settings/compose/prefs/IconShapePref.kt

Lines changed: 109 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
@file:OptIn(ExperimentalMaterial3ExpressiveApi::class)
2+
13
package com.dede.android_eggs.views.settings.compose.prefs
24

5+
import android.content.Context
36
import androidx.compose.foundation.Image
47
import androidx.compose.foundation.background
58
import androidx.compose.foundation.layout.Box
@@ -10,32 +13,40 @@ import androidx.compose.material.icons.Icons
1013
import androidx.compose.material.icons.rounded.CheckCircle
1114
import androidx.compose.material3.Card
1215
import androidx.compose.material3.CardDefaults
16+
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
1317
import androidx.compose.material3.Icon
18+
import androidx.compose.material3.MaterialShapes
1419
import androidx.compose.material3.MaterialTheme.colorScheme
20+
import androidx.compose.material3.toShape
1521
import androidx.compose.runtime.Composable
1622
import androidx.compose.runtime.getValue
1723
import androidx.compose.runtime.setValue
1824
import androidx.compose.ui.Alignment
1925
import androidx.compose.ui.Modifier
2026
import androidx.compose.ui.draw.clip
27+
import androidx.compose.ui.graphics.Shape
2128
import androidx.compose.ui.layout.ContentScale
2229
import androidx.compose.ui.layout.Layout
30+
import androidx.compose.ui.platform.LocalContext
2331
import androidx.compose.ui.res.painterResource
24-
import androidx.compose.ui.res.stringArrayResource
2532
import androidx.compose.ui.res.stringResource
2633
import androidx.compose.ui.tooling.preview.Preview
2734
import androidx.compose.ui.unit.Constraints
2835
import androidx.compose.ui.unit.dp
2936
import androidx.core.os.bundleOf
37+
import androidx.graphics.shapes.CornerRounding
38+
import androidx.graphics.shapes.RoundedPolygon
39+
import androidx.graphics.shapes.rectangle
40+
import androidx.graphics.shapes.star
3041
import com.dede.android_eggs.R
3142
import com.dede.android_eggs.alterable_adaptive_icon.PathShape
3243
import com.dede.android_eggs.ui.composes.icons.rounded.Shapes
3344
import com.dede.android_eggs.util.LocalEvent
3445
import com.dede.android_eggs.views.settings.compose.basic.ExpandOptionsPref
3546
import com.dede.android_eggs.views.settings.compose.basic.SettingPrefUtil
3647
import com.dede.android_eggs.views.settings.compose.basic.rememberPrefIntState
48+
import sv.lib.squircleshape.SquircleShape
3749
import com.dede.android_eggs.resources.R as StringsR
38-
import com.dede.android_eggs.settings.R as SettingsR
3950

4051
private const val SPAN_COUNT = 5
4152

@@ -47,45 +58,109 @@ fun IconShapePref() {
4758
leadingIcon = Icons.Rounded.Shapes,
4859
title = stringResource(StringsR.string.pref_title_icon_shape_override),
4960
) {
50-
IconShapeGroup(selectedIndex) { index, path ->
51-
selectedIndex = index
52-
val extras = bundleOf(SettingPrefUtil.EXTRA_VALUE to path)
53-
with(LocalEvent.poster()) {
54-
post(IconShapePrefUtil.ACTION_CHANGED, extras)
55-
post(SettingPrefUtil.ACTION_CLOSE_SETTING)
56-
}
57-
}
58-
}
59-
}
60-
61-
@Preview
62-
@Composable
63-
private fun IconShapeGroup(
64-
selectedIndex: Int = 0,
65-
onShapeClick: ((index: Int, path: String) -> Unit)? = null
66-
) {
67-
val items = stringArrayResource(SettingsR.array.icon_shape_override_paths)
68-
Layout(
69-
content = {
70-
items.forEachIndexed { index, path ->
61+
IconShapeGridLayout {
62+
polygonItems.forEachIndexed { index, roundedPolygon ->
7163
Box(modifier = Modifier.padding(4.dp)) {
7264
ShapeItem(
7365
isChecked = index == selectedIndex,
74-
path = path,
66+
polygon = roundedPolygon.toShapePlusNullable(),
7567
onClick = onClick@{
7668
if (selectedIndex == index) return@onClick
77-
onShapeClick?.invoke(index, path)
69+
selectedIndex = index
70+
val extras = bundleOf(SettingPrefUtil.EXTRA_VALUE to index)
71+
with(LocalEvent.poster()) {
72+
post(IconShapePrefUtil.ACTION_CHANGED, extras)
73+
post(SettingPrefUtil.ACTION_CLOSE_SETTING)
74+
}
7875
}
7976
)
8077
}
8178
}
82-
},
79+
}
80+
}
81+
}
82+
83+
fun getIconShapeRoundedPolygon(context: Context): RoundedPolygon? {
84+
val index = IconShapePrefUtil.getIconShapeIndexOf(context)
85+
return polygonItems.getOrNull(index)
86+
}
87+
88+
@Composable
89+
fun getIconShapePref(): Shape {
90+
val roundedPolygon = getIconShapeRoundedPolygon(LocalContext.current)
91+
return roundedPolygon.toShapePlus()
92+
}
93+
94+
@Composable
95+
fun RoundedPolygon?.toShapePlus(): Shape {
96+
val shape = this.toShapePlusNullable()
97+
if (shape == null) {
98+
val path = IconShapePrefUtil.getSystemIconMaskPath(LocalContext.current)
99+
if (path != null) {
100+
return PathShape(path)
101+
}
102+
return defaultSquare.toShape()
103+
}
104+
return shape
105+
}
106+
107+
@Composable
108+
private fun RoundedPolygon?.toShapePlusNullable(): Shape? {
109+
if (this == null) return null
110+
if (this == _fakeSquircle) return SquircleShape()
111+
return this.toShape()
112+
}
113+
114+
private val defaultSquare = MaterialShapes.Square
115+
116+
@Suppress("ObjectPropertyName")
117+
private val _fakeSquircle = RoundedPolygon.rectangle()
118+
119+
private val polygonItems: Array<RoundedPolygon?> = arrayOf(
120+
null,
121+
defaultSquare,
122+
// Squircle
123+
_fakeSquircle,
124+
MaterialShapes.Circle,
125+
// CornerSE
126+
RoundedPolygon(
127+
vertices = floatArrayOf(1f, 1f, -1f, 1f, -1f, -1f, 1f, -1f),
128+
perVertexRounding = listOf(
129+
CornerRounding(0.4f),
130+
CornerRounding(1f),
131+
CornerRounding(1f),
132+
CornerRounding(1f),
133+
),
134+
).normalized(),
135+
136+
MaterialShapes.Cookie4Sided,
137+
// Scallop
138+
RoundedPolygon.star(
139+
numVerticesPerRadius = 13,
140+
innerRadius = .9f,
141+
rounding = CornerRounding(.2f),
142+
innerRounding = CornerRounding(.3f)
143+
).normalized(),
144+
MaterialShapes.Clover8Leaf,
145+
MaterialShapes.Pill,
146+
RoundedPolygon.star(
147+
numVerticesPerRadius = 10,
148+
innerRadius = .6f,
149+
rounding = CornerRounding(.3f),
150+
innerRounding = CornerRounding(.3f)
151+
).normalized(),
152+
)
153+
154+
@Composable
155+
private fun IconShapeGridLayout(spanCount: Int = SPAN_COUNT, content: @Composable () -> Unit) {
156+
Layout(
157+
content = content,
83158
measurePolicy = { measurables, constraints ->
84-
val childConstraints = Constraints.fixedWidth(constraints.maxWidth / SPAN_COUNT)
159+
val childConstraints = Constraints.fixedWidth(constraints.maxWidth / spanCount)
85160
var height = 0
86161
val placeables = measurables.mapIndexed { index, measurable ->
87162
measurable.measure(childConstraints).also { placeable ->
88-
if (index % SPAN_COUNT == 0) {
163+
if (index % spanCount == 0) {
89164
height += placeable.height
90165
}
91166
}
@@ -94,25 +169,24 @@ private fun IconShapeGroup(
94169
var x: Int
95170
var y: Int
96171
placeables.forEachIndexed { index, placeable ->
97-
x = index % SPAN_COUNT * placeable.width
98-
y = index / SPAN_COUNT * placeable.height
172+
x = index % spanCount * placeable.width
173+
y = index / spanCount * placeable.height
99174
placeable.placeRelative(x, y)
100175
}
101176
}
102177
}
103178
)
104179
}
105180

106-
@Preview(widthDp = 56)
107181
@Composable
108182
private fun ShapeItem(
109183
modifier: Modifier = Modifier,
110184
isChecked: Boolean = false,
111-
path: String = stringResource(id = SettingsR.string.icon_shape_clover_path),
185+
polygon: Shape? = MaterialShapes.Circle.toShape(),
112186
onClick: () -> Unit = {}
113187
) {
114188
Card(
115-
shape = PathShape(stringResource(SettingsR.string.icon_shape_clover_path)),
189+
shape = MaterialShapes.Cookie4Sided.toShape(),
116190
onClick = onClick,
117191
modifier = modifier then Modifier.aspectRatio(1f),
118192
colors = CardDefaults.cardColors(containerColor = colorScheme.surface)
@@ -121,7 +195,7 @@ private fun ShapeItem(
121195
contentAlignment = Alignment.Center,
122196
modifier = Modifier.fillMaxSize()
123197
) {
124-
if (path.isEmpty()) {
198+
if (polygon == null) {
125199
Image(
126200
painter = painterResource(R.drawable.ic_android_classic),
127201
contentDescription = null,
@@ -132,7 +206,7 @@ private fun ShapeItem(
132206
} else {
133207
Box(
134208
modifier = Modifier
135-
.clip(PathShape(path))
209+
.clip(polygon)
136210
.background(colorScheme.primary)
137211
.fillMaxSize(0.56f)
138212
)

app/src/main/java/com/dede/android_eggs/views/timeline/TimelineListDialog.kt

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,9 @@ import androidx.constraintlayout.compose.Dimension
5252
import androidx.hilt.navigation.compose.hiltViewModel
5353
import androidx.lifecycle.ViewModel
5454
import com.dede.android_eggs.R
55-
import com.dede.android_eggs.alterable_adaptive_icon.PathShape
5655
import com.dede.android_eggs.views.main.compose.EasterEggLogo
5756
import com.dede.android_eggs.views.main.util.AndroidLogoMatcher
58-
import com.dede.android_eggs.views.settings.compose.prefs.IconShapePrefUtil
57+
import com.dede.android_eggs.views.settings.compose.prefs.getIconShapePref
5958
import com.dede.android_eggs.views.timeline.TimelineEventHelp.eventAnnotatedString
6059
import com.dede.android_eggs.views.timeline.TimelineEventHelp.isNewGroup
6160
import com.dede.android_eggs.views.timeline.TimelineEventHelp.localMonth
@@ -220,12 +219,8 @@ private fun TimelineItem(
220219
}
221220
val isAdaptiveIcon = context.isAdaptiveIconDrawable(logoRes)
222221
if (!isAdaptiveIcon) {
223-
val iconShape = IconShapePrefUtil.getMaskPath(context)
224222
imageModifier = Modifier
225-
.background(
226-
colorScheme.secondaryContainer,
227-
PathShape(iconShape)
228-
)
223+
.background(colorScheme.secondaryContainer, getIconShapePref())
229224
.then(imageModifier)
230225
.padding(6.dp)
231226
}

core/alterable-adaptive-icon/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,6 @@ dependencies {
1414
implementation(libs.androidx.compose.ui)
1515
implementation(libs.google.accompanist.drawablepainter)
1616
implementation(libs.androidx.compose.ui.tooling.preview)
17+
implementation(libs.androidx.compose.material3)
1718
debugImplementation(libs.androidx.compose.ui.tooling)
1819
}

0 commit comments

Comments
 (0)