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>
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 49 KiB |
|
After Width: | Height: | Size: 111 KiB |
|
After Width: | Height: | Size: 203 KiB |
@@ -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>
|
||||
@@ -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>
|
||||
BIN
composeApp/src/androidMain/res/mipmap-hdpi/widget_icon.png
Normal file
|
After Width: | Height: | Size: 6.3 KiB |
BIN
composeApp/src/androidMain/res/mipmap-mdpi/widget_icon.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
composeApp/src/androidMain/res/mipmap-xhdpi/widget_icon.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
composeApp/src/androidMain/res/mipmap-xxhdpi/widget_icon.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
composeApp/src/androidMain/res/mipmap-xxxhdpi/widget_icon.png
Normal file
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 1.4 MiB |
@@ -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>
|
||||
BIN
composeApp/src/commonMain/composeResources/drawable/outline.png
Normal file
|
After Width: | Height: | Size: 748 KiB |
BIN
composeApp/src/commonMain/composeResources/drawable/tab_view.png
Normal file
|
After Width: | Height: | Size: 508 KiB |
|
After Width: | Height: | Size: 1.4 MiB |
@@ -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")
|
||||
}
|
||||
}
|
||||
BIN
docs/ios-parity/source-assets/app_icon_mark.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
docs/ios-parity/source-assets/outline.pdf
Normal file
BIN
docs/ios-parity/source-assets/tab_view_3x.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
docs/ios-parity/source-assets/widget_icon_source.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |