Agent Skills › maxrave-dev/SimpMusic › simpmusic-icons

simpmusic-icons

GitHub

指导SimpMusic项目中Material Symbols图标的添加、替换及规范使用。涵盖从Google服务生成Kotlin文件、参数配置、代码重构到避免RTL镜像陷阱等全流程,确保图标风格统一并解决ImageVector类型错误。

.claude/skills/simpmusic-icons/SKILL.md maxrave-dev/SimpMusic

Trigger Scenarios

需要向SimpMusic项目添加新图标 替换现有图标或遇到ImageVector/Painter类型错误

Install

npx skills add maxrave-dev/SimpMusic --skill simpmusic-icons -g -y
More Options

Non-standard path

npx skills add https://github.com/maxrave-dev/SimpMusic/tree/dev/.claude/skills/simpmusic-icons -g -y

Use without installing

npx skills use maxrave-dev/SimpMusic@simpmusic-icons

指定 Agent (Claude Code)

npx skills add maxrave-dev/SimpMusic --skill simpmusic-icons -a claude-code -g -y

安装 repo 全部 skill

npx skills add maxrave-dev/SimpMusic --all -g -y

预览 repo 内 skill

npx skills add maxrave-dev/SimpMusic --list

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-extended dependency (Icons.Rounded.*)
  • XML icon drawables in composeResources/drawable used through painterResource(...)

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:

  1. package com.example.test → package com.maxrave.simpmusic.ui.icon
  2. public val <symbol_name>: ImageVector → val SimpIcons.<PascalName>: ImageVector
  3. Rename the backing field _<symbol_name> → _<PascalName>, and name = "<symbol_name>" → "<PascalName>"
  4. Never add autoMirror = true to ImageVector.Builder(...): in right-to-left languages only text flips, icons keep their shape (owner's rule, 2026-10-14). ArrowForwardIos, QueueMusic, Sort, TrendingUp, VolumeOff and VolumeUp had it and drew mirrored until it was removed.
  5. Replace the closing .build() with .buildSimpIcon() (in SimpIcons.kt): it is build() plus the fit into Material's 20×20 live area described under Traps. SimpIconsFitTest fails on a file that still ends with .build().

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).

Glyphs that spill past Material's 20×20 live area are fitted into it (2026-10-18). Material's icon grid keeps a glyph inside the centred 20×20 square of the 24 box, with 2dp of padding, and balances the glyphs inside it by keyline shapes of about equal area. 24 of our 111 Symbols spill into that padding anyway: Lyrics' note and Groups reach the box edge, ThumbsUpDown spans the full width. At one size those read bigger than everything beside them. buildSimpIcon() measures how far each glyph reaches from the centre along either axis (walking its path nodes, 16 samples per curve) and shrinks one that passes 10 of 24 about the centre until it fits: Lyrics, Groups and ThumbsUpDown ×0.83, the other 21 ×0.86–0.996. A glyph inside the square is left exactly as Google drew it, nothing is enlarged, and anything it cannot measure (an arc, a group, a fit tighter than ×0.6) keeps the glyph as generated and logs under SimpIcons. SimpIconsFitTest (jvmTest) fails if a file skips buildSimpIcon() or any icon ends up outside the square.

  • Do not fit glyphs into a circle instead. That was tried first: a circle judges a glyph by its one farthest point, so Lyrics shrank to ×0.72 to pull its note's tip inside and then read far smaller than QueueMusic beside it.

A small round button cuts a glyph that fills the live area. IconButton clips its content to its shape (a circle by default), and a button sized 24dp makes that a 24dp circle, 12dp from the centre. A glyph that fills the 20×20 square reaches 14.1 at its corners — comment reaches 13.3 — so its corners are cut while round or open glyphs beside it are not. The icon file is not the problem; the button is. Check the farthest glyph point before putting an icon in one: Classic's Comments button clips to 28dp with requiredSize(28.dp) inside the same 24dp slot.

Icons in one row take one size. Tuning each glyph by eye is what made the Apple Music row read big-small (23 and 24dp beside 22dp dock buttons, the title-row star 32dp beside 24dp glyphs). Only a deliberate hierarchy, such as a Play button over Previous/Next, gets a different size.

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

  • a81563e Current 2026-10-18 18:38

    修正了关于RTL(从右至左)语言下图标镜像的规则,明确禁止对图标使用autoMirror=true,以符合2026年10月14日的维护者规定。

  • 973fbc8 2026-08-04 18:59

Same Skill Collection

.claude/skills/android-accessibility/SKILL.md
.claude/skills/android-architecture/SKILL.md
.claude/skills/android-coroutines/SKILL.md
.claude/skills/android-data-layer/SKILL.md
.claude/skills/android-emulator-skill/SKILL.md
.claude/skills/android-gradle-logic/SKILL.md
.claude/skills/android-retrofit/SKILL.md
.claude/skills/android-testing/SKILL.md
.claude/skills/android-viewmodel/SKILL.md
.claude/skills/coil-compose/SKILL.md
.claude/skills/compose-navigation/SKILL.md
.claude/skills/compose-performance-audit/SKILL.md
.claude/skills/compose-ui/SKILL.md
.claude/skills/gradle-build-performance/SKILL.md
.claude/skills/jetpack-compose/SKILL.md
.claude/skills/kotlin-concurrency-expert/SKILL.md
.claude/skills/marketing-ideas/SKILL.md
.claude/skills/mobile-android-design/SKILL.md
.claude/skills/simpmusic-release-changelog/SKILL.md
.claude/skills/xml-to-compose-migration/SKILL.md
.claude/skills/find-skills/SKILL.md
.claude/skills/kmp/SKILL.md
.claude/skills/ui-ux-pro-max/SKILL.md

Metadata

Files
0
Version
a81563e
Hash
74c7718a
Indexed
2026-08-04 18:59

홈 - 위키
Copyright © 2011-2026 iteam. Current version is 2.155.2. UTC+08:00, 2026-10-20 13:51
浙ICP备14020137号-1