P1 Stream B: port iOS brand assets to Android

- outline, tab_view, app_icon_mark, widget_icon: rasterized from iOS
  PDF/PNG sources at 1024px (gradients/shadows would flatten via SVG trace)
- honeycomb_texture: hand-authored VectorDrawable, seamless hex tiling
  verified at 4x4 render
- widget_icon: adaptive icon (mipmap-anydpi-v26) + density-specific
  mipmap + foreground bitmaps (mdpi..xxxhdpi)
- Source PDFs preserved in docs/ios-parity/source-assets/ for future
  re-rasterization

AssetInventoryTest asserts all 5 Res.drawable entries resolve.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Trey T
2026-04-18 12:35:31 -05:00
parent db181c0d6a
commit 7aab8b0f29
22 changed files with 135 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 KiB

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Adaptive icon background for widget_icon.
Matches iOS AppIcon dark navy tone (#0A1929 app background primary dark).
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#0A1929" />
</shape>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/widget_icon_background" />
<foreground android:drawable="@drawable/widget_icon_foreground" />
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

View File

@@ -0,0 +1,68 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
honeycomb_texture
Seamlessly tileable honeycomb pattern, mimicking the faint hex grid in
iOS outline.pdf / tab_view. Meant to be painted as a repeating tile via
Compose (e.g. Brush.tile or Modifier.drawBehind { drawImage(tileMode=Repeated) }).
Geometry — pointy-top hexagons (flat sides left/right, points top/bottom),
side length s = 10.
W = sqrt(3) * s = 17.3205 (hexagon width)
Row vertical step (center-to-center) = 1.5 * s = 15
Two rows = 30u vertical period
Tile size = W x 30 = 17.3205 x 30
Row 0 has hex centers at x = 0, W, 2W, ...
Row 1 (offset) has hex centers at x = W/2, 3W/2, ... (offset by +W/2)
Row 2 aligns with Row 0 again.
In one W x 30 tile we need:
- Row 0 center at (0, 0): partial, 4 corners at tile corners → contributes 1/4 hex each to 4 adjacent tiles.
- Row 0 center at (W, 0): same (tile corners on right edge).
- Row 1 center at (W/2, 15): fully inside tile.
Stroke: amber @ ~20% alpha via android:tint, so it reads as subtle background.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="86dp"
android:height="150dp"
android:viewportWidth="17.3205"
android:viewportHeight="30"
android:tint="#33F5A623">
<!-- Row 1 full hex, center (W/2, 15) = (8.66025, 15), vertices at y=5 & y=25 -->
<path
android:pathData="M 8.66025,5 L 17.3205,10 L 17.3205,20 L 8.66025,25 L 0,20 L 0,10 Z"
android:strokeColor="#FFFFFFFF"
android:strokeWidth="0.4"
android:fillColor="#00000000" />
<!-- Row 0 partial hex, center (0, 0). Only bottom-right quadrant inside tile. -->
<path
android:pathData="M 0,-10 L 8.66025,-5 L 8.66025,5 L 0,10 L -8.66025,5 L -8.66025,-5 Z"
android:strokeColor="#FFFFFFFF"
android:strokeWidth="0.4"
android:fillColor="#00000000" />
<!-- Row 0 partial hex, center (W, 0). Only bottom-left quadrant inside tile. -->
<path
android:pathData="M 17.3205,-10 L 25.98075,-5 L 25.98075,5 L 17.3205,10 L 8.66025,5 L 8.66025,-5 Z"
android:strokeColor="#FFFFFFFF"
android:strokeWidth="0.4"
android:fillColor="#00000000" />
<!-- Row 2 partial hex, center (0, 30). Only top-right quadrant inside tile. -->
<path
android:pathData="M 0,20 L 8.66025,25 L 8.66025,35 L 0,40 L -8.66025,35 L -8.66025,25 Z"
android:strokeColor="#FFFFFFFF"
android:strokeWidth="0.4"
android:fillColor="#00000000" />
<!-- Row 2 partial hex, center (W, 30). Only top-left quadrant inside tile. -->
<path
android:pathData="M 17.3205,20 L 25.98075,25 L 25.98075,35 L 17.3205,40 L 8.66025,35 L 8.66025,25 Z"
android:strokeColor="#FFFFFFFF"
android:strokeWidth="0.4"
android:fillColor="#00000000" />
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 748 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 508 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

View File

@@ -0,0 +1,53 @@
package com.tt.honeyDue.resources
import honeydue.composeapp.generated.resources.Res
import honeydue.composeapp.generated.resources.app_icon_mark
import honeydue.composeapp.generated.resources.honeycomb_texture
import honeydue.composeapp.generated.resources.outline
import honeydue.composeapp.generated.resources.tab_view
import honeydue.composeapp.generated.resources.widget_icon
import org.jetbrains.compose.resources.ExperimentalResourceApi
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
/**
* P1 Stream B — Asset port inventory.
*
* Guards iOS → Android brand-asset parity. Each referenced drawable must
* exist in composeResources so the generated `Res.drawable.<name>` accessor
* compiles. Any removal will fail at compile time, not silently at runtime.
*
* Sources documented in docs/ios-parity/source-assets/ (commit-tracked).
*/
@OptIn(ExperimentalResourceApi::class)
class AssetInventoryTest {
@Test
fun all_ported_assets_exist() {
// Fails to compile if any of these generated symbols don't exist.
val assets = listOf(
Res.drawable.outline,
Res.drawable.tab_view,
Res.drawable.app_icon_mark,
Res.drawable.honeycomb_texture,
Res.drawable.widget_icon
)
assertEquals(5, assets.size, "Expected 5 ported brand assets")
assets.forEach { assertNotNull(it) }
}
@Test
fun assets_are_distinct_resources() {
// Ensures no accidental alias/duplicate definitions.
val assets = setOf(
Res.drawable.outline,
Res.drawable.tab_view,
Res.drawable.app_icon_mark,
Res.drawable.honeycomb_texture,
Res.drawable.widget_icon
)
assertEquals(5, assets.size, "All ported assets must be distinct DrawableResource instances")
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB