simpmusic-icons
GitHubSimpMusic图标管理规范,指导如何添加或修改Material Symbols ImageVector图标,避免依赖XML或外部库,提供生成、配置及在Compose中使用的具体步骤与陷阱。
Trigger Scenarios
Install
npx skills add maxrave-dev/SimpMusic --skill simpmusic-icons -g -y
SKILL.md
Frontmatter
{
"name": "simpmusic-icons",
"description": "Add or change an icon in SimpMusic. Every icon is a generated Material Symbols ImageVector under ui\/icon, addressed as SimpIcons.<Name> — there is no material-icons dependency and no XML icon drawable. Use when adding a new icon, replacing one, or hitting ImageVector\/Painter type errors."
}
SimpMusic Icons
Icons live in composeApp/src/commonMain/kotlin/com/maxrave/simpmusic/ui/icon/, one file per
icon, each declaring val SimpIcons.<Name>: ImageVector.
Two things are deliberately absent and must not come back:
- the
material-icons-core/material-icons-extendeddependency (Icons.Rounded.*) - XML icon drawables in
composeResources/drawableused throughpainterResource(...)
Adding an icon
Google's font service generates the Compose file directly — no conversion tool needed:
curl -sfL --compressed \
"https://fonts.gstatic.com/render/v1/Material+Symbols+Rounded/24dp/<symbol_name>.kt?var=opsz,wght,FILL,GRAD,ROND@24,400,1,0,50" \
-o <PascalName>.kt
Keep these axes identical for every icon, or the set stops looking like one set:
Rounded, opsz 24, wght 400, GRAD 0, ROND 50, FILL=1.
Use FILL=0 only for the "off" half of a state pair — FavoriteBorder,
AddCircleOutline, DownloadForOfflineOutlined. At FILL=1 the off state renders identically to
the on state and the distinction is lost.
Then edit the downloaded file:
package com.example.test→package com.maxrave.simpmusic.ui.iconpublic val <symbol_name>: ImageVector→val SimpIcons.<PascalName>: ImageVector- Rename the backing field
_<symbol_name>→_<PascalName>, andname = "<symbol_name>"→"<PascalName>" - If the glyph must flip in RTL, add
autoMirror = true,insideImageVector.Builder(...)(currently:ArrowForwardIos,QueueMusic,Sort,TrendingUp,VolumeOff,VolumeUp)
Using an icon
import com.maxrave.simpmusic.ui.icon.PlayArrow // per-icon import, required
import com.maxrave.simpmusic.ui.icon.SimpIcons
Icon(SimpIcons.PlayArrow, contentDescription = null)
The project's own icon buttons take an ImageVector:
RippleIconButton(imageVector = …), LiquidGlassIconButton(imageVector = …),
ActionButton(icon = …).
Traps
Every icon needs its own import. val SimpIcons.X is an extension property; importing the
SimpIcons object alone leaves it unresolved. This is the same mechanism that lets R8 strip unused
icons, so never "simplify" the set into a map or a when — that would ship all of them.
ImageVector is not a Painter. Icon and Image have overloads for both, so those look
fine. These do not, and need rememberVectorPainter(SimpIcons.X):
AsyncImage(placeholder = …, error = …)- anything drawn inside a
DrawScope(with(painter) { draw(...) }) - custom composables whose parameter is typed
Painter
Do not text-replace blindly. A regex swap of Res.drawable.X → SimpIcons.X produced 40
compile errors in one pass: it also hit Painter parameters, and it landed inside multi-line
painterResource(\n Res.drawable.X,\n) calls, leaving the syntactically valid but wrong
painterResource(SimpIcons.X).
Do not replace an icon whose colour carries meaning. These stay as resources:
| File | Why |
|---|---|
baseline_downloaded.xml |
#FF00A0CB — the blue is the "downloaded" state |
baseline_favorite_24.xml |
#D10000 — the red is the "liked" state |
mono.xml, monochrome.xml |
SimpMusic logos (#0AEAF8 / black) |
app_icon.png, circle_app_icon.png, holder*.png |
bitmaps, not icons |
If a state needs that colour with a shared symbol, pass it explicitly — e.g. ActionButton's
iconColor = Color(0xFF00A0CB) for the downloaded state.
Verify the symbol name exists before assuming a mapping, against
google/material-design-icons → variablefont/MaterialSymbolsRounded[FILL,GRAD,opsz,wght].codepoints.
Legacy names such as favorite_border, add_circle_outline and thumb_up_alt do still exist;
person_add_alt_1 does not (use person_add).
The response is gzipped even when the request asks for identity — curl --compressed, or
decompress by magic bytes (\x1f\x8b) when fetching from a script.
Version History
- 973fbc8 Current 2026-08-04 18:59


