diff --git a/SPECS.md b/SPECS.md index 02163ee..e330b4b 100644 --- a/SPECS.md +++ b/SPECS.md @@ -140,6 +140,7 @@ tyre pressures, GPS (lat, lon, elevation, heading) and the is_* flags. It does * confirmed car facts, app structure, UI conventions, build/test/ship, documents. It replaces upstream's EVABRPUploader text. The test emulator is now in the flake: `nix run .#car-emulator` (Android 9 AOSP at 1920×720, 160 dpi; the car build installs as the system user — checked). +- 2026-11-07 — App 2.49.1: **the Caterpillar looks more like the one in the book** (owner: "Make it more like the original caterpillar", with the German edition's cover as the reference). It arches over the middle like the cover's: overlapping chunks of mottled green and teal paper, a yellow fringe along the back, olive feet, and a red-orange head with a yellow face, green eyes and purple antennae; the speed is on the face. Power still lights the segments (blue when regenerating) and the back humps while driving. Still my own drawing of it, not the book's artwork. Two screenshots redone. Tested: unit tests, lint, emulator with sample values. Not tried on the car. - 2026-11-07 — App 2.49.0 published (`car-2.49.0`). - 2026-11-07 — App 2.49.0: **the Caterpillar style** (owner: "a theme based on The Very Hungry Caterpillar"; design agreed first, defaults taken: dark night page, named "Caterpillar" not the book's title, fruit as range). `CaterpillarView`, an original cut-paper collage, not the book's illustrations: the caterpillar's red head is the speed and its ten segments the power (blue while regenerating, wiggling while driving); the sun is the battery (a grey moon below 20 %); a leaf holds trip, usage, climate and odometer; a clock in a paper circle; fruit in the story's order (1 apple, 2 pears, 3 plums, 4 strawberries, 5 oranges) is the range, one per 50 km, used ones with a bite out; P R N D are an egg, the caterpillar, a cocoon and a butterfly; hills are the last two minutes of power. The music light (now also for this style) bounces the sun's rays and the segments on the kick drum. What plays: a butterfly and, on the next pass, a ladybird tow a strip of tissue paper (colour fixed by the song); both in `SongCarriers`. Own palette (green). Two screenshots in THEMES.md. Tested: unit tests, lint, emulator with sample values and with the throwaway fake song. Not tried on the car. - 2026-11-07 — App 2.48.0 published (`car-2.48.0`). diff --git a/THEMES.md b/THEMES.md index 469d945..4b3ffe6 100644 --- a/THEMES.md +++ b/THEMES.md @@ -307,9 +307,11 @@ trumpet; the paper's colour is fixed by the song. The app turns red with it. ## Caterpillar After a picture book about a very hungry caterpillar, drawn anew in cut-paper shapes and bright -tissue colours on the book's night page. The caterpillar crosses the middle: its red head carries -the speed, and its ten green segments are the power, lit from the head backwards while drawing and -blue while regenerating; it wiggles along while you drive. On the left the sun is the battery, +tissue colours on the book's night page. The caterpillar arches over the middle, after the one on +the book's cover: overlapping chunks of mottled green paper with a yellow fringe along the back and +olive feet, and a red-orange head with a yellow face, green eyes and purple antennae. The face +carries the speed, and its ten segments are the power, lit from the head backwards while drawing and +blue while regenerating; its back humps up and down while you drive. On the left the sun is the battery, filling with the charge; below 20 % it turns into a grey moon and "BATTERY LOW" blinks. A big leaf above it holds trip, usage, climate and odometer on paper labels. On the right a clock in a paper circle, and the story's fruit, one apple, two pears, three plums, four strawberries and five diff --git a/app/build.gradle b/app/build.gradle index 1b6e9d9..8c0a7ed 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -34,8 +34,8 @@ android { applicationId "com.stinnesbeck.nilsmg4" minSdk 27 targetSdk 36 - versionCode 166 - versionName "2.49.0" + versionCode 167 + versionName "2.49.1" // Nothing about the server ships in the APK: its URL and ingest token are entered in // the app's Server tab (ServerConfig), and the database is only reachable by the server. diff --git a/app/src/main/java/com/evsuite/abrp/CaterpillarView.java b/app/src/main/java/com/evsuite/abrp/CaterpillarView.java index 5f7a90a..f2478da 100644 --- a/app/src/main/java/com/evsuite/abrp/CaterpillarView.java +++ b/app/src/main/java/com/evsuite/abrp/CaterpillarView.java @@ -17,9 +17,10 @@ import java.util.Locale; * The Caterpillar Overview style, after the picture book about a very hungry caterpillar (drawn * anew, not copied from it): cut-paper shapes in bright tissue colours on the book's night page. * - * In the middle the caterpillar: its red head carries the speed, and its ten green segments are the - * power, lit from the head backwards while drawing, blue while regenerating; it wiggles along while - * the car moves. On the left the sun is the battery, filling with the charge, and turning into a + * In the middle the caterpillar, arched like the one on the book's cover (overlapping chunks of + * mottled green tissue paper, a yellow fringe along the back, a red-orange head with a yellow face): + * the face carries the speed, and its ten segments are the power, lit from the head backwards while + * drawing, blue while regenerating; its back humps up and down while the car moves. On the left the sun is the battery, filling with the charge, and turning into a * grey moon with "BATTERY LOW" blinking below 20 %; above it a big leaf holds trip, usage, climate * and odometer on paper labels. On the right the clock in a paper circle, and the * story's fruit: one apple, two pears, three plums, four strawberries and five oranges, a fruit @@ -442,54 +443,90 @@ final class CaterpillarView extends ClusterCanvasView { // ---------- The caterpillar: speed and power ---------- + /** Tissue-paper greens for a segment: base colours, a light patch and a teal patch, by state. */ + private static final int[][] DRAWING = { { 0xFF4CC15C, 0xFF38A94A, 0xFF66CC6A }, { 0xFF9BE08A, 0xFF2FA89A } }; + private static final int[][] REGEN = { { 0xFF2A8FC9, 0xFF1F78B4, 0xFF4AA8DA }, { 0xFF9BDCFF, 0xFF3AC8D0 } }; + private static final int[][] OFF = { { 0xFF2B7A40, 0xFF256B38, 0xFF328C4B }, { 0xFF3A9060, 0xFF2A8078 } }; + + /** + * The caterpillar arches over the middle like the one on the book's cover: overlapping chunks of + * mottled tissue paper in greens and teals, a fringe of yellow hair along its back, olive feet, + * and a red-orange head with a yellow face, green eyes and purple antennae. Power lights the + * segments (bright green, or blue when regenerating, dull when dark); the speed is on its face. + */ private void drawCaterpillar(Canvas c, int w, float u, float pulse) { - float hx = w / 2f + 250f * u, hy = 400f * u, hr = 76f * u; + float hx = w / 2f + 250f * u, hy = 410f * u, hr = 76f * u; int lit = Tally.ofPower(v.powerKw, 150f, 50f, SEGMENTS); - int colour = regen() ? ICE : GREEN; - // The segments from the tail towards the head, the legs behind them. + int[][] palette = regen() ? REGEN : DRAWING; + // The arch: highest in the middle, and it humps up and down while the car moves. + float arch = 92f * u + (float) Math.sin(time * 3.5f) * 16f * u; + float[] xs = new float[SEGMENTS], ys = new float[SEGMENTS]; + for (int i = 0; i < SEGMENTS; i++) { + xs[i] = hx - 92f * u - i * 48f * u; + ys[i] = hy + 24f * u - arch * (float) Math.sin(Math.PI * (i + 0.7) / (SEGMENTS + 0.4)); + } + // From the tail towards the head, each overlapping the one before. for (int i = SEGMENTS - 1; i >= 0; i--) { - float x = hx - 100f * u - i * 54f * u; - float y = hy + 10f * u + (float) Math.sin(time * 3.5f - i * 0.75f) * 12f * u; boolean on = i < lit; - float rad = 32f * u * (1f + (on ? pulse * (i % 2 == 0 ? 0.18f : 0.12f) : 0f)); - line.setColor(HEAD); - line.setStrokeWidth(4f * u); - float foot = (i % 2 == 0 ? 6f : -6f) * u; - c.drawLine(x, y + rad - 4f * u, x + foot, y + rad + 14f * u, line); - fill.setColor(on ? colour : 0xFF3B7A4A); - c.drawCircle(x, y, rad, fill); - line.setColor(on ? GREEN_DARK : 0xFF2A5A36); - line.setStrokeWidth(3f * u); - c.drawCircle(x, y, rad, line); - if (on) { - fill.setColor(0x44FFFFFF); - c.drawCircle(x - rad * 0.3f, y - rad * 0.35f, rad * 0.28f, fill); + int[][] pal = on ? palette : OFF; + float x = xs[i], y = ys[i]; + int prev = Math.max(0, i - 1), next = Math.min(SEGMENTS - 1, i + 1); + float angle = (float) Math.toDegrees(Math.atan2(ys[prev] - ys[next], xs[prev] - xs[next])); + float grow = 1f + (on ? pulse * (i % 2 == 0 ? 0.16f : 0.1f) : 0f); + c.save(); + c.rotate(angle, x, y); + c.scale(grow, grow, x, y); + // The foot, under every other segment. + if (i % 2 == 0) { + line.setColor(0xFF8A7B2E); + line.setStrokeWidth(8f * u); + c.drawLine(x, y + 46f * u, x + 3f * u, y + 60f * u, line); } + // The chunk of paper with two patches of other tissue on it. + r.set(x - 37f * u, y - 48f * u, x + 37f * u, y + 48f * u); + fill.setColor(pal[0][i % 3]); + c.drawOval(r, fill); + fill.setColor(AccentColor.withAlpha(pal[1][0], 0x80)); + r.set(x - 28f * u, y - (i % 2 == 0 ? 38f : 10f) * u, x + 4f * u, y + (i % 2 == 0 ? -4f : 36f) * u); + c.drawOval(r, fill); + fill.setColor(AccentColor.withAlpha(pal[1][1], 0x70)); + r.set(x - 2f * u, y - (i % 2 == 0 ? 8f : 40f) * u, x + 30f * u, y + (i % 2 == 0 ? 38f : 0f) * u); + c.drawOval(r, fill); + // The fringe of hair along the back. + line.setColor(on ? 0xFFD9C84A : 0x80D9C84A); + line.setStrokeWidth(2f * u); + for (int k = -2; k <= 2; k++) { + c.drawLine(x + k * 12f * u, y - (46f - Math.abs(k) * 4f) * u, x + k * 15f * u, y - (57f - Math.abs(k) * 4f) * u, line); + } + c.restore(); } - // The head, with antennae and eyes, and the speed. - line.setColor(HEAD); - line.setStrokeWidth(4f * u); - c.drawLine(hx - 22f * u, hy - hr + 8f * u, hx - 40f * u, hy - hr - 30f * u, line); - c.drawLine(hx + 22f * u, hy - hr + 8f * u, hx + 40f * u, hy - hr - 30f * u, line); - fill.setColor(0xFFFFA726); - c.drawCircle(hx - 40f * u, hy - hr - 32f * u, 7f * u, fill); - c.drawCircle(hx + 40f * u, hy - hr - 32f * u, 7f * u, fill); - fill.setColor(HEAD); - c.drawCircle(hx, hy, hr * (1f + pulse * 0.04f), fill); - fill.setColor(0x33FFFFFF); - c.drawCircle(hx - hr * 0.35f, hy - hr * 0.4f, hr * 0.2f, fill); + // The head: purple antennae, a red-orange head with a yellow face, green eyes, the speed. + line.setColor(0xFF7B68C8); + line.setStrokeWidth(8f * u); + c.drawLine(hx - 24f * u, hy - hr + 10f * u, hx - 46f * u, hy - hr - 40f * u, line); + c.drawLine(hx + 24f * u, hy - hr + 10f * u, hx + 46f * u, hy - hr - 40f * u, line); + float hs = 1f + pulse * 0.04f; + fill.setColor(0xFFE8452B); + c.drawCircle(hx, hy, hr * hs, fill); + r.set(hx - 52f * u, hy - 64f * u, hx + 52f * u, hy + 62f * u); + fill.setColor(0xFFFFC21F); + c.drawOval(r, fill); for (int side = -1; side <= 1; side += 2) { - fill.setColor(0xFFFFFFFF); - c.drawCircle(hx + side * 24f * u, hy - 34f * u, 14f * u, fill); + r.set(hx + side * 26f * u - 11f * u, hy - 50f * u, hx + side * 26f * u + 11f * u, hy - 20f * u); + fill.setColor(0xFF3F9A3A); + c.drawOval(r, fill); + line.setColor(INK); + line.setStrokeWidth(2f * u); + c.drawOval(r, line); fill.setColor(INK); - c.drawCircle(hx + side * 24f * u + 3f * u, hy - 33f * u, 6f * u, fill); + c.drawCircle(hx + side * 26f * u, hy - 38f * u, 3.5f * u, fill); } - label(c, fmt(v.speedKmh, "%.0f", "—"), hx, hy + 38f * u, 58f * u, 0xFFFFFFFF, serif, Paint.Align.CENTER); - label(c, "KM/H", hx, hy + 58f * u, 14f * u, 0xCCFFE9EA, face, Paint.Align.CENTER); + label(c, fmt(v.speedKmh, "%.0f", "—"), hx, hy + 30f * u, 46f * u, INK, serif, Paint.Align.CENTER); + label(c, "KM/H", hx, hy + 48f * u, 13f * u, 0xFF7A3A10, face, Paint.Align.CENTER); boolean regen = regen(); String power = v.powerKw == null ? "— kW" : String.format(Locale.US, regen ? "+%.1f kW" : "%.1f kW", Math.abs(v.powerKw)); - outlined(c, power, hx - 100f * u - 4.5f * 54f * u, 556f * u, 34f * u, regen ? ICE : BONE, face, Paint.Align.CENTER, u); + outlined(c, power, hx - 92f * u - 4.5f * 48f * u, 556f * u, 34f * u, regen ? ICE : BONE, face, Paint.Align.CENTER, u); } // ---------- What plays: a butterfly or a ladybird tows it on tissue paper ---------- diff --git a/screenshots/themes/caterpillar-panel.png b/screenshots/themes/caterpillar-panel.png index dafce43..f9cf9c6 100644 Binary files a/screenshots/themes/caterpillar-panel.png and b/screenshots/themes/caterpillar-panel.png differ diff --git a/screenshots/themes/caterpillar.png b/screenshots/themes/caterpillar.png index cb7fc2d..da24081 100644 Binary files a/screenshots/themes/caterpillar.png and b/screenshots/themes/caterpillar.png differ