Skip to content

feat(android): organize block inserter into cards with search#469

Draft
jkmassel wants to merge 1 commit intojkmassel/android-block-pickerfrom
jkmassel/block-picker-organize
Draft

feat(android): organize block inserter into cards with search#469
jkmassel wants to merge 1 commit intojkmassel/android-block-pickerfrom
jkmassel/block-picker-organize

Conversation

@jkmassel
Copy link
Copy Markdown
Contributor

@jkmassel jkmassel commented Apr 23, 2026

Stacks on top of #468. Organizes the native block inserter into iOS-parity per-section cards with a debounced search field and per-card Show More/Less, mirroring the iOS BlockInserterView. All UI is Jetpack Compose, hosted in a ComposeView inside GutenbergView's BottomSheetDialog so the integration surface is unchanged.

Changes

Layout

  • LazyColumn of rounded colorSurfaceContainerHigh section cards, each containing a chunked-Row grid of icon-over-label tiles. Span count is derived from display width with a min of 3 so narrow devices stay readable.
  • Tile = 44dp chip + 12sp caption (maxLines = 2, ellipsize end). Matches the iOS BlockInserterBlockView stack of BlockIconView over Text(title).lineLimit(2, reservesSpace: true).
  • Sections with a null name render headerless, matching iOS (gbk-most-used and gbk-contextual sit at the top with no label).
  • Sections with more than 16 blocks collapse with per-card Show More/Less — same threshold iOS uses.

Search

  • Debounced TextField at the top of the dialog. Ranking is a direct port of ios/Sources/GutenbergKit/Sources/Helpers/SearchEngine.swift (weighted title/name/keywords/description/category, prefix + Levenshtein fallback for short queries), so results order matches iOS.
  • Empty query restores browsable sections; zero-match query shows a No results empty state.

Compose

  • Library gains the org.jetbrains.kotlin.plugin.compose plugin and Compose BOM + ui + material3 deps (already declared in libs.versions.toml).
  • BlockInserterDialog keeps its existing BottomSheetDialog public surface; only the content is Compose.

Icon chip sizing and the contrast-aware tinting from #468 are unchanged.

Test plan

  • ./gradlew :Gutenberg:testDebugUnitTest :Gutenberg:detekt :Gutenberg:assembleDebug — BUILD SUCCESSFUL
  • Manually verified on Pixel 9 Pro XL with enableNativeBlockInserter on, dark theme: sections render, fuzzy search (imge → Image first), Show More/Less toggles in Theme section, swipe-to-dismiss, tile tap inserts block.
  • Reviewer: verify in light theme.
  • Reviewer: verify disabled blocks still render at 0.5 alpha and don't fire click handlers.

Related

@github-actions github-actions Bot added the [Type] Enhancement A suggestion for improvement. label Apr 23, 2026
@jkmassel jkmassel marked this pull request as draft April 23, 2026 20:43
@jkmassel jkmassel marked this pull request as draft April 23, 2026 20:43
@jkmassel jkmassel marked this pull request as draft April 23, 2026 20:43
@jkmassel jkmassel changed the title feat(android): render block inserter as an adaptive grid feat(android): organize block inserter into cards with search Apr 23, 2026
@jkmassel jkmassel force-pushed the jkmassel/block-picker-organize branch from 2287a41 to 20b6773 Compare April 23, 2026 22:04
@jkmassel jkmassel force-pushed the jkmassel/android-svg-icons branch from ae080ac to f19065a Compare April 24, 2026 16:51
Organizes the native block inserter into iOS-parity per-section cards
with a debounced search field and per-card Show More/Less, mirroring
the iOS `BlockInserterView`. All UI is Jetpack Compose, hosted in a
`ComposeView` inside the existing `BottomSheetDialog` so the
integration surface is unchanged.

## Layout

`LazyColumn` of rounded `colorSurfaceContainerHigh` section cards,
each containing a chunked-`Row` grid of icon-over-label tiles. Span
count is derived from display width with a min of 3 so narrow devices
stay readable. Tile = 44dp chip + 12sp caption (`maxLines = 2`,
ellipsize end). Matches the iOS `BlockInserterBlockView` stack of
`BlockIconView` over `Text(title).lineLimit(2, reservesSpace: true)`.

Sections with a null `name` render headerless, matching iOS
(`gbk-most-used` and `gbk-contextual` sit at the top with no label).
Sections with more than 16 blocks collapse with per-card Show More /
Show Less — same threshold iOS uses.

## Search

Debounced `TextField` at the top of the dialog. Ranking is a direct
port of `ios/Sources/GutenbergKit/Sources/Helpers/SearchEngine.swift`
(weighted title/name/keywords/description/category, prefix +
Levenshtein fallback for short queries), so results order matches iOS.
Empty query restores browsable sections; zero-match query shows a
`No results` empty state.

## SVG rendering

Adopts AndroidSVG (`com.caverock:androidsvg-aar:1.4`) — the rendering
engine Coil-SVG wraps, used directly to avoid pulling in Coil. Parses
each block's inline `@wordpress/icons` SVG once per `BlockType.id`,
caches the rendered bitmap so recomposition doesn't re-render. Three
patterns the browser handles via CSS that AndroidSVG does not:

1. **Missing `viewBox`** (e.g. `core/site-tagline`) — synthesise one
   from intrinsic dimensions and set document width/height to 100%.
2. **`fill="none"` at root** (e.g. `core/icon`) — inject `svg { fill:
   currentColor }` via `RenderOptions.css`.
3. **Multi-fill branded icons** (e.g. Pocket Casts, Animoto) — detect
   hex fills in the raw string and skip the tint so internal contrast
   survives.

## Contrast-aware tinting

`SvgIconCache.shouldTint` decides per-icon whether to apply the theme
tint:

1. **No declared colours** — pure monochrome; tint to text colour.
2. **Multiple declared colours** — render as-is. A PorterDuff SRC_IN
   tint would flatten internal contrast into a silhouette.
3. **Single declared colour** — keep it if it has at least 3:1
   contrast (WCAG 2.x SC 1.4.11 — minimum for UI graphics) against
   the chip fill composited over the surface; otherwise strip it and
   tint.

Measuring against bare black instead of the composited surface makes
marginal colours like WordPress blue (#0073AA) appear to pass at
3.16:1 while reading as dim against the actual ~`#3B3B3D` chip
surrogate (2.1:1).

## Bridge

`getBlockIcon` at `src/utils/blocks.js:44` previously dropped
`icon.foreground` — the JS metadata the web editor applies as CSS
`color`, which paths inside the SVG pick up via `currentColor`. Adds
`getBlockIconForeground` to the serialised inserter payload, with
matching `iconForeground: String?` fields on the Android `BlockType`
data class and the iOS `BlockType` struct (iOS gets the field for
parity; no rendering change).

## Test plan

- [x] `./gradlew :Gutenberg:testDebugUnitTest :Gutenberg:detekt :Gutenberg:assembleDebug` — BUILD SUCCESSFUL
- [x] Manually verified on Pixel 9 Pro XL with `enableNativeBlockInserter` on, dark theme: sections render, fuzzy search (`imge` → Image first), Show More/Less toggles in Theme section, swipe-to-dismiss, tile tap inserts block.
- [ ] Reviewer: verify in light theme.
- [ ] Reviewer: verify disabled blocks still render at 0.5 alpha and don't fire click handlers.
@jkmassel jkmassel force-pushed the jkmassel/block-picker-organize branch from 20b6773 to f1afeab Compare April 24, 2026 17:57
@jkmassel jkmassel changed the base branch from jkmassel/android-svg-icons to jkmassel/android-block-picker April 24, 2026 17:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Type] Enhancement A suggestion for improvement.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant