App 2.49.0: the Caterpillar style

A cut-paper collage after a picture book about a hungry caterpillar:
the caterpillar is the speed and power, the sun the battery, the story's
fruit the range. The music light bounces the sun and the segments. A
butterfly or a ladybird tows the song on tissue paper.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LhzAVaUyqcw5PkGjz4mWW6
This commit is contained in:
2026-11-07 10:07:02 +01:00
co-authored by Claude Sonnet 5.5
parent 5cbd6699aa
commit bf6038fd37
15 changed files with 655 additions and 17 deletions
+1 -1
View File
@@ -77,7 +77,7 @@ Repository: `ssh://git@git.stinnesbeck.com:2222/nils/mg4.git` (remote `origin`),
| Tab | Contents |
|---|---|
| Overview | value tiles (battery, range, range/usage/energy since start, 5-min averages of range, usage, power, speed, and under the battery what plays: `NowPlaying` picks from the media sessions `NowPlayingWatcher` follows — listened to only, never connected to, nothing logged or sent; Tiles, Terminal, Sci-fi, Quest, Empire, Día de Muertos, Halloween and Christmas, Settings → "Show what's playing", `PREF_NOW_PLAYING`, on by default), vehicle info (odometer, lease allowance and the odometer against it), tyre pressures; read every 0.2 s on a background thread. Styles (Settings → Overview style): Tiles, Gauges (`GaugeView` needles), Cockpit (`RingView` rings, `TraceView` power trace), Retro (`RetroView`, seven-segment VFD), Futuristic (`FlowView`, animated energy flow), Sci-fi (`SciFiView`, starship console), Cartoon (`CartoonView`), MG (`OctagonView`, octagonal dials after the classic MG dashboards), and on `ClusterCanvasView`: Minimal, Terminal, Nixie tubes, Chronograph, Grid (`GridView`, neon lines over a moving grid floor, power as a light-cycle trail from `ClusterValues.powerTrail`), Grid with map (the same `GridView` over the roads round the car, top-down: GPS while it is shown, tiles from the server's `/v1/map/roads` through `MapRoads`, cached in `files/map`; `RoadTile`, `MapGeometry`; the map is `MapLayerView`, a hardware layer drawn once and moved and turned by the GPU every display frame), Time circuits (`TimeCircuitsView`, the time machine's LED rows with `Segments14` letters and a flux capacitor), Quest (`QuestView`, a fantasy game interface), Visor (`VisorView`, a helmet display with a motion tracker of the power trail), Empire (`EmpireView`, a strategy game with an isometric world and ages by odometer, `EmpireAge`), Día de Muertos (`MuertosView`, an ofrenda: papel picado, candles, a calavera in a ring of marigolds), Halloween (`HalloweenView`, a jack-o'-lantern, a potion flask, a tombstone on a graveyard hill), Christmas (`ChristmasView`, a tree with a bauble, fairy lights, a mug of cocoa, a wreath clock and candy canes in the snow), all counting with `Tally`, the pumpkin's light and Christmas's tree lights flaring on the kick drum while on screen, when "Light follows the music" is on (off by default, `PREF_MUSIC_LIGHT`; `MusicTap`, `MusicPulse`: Android's audio analyser, 40–120 Hz, jumps not levels; Bluetooth and CarPlay, not the radio); the drawn styles share `ClusterValues`. A style shown in its own palette (Sci-fi, Cartoon, Nixie, Time circuits, Quest, Empire, Día de Muertos, Halloween, Christmas; Retro, Terminal, Grid and Visor with "Original") gives the whole app its colours (`StylePalette`, `res/values/style_palettes.xml`) instead of the accent colour. A button in the navigation's header hides the navigation for a full-width Overview (`PREF_OVERVIEW_FULLSCREEN`); a swipe in from the left edge (`EdgeSwipe`) brings it back; without the app's own full screen the car's sidebar takes that edge, so a tab at the left edge (`edge_tab`) is tapped or swiped instead. Pages change only by the navigation (the pager's own swiping is off) |
| Overview | value tiles (battery, range, range/usage/energy since start, 5-min averages of range, usage, power, speed, and under the battery what plays: `NowPlaying` picks from the media sessions `NowPlayingWatcher` follows — listened to only, never connected to, nothing logged or sent; Tiles, Terminal, Sci-fi, Quest, Empire, Día de Muertos, Halloween, Christmas and Caterpillar, Settings → "Show what's playing", `PREF_NOW_PLAYING`, on by default), vehicle info (odometer, lease allowance and the odometer against it), tyre pressures; read every 0.2 s on a background thread. Styles (Settings → Overview style): Tiles, Gauges (`GaugeView` needles), Cockpit (`RingView` rings, `TraceView` power trace), Retro (`RetroView`, seven-segment VFD), Futuristic (`FlowView`, animated energy flow), Sci-fi (`SciFiView`, starship console), Cartoon (`CartoonView`), MG (`OctagonView`, octagonal dials after the classic MG dashboards), and on `ClusterCanvasView`: Minimal, Terminal, Nixie tubes, Chronograph, Grid (`GridView`, neon lines over a moving grid floor, power as a light-cycle trail from `ClusterValues.powerTrail`), Grid with map (the same `GridView` over the roads round the car, top-down: GPS while it is shown, tiles from the server's `/v1/map/roads` through `MapRoads`, cached in `files/map`; `RoadTile`, `MapGeometry`; the map is `MapLayerView`, a hardware layer drawn once and moved and turned by the GPU every display frame), Time circuits (`TimeCircuitsView`, the time machine's LED rows with `Segments14` letters and a flux capacitor), Quest (`QuestView`, a fantasy game interface), Visor (`VisorView`, a helmet display with a motion tracker of the power trail), Empire (`EmpireView`, a strategy game with an isometric world and ages by odometer, `EmpireAge`), Día de Muertos (`MuertosView`, an ofrenda: papel picado, candles, a calavera in a ring of marigolds), Halloween (`HalloweenView`, a jack-o'-lantern, a potion flask, a tombstone on a graveyard hill), Christmas (`ChristmasView`, a tree with a bauble, fairy lights, a mug of cocoa, a wreath clock and candy canes in the snow), Caterpillar (`CaterpillarView`, cut-paper collage after a picture book: the caterpillar is speed and power, the sun the battery, fruit the range), all counting with `Tally`, the pumpkin's light, Christmas's tree lights and the Caterpillar's sun flaring on the kick drum while on screen, when "Light follows the music" is on (off by default, `PREF_MUSIC_LIGHT`; `MusicTap`, `MusicPulse`: Android's audio analyser, 40–120 Hz, jumps not levels; Bluetooth and CarPlay, not the radio); the drawn styles share `ClusterValues`. A style shown in its own palette (Sci-fi, Cartoon, Nixie, Time circuits, Quest, Empire, Día de Muertos, Halloween, Christmas, Caterpillar; Retro, Terminal, Grid and Visor with "Original") gives the whole app its colours (`StylePalette`, `res/values/style_palettes.xml`) instead of the accent colour. A button in the navigation's header hides the navigation for a full-width Overview (`PREF_OVERVIEW_FULLSCREEN`); a swipe in from the left edge (`EdgeSwipe`) brings it back; without the app's own full screen the car's sidebar takes that edge, so a tab at the left edge (`edge_tab`) is tapped or swiped instead. Pages change only by the navigation (the pager's own swiping is off) |
| Consumption / Charging | period totals, daily log, charging sessions and curve |
| Live data | on/off, ABRP API key and user token, upload frequency |
| Server | URL, pairing by QR, ingest token, installation ID (read-only), config import |
+1
View File
@@ -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.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`).
- 2026-11-07 — App 2.48.0: **the Christmas style** (owner: "let's also do a christmas based theme", with music and something that pulsates; design agreed first). `ChristmasView`: a tree with a big red bauble (speed), fairy lights arching over it (power: gold left to right while drawing, ice blue and right to left while regenerating), a glass mug of cocoa filling with the charge (cold blue and "BATTERY LOW" below 20 %), gift tags (trip, usage, climate, odometer), a wreath clock, a signboard (range) with a candy cane per 50 km, P R N D as baubles, and a snow drift of the last two minutes of power; snow falls and the lights twinkle while driving. Own palette (red); always in the drop-down; English only. The music light is now **"Light follows the music"** for Halloween and Christmas (same `PREF_MUSIC_LIGHT`, off by default): the tree's lights and the arch flare on the kick drum. What plays: Santa's sleigh with a reindeer and, on the next pass, an angel with a trumpet tow a strip of wrapping paper (colour fixed by the song); both in `SongCarriers`. Two screenshots in THEMES.md. Tested: unit tests, lint, emulator with sample values (panel and full screen, and with no data), with the throwaway fake song (both carriers), and the music-tap thread runs only with the switch on. Not tried on the emulator: the pulse itself with real sound (the code path is Halloween's `MusicTap`). Not tried on the car.
- 2026-11-02 — App 2.47.0 published (`car-2.47.0`).
+23 -1
View File
@@ -11,7 +11,7 @@ emulator has no car; on the car the values are live.
**Colours.** Most styles are drawn in the accent colour you pick from the MG4 paints (Settings →
Accent colour; shown here in Dynamic Red). Retro, Terminal, Grid (with or without the map) and Visor can also show their original
colours, and Sci-fi, Cartoon, Nixie tubes, Time circuits, Quest, Empire, Día de Muertos, Halloween and Christmas always do; while one of those is shown, the whole app
colours, and Sci-fi, Cartoon, Nixie tubes, Time circuits, Quest, Empire, Día de Muertos, Halloween, Christmas and Caterpillar always do; while one of those is shown, the whole app
(navigation, buttons, switches) takes on its colours too.
| Style | Look | Colours |
@@ -37,6 +37,7 @@ colours, and Sci-fi, Cartoon, Nixie tubes, Time circuits, Quest, Empire, Día de
| [Día de Muertos](#día-de-muertos) | an ofrenda with papel picado and a calavera | own |
| [Halloween](#halloween) | a jack-o'-lantern on a graveyard hill | own |
| [Christmas](#christmas) | a Christmas tree in a snowy village | own |
| [Caterpillar](#caterpillar) | a hungry caterpillar in cut-paper collage | own |
## Tiles
@@ -302,3 +303,24 @@ trumpet; the paper's colour is fixed by the song. The app turns red with it.
![Christmas with the navigation](screenshots/themes/christmas-panel.png)
![Christmas, full screen](screenshots/themes/christmas.png)
## 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,
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
oranges: a fruit per 50 km of range, those used up with a bite taken out. P R N D are the stages of
its life, an egg, the caterpillar, a cocoon and a butterfly, the one engaged lit. The hills along
the bottom are the last two minutes of power, rising with it. With "Light follows the music" on
(Settings, shown with this style, Halloween and Christmas; off by default) and Bluetooth or CarPlay
music playing, the sun's rays and the caterpillar's segments bounce on each kick drum; the radio
cannot be heard. What plays (Bluetooth, CarPlay or the radio) crosses the sky on a waving strip of
tissue paper towed by a butterfly and, on the next pass, a ladybird; the paper's colour is fixed by
the song. The app turns green with it.
![Caterpillar with the navigation](screenshots/themes/caterpillar-panel.png)
![Caterpillar, full screen](screenshots/themes/caterpillar.png)
+2 -2
View File
@@ -34,8 +34,8 @@ android {
applicationId "com.stinnesbeck.nilsmg4"
minSdk 27
targetSdk 36
versionCode 165
versionName "2.48.0"
versionCode 166
versionName "2.49.0"
// 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.
@@ -0,0 +1,528 @@
package com.evsuite.abrp;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.Typeface;
import android.os.SystemClock;
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
* 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
* per 50 km of range, those used up with a bite taken out. P R N D are the four stages of its life,
* an egg, the caterpillar, a cocoon and a butterfly, the one engaged lit. The hills along the bottom
* are the last two minutes of power, rising with it. Unread values show "—" and light nothing.
* With music playing, the sun's rays and the caterpillar's segments bounce on the beat
* ({@link MusicTap}). What plays crosses the sky on a banner of tissue paper towed by a butterfly
* and, on the next pass, a ladybird ({@link NowPlaying}).
*
* The night, the stars and the far hills are drawn once into a bitmap per size. The caterpillar
* wiggles (about 30 frames a second) only while the car moves and the view is on screen; the
* pulse and the song keep it drawing while there are any.
*/
final class CaterpillarView extends ClusterCanvasView {
private static final int SEGMENTS = 10, RAYS = 14, FRUIT = 15;
private static final int NIGHT_TOP = 0xFF050B22, NIGHT_LOW = 0xFF12305A, NIGHT_END = 0xFF1D4A5E;
private static final int GREEN = 0xFF7ED957, GREEN_DARK = 0xFF2E8B3A, LEAF = 0xFF3FA34D, HEAD = 0xFFE53935;
private static final int SUN = 0xFFFFA726, SUN_LIGHT = 0xFFFFD54F, MOON = 0xFFB8BCC8, ICE = 0xFF5CC8FF;
private static final int PAPER = 0xFFFBF1DA, INK = 0xFF1B1530, BONE = 0xFFF6F0E4, YELLOW = 0xFFFFE066;
private static final int[] TISSUE = { 0xFFE53935, 0xFF2B7BE0, 0xFF2EA84F, 0xFFFFC928, 0xFF8E4DD6, 0xFFFF8A1F };
private final Paint fill = new Paint(Paint.ANTI_ALIAS_FLAG);
private final Paint line = new Paint(Paint.ANTI_ALIAS_FLAG);
private final Paint text = new Paint(Paint.ANTI_ALIAS_FLAG);
private final Paint image = new Paint(Paint.FILTER_BITMAP_FLAG);
private final Path path = new Path();
private final Path bite = new Path();
private final SongBanner banner = new SongBanner();
private final RectF r = new RectF();
private final Typeface serif = Typeface.create("serif", Typeface.BOLD);
private final Typeface face = Typeface.create("sans-serif-condensed", Typeface.BOLD);
private final Typeface thin = Typeface.create("sans-serif-condensed", Typeface.NORMAL);
private Bitmap sky;
private int skyW, skyH;
private float time;
private long lastFrame;
private MusicTap music;
CaterpillarView(Context context) {
super(context);
line.setStyle(Paint.Style.STROKE);
line.setStrokeCap(Paint.Cap.ROUND);
}
/** Where the sun and the caterpillar hear the music; without it, they do not follow any. */
void setMusic(MusicTap tap) {
music = tap;
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
sky = null;
skyW = 0;
}
@Override
protected void onDraw(Canvas c) {
super.onDraw(c);
int w = getWidth(), h = getHeight();
if (w <= 0 || h <= 0) return;
float u = Math.min(h / 640f, w / 1100f);
boolean moving = v.speedKmh != null && v.speedKmh > 0.5f;
if (moving) {
long now = SystemClock.uptimeMillis();
time += lastFrame == 0 ? 0f : Math.min(0.2f, (now - lastFrame) / 1000f);
lastFrame = now;
} else {
lastFrame = 0;
}
if (sky == null || skyW != w || skyH != h) buildSky(w, h, u);
c.drawBitmap(sky, 0, 0, image);
boolean song = drawSong(c, w, u);
drawHills(c, w, h, u);
float pulse = music == null ? 0f : music.pulse();
drawSun(c, u, pulse);
drawLeaf(c, u);
drawClock(c, w, u);
drawFruit(c, w, u);
drawGears(c, w, u);
drawCaterpillar(c, w, u, pulse);
keepAnimating(moving || pulse > 0f || song, 33);
}
private void label(Canvas c, String s, float x, float y, float size, int colour, Typeface tf, Paint.Align a) {
text.setTypeface(tf);
text.setTextSize(size);
text.setTextAlign(a);
text.setColor(colour);
c.drawText(s, x, y, text);
}
/** Text with a dark outline, to read over whatever is behind it. */
private void outlined(Canvas c, String s, float x, float y, float size, int colour, Typeface tf, Paint.Align a, float u) {
text.setStyle(Paint.Style.STROKE);
text.setStrokeWidth(Math.max(3f * u, size * 0.12f));
text.setStrokeJoin(Paint.Join.ROUND);
label(c, s, x, y, size, 0xDD0A0608, tf, a);
text.setStyle(Paint.Style.FILL);
label(c, s, x, y, size, colour, tf, a);
}
private boolean regen() {
return v.powerKw != null && v.powerKw < -0.05f;
}
// ---------- The night page ----------
private void buildSky(int w, int h, float u) {
sky = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
skyW = w;
skyH = h;
Canvas c = new Canvas(sky);
Paint p = new Paint();
p.setShader(new LinearGradient(0, 0, 0, h, new int[] { NIGHT_TOP, NIGHT_LOW, NIGHT_END },
new float[] { 0f, 0.7f, 1f }, Shader.TileMode.CLAMP));
c.drawRect(0, 0, w, h, p);
Paint star = new Paint(Paint.ANTI_ALIAS_FLAG);
star.setColor(0xB0FFF6C8);
for (int i = 0; i < 80; i++) {
float x = ((i * 191 + 17) % 1009) / 1009f * w, y = ((i * 83 + 5) % 421) * u;
c.drawCircle(x, y, (i % 4 == 0 ? 2.2f : 1.2f) * u, star);
}
// A crescent moon, as on the book's first night.
Path moon = new Path(), cut = new Path();
float mx = w * 0.3f, my = 150f * u;
moon.addCircle(mx, my, 30f * u, Path.Direction.CW);
cut.addCircle(mx + 13f * u, my - 6f * u, 27f * u, Path.Direction.CW);
moon.op(cut, Path.Op.DIFFERENCE);
Paint paper = new Paint(Paint.ANTI_ALIAS_FLAG);
paper.setColor(0xFFFFF3B0);
c.drawPath(moon, paper);
hills(c, w, h, h - 150f * u, 0xFF124A3A, 0.8f, u);
hills(c, w, h, h - 100f * u, 0xFF1A5E45, 1.5f, u);
}
private void hills(Canvas c, int w, int h, float base, int colour, float wave, float u) {
Path hill = new Path();
hill.moveTo(0, h);
for (int i = 0; i <= 24; i++) {
float x = w * i / 24f;
hill.lineTo(x, base - (float) (Math.sin(i * wave) * 0.5 + Math.sin(i * wave * 0.37 + 1) * 0.5) * 22f * u);
}
hill.lineTo(w, h);
hill.close();
Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
p.setColor(colour);
c.drawPath(hill, p);
}
// ---------- The hills in front: the last two minutes of power ----------
private void drawHills(Canvas c, int w, int h, float u) {
float base = h - 44f * u, amp = 36f * u;
float[] trail = v.powerTrail;
path.reset();
path.moveTo(0, h);
int n = trail == null ? 0 : trail.length;
if (n == 0) {
path.lineTo(0, base);
path.lineTo(w, base);
} else {
for (int i = 0; i < n; i++) {
float p = trail[i];
float height = Float.isNaN(p) ? 0f : Math.min(1f, Math.abs(p) / (p < 0 ? 40f : 60f));
path.lineTo(i / (n - 1f) * w, base - height * amp);
}
}
path.lineTo(w, h);
path.close();
fill.setColor(0xFF2E9A4E);
c.drawPath(path, fill);
if (n > 1) {
line.setStrokeWidth(3f * u);
for (int i = 1; i < n; i++) {
float a = trail[i - 1], b = trail[i];
if (Float.isNaN(a) || Float.isNaN(b)) continue;
float ha = Math.min(1f, Math.abs(a) / (a < 0 ? 40f : 60f));
float hb = Math.min(1f, Math.abs(b) / (b < 0 ? 40f : 60f));
line.setColor(b < 0 ? ICE : YELLOW);
c.drawLine((i - 1f) / (n - 1f) * w, base - ha * amp, i / (n - 1f) * w, base - hb * amp, line);
}
}
}
// ---------- The sun (or the moon): the battery ----------
private void drawSun(Canvas c, float u, float pulse) {
float cx = 178f * u, cy = 452f * u, rad = 100f * u;
boolean low = v.socPercent != null && v.socPercent < 20f;
boolean blinkOn = (SystemClock.uptimeMillis() / 500) % 2 == 0;
float share = v.socPercent == null ? 0f : Math.max(0f, Math.min(1f, v.socPercent / 100f));
// The rays, longer and stronger with the music; none for the moon.
if (!low) {
for (int i = 0; i < RAYS; i++) {
double a = Math.PI * 2 * i / RAYS;
float cos = (float) Math.cos(a), sin = (float) Math.sin(a);
float tip = rad + (22f + pulse * 16f + (i % 2) * 8f) * u;
float nx = -sin * 8f * u, ny = cos * 8f * u;
path.reset();
path.moveTo(cx + cos * (rad - 2f * u) + nx, cy + sin * (rad - 2f * u) + ny);
path.lineTo(cx + cos * tip, cy + sin * tip);
path.lineTo(cx + cos * (rad - 2f * u) - nx, cy + sin * (rad - 2f * u) - ny);
path.close();
fill.setColor(AccentColor.withAlpha(SUN_LIGHT, 0x60 + (int) (share * 0x9F)));
c.drawPath(path, fill);
}
}
// The disc: dark where the charge is gone, the sun's colour as high as it is full.
fill.setColor(0xFF1E2748);
c.drawCircle(cx, cy, rad, fill);
if (v.socPercent != null) {
float level = cy + rad - share * 2f * rad;
c.save();
c.clipRect(cx - rad, level, cx + rad, cy + rad + 1f);
fill.setColor(low ? MOON : SUN);
c.drawCircle(cx, cy, rad, fill);
c.restore();
}
if (low) {
fill.setColor(0x33000000);
c.drawCircle(cx - 36f * u, cy - 30f * u, 13f * u, fill);
c.drawCircle(cx + 34f * u, cy + 40f * u, 17f * u, fill);
c.drawCircle(cx + 40f * u, cy - 36f * u, 8f * u, fill);
}
line.setColor(low ? MOON : SUN_LIGHT);
line.setStrokeWidth(4f * u);
c.drawCircle(cx, cy, rad, line);
String number = fmt(v.socPercent, "%.0f %%", "—");
outlined(c, number, cx, cy + 20f * u, 58f * u, BONE, serif, Paint.Align.CENTER, u);
if (low && blinkOn) outlined(c, "BATTERY LOW", cx, cy + 56f * u, 21f * u, BONE, face, Paint.Align.CENTER, u);
else if (!low) outlined(c, "BATTERY", cx, cy + 56f * u, 19f * u, YELLOW, face, Paint.Align.CENTER, u);
}
// ---------- The leaf: trip, usage, climate, odometer ----------
private void drawLeaf(Canvas c, float u) {
float l = 20f * u, rr = 336f * u, t = 34f * u, b = 246f * u;
// A stem, then the leaf: a rounded sheet with a pointed tip and veins.
line.setColor(GREEN_DARK);
line.setStrokeWidth(7f * u);
c.drawLine(l + 6f * u, b - 4f * u, l - 12f * u, b + 20f * u, line);
path.reset();
path.moveTo(l + 30f * u, t);
path.lineTo(rr - 50f * u, t);
path.quadTo(rr, t + 4f * u, rr + 10f * u, (t + b) / 2f);
path.quadTo(rr, b - 4f * u, rr - 50f * u, b);
path.lineTo(l + 30f * u, b);
path.quadTo(l, b, l, b - 30f * u);
path.lineTo(l, t + 30f * u);
path.quadTo(l, t, l + 30f * u, t);
path.close();
fill.setColor(LEAF);
c.drawPath(path, fill);
line.setColor(0xFF2E8B3A);
line.setStrokeWidth(3f * u);
c.drawPath(path, line);
line.setColor(0x55B6F0A8);
line.setStrokeWidth(2.5f * u);
c.drawLine(l + 8f * u, (t + b) / 2f, rr + 6f * u, (t + b) / 2f, line);
for (int i = 1; i < 6; i++) {
float x = l + i * 50f * u;
c.drawLine(x, (t + b) / 2f, x + 26f * u, t + 8f * u, line);
c.drawLine(x, (t + b) / 2f, x + 26f * u, b - 8f * u, line);
}
String[][] rows = {
{ "TRIP", fmt(v.tripKm, "%.1f km", "—") },
{ "5 MIN", fmt(v.usage5Min, "%.1f kWh/100", "—") },
{ "SINCE START", fmt(v.usageTrip, "%.1f kWh/100", "—") },
{ "CLIMATE", fmt(v.climateCelsius, "%.0f °C", "—") },
{ "ODOMETER", fmt(v.odometerKm, "%,.0f km", "—") },
};
for (int i = 0; i < rows.length; i++) {
float top = t + 12f * u + i * 38f * u;
c.save();
c.rotate((i % 2 == 0 ? -0.6f : 0.6f), (l + rr) / 2f, top + 16f * u);
r.set(l + 14f * u, top, rr - 14f * u, top + 32f * u);
fill.setColor(PAPER);
c.drawRect(r, fill);
label(c, rows[i][0], l + 24f * u, top + 22f * u, 16f * u, 0xFF6B4A8A, thin, Paint.Align.LEFT);
label(c, rows[i][1], rr - 24f * u, top + 24f * u, 21f * u, INK, face, Paint.Align.RIGHT);
c.restore();
}
}
// ---------- The clock ----------
private void drawClock(Canvas c, int w, float u) {
float cx = w - 115f * u, cy = 96f * u, rad = 62f * u;
// Two leaves on top.
fill.setColor(LEAF);
for (int side = -1; side <= 1; side += 2) {
c.save();
c.rotate(side * 40f, cx, cy);
r.set(cx - 12f * u, cy - rad - 34f * u, cx + 12f * u, cy - rad + 8f * u);
c.drawOval(r, fill);
c.restore();
}
fill.setColor(PAPER);
c.drawCircle(cx, cy, rad, fill);
line.setColor(GREEN_DARK);
line.setStrokeWidth(6f * u);
c.drawCircle(cx, cy, rad, line);
label(c, v.clock.isEmpty() ? "—" : v.clock, cx, cy + 14f * u, 40f * u, INK, serif, Paint.Align.CENTER);
}
// ---------- The fruit: the range ----------
private void drawFruit(Canvas c, int w, float u) {
float rr = w - 40f * u, l = rr - 250f * u;
label(c, fmt(v.rangeKm, "%.0f km", "— km"), (l + rr) / 2f, 220f * u, 36f * u, BONE, serif, Paint.Align.CENTER);
int whole = Tally.lit(v.rangeKm, 50f, FRUIT);
int index = 0;
for (int row = 0; row < 5; row++) {
for (int k = 0; k <= row; k++, index++) {
fruit(c, row, l + 24f * u + k * 46f * u, 256f * u + row * 50f * u, 19f * u, index < whole, u);
}
}
}
/** One fruit of kind {@code kind} (apple, pear, plum, strawberry, orange); used up ones have a bite out. */
private void fruit(Canvas c, int kind, float x, float y, float s, boolean whole, float u) {
final int[] colours = { 0xFFE53935, 0xFFA8D83F, 0xFF7E3FBF, 0xFFE8263E, 0xFFFF8F1F };
path.reset();
switch (kind) {
case 1: r.set(x - s * 0.8f, y - s, x + s * 0.8f, y + s); path.addOval(r, Path.Direction.CW); break;
case 3:
path.moveTo(x - s, y - s * 0.6f);
path.lineTo(x + s, y - s * 0.6f);
path.lineTo(x, y + s);
path.close();
break;
default: path.addCircle(x, y, s, Path.Direction.CW);
}
if (!whole) {
bite.reset();
bite.addCircle(x + s * 0.85f, y - s * 0.55f, s * 0.5f, Path.Direction.CW);
path.op(bite, Path.Op.DIFFERENCE);
}
fill.setColor(whole ? colours[kind] : AccentColor.withAlpha(colours[kind], 0x78));
c.drawPath(path, fill);
if (!whole) return;
// A stem and a leaf, seeds on the strawberry.
line.setColor(kind == 3 ? GREEN_DARK : 0xFF6B4226);
line.setStrokeWidth(2.5f * u);
c.drawLine(x, y - s * (kind == 3 ? 0.6f : 0.95f), x + 3f * u, y - s * 1.3f, line);
fill.setColor(GREEN);
r.set(x + 1f * u, y - s * 1.35f, x + 11f * u, y - s * 1.05f);
c.drawOval(r, fill);
if (kind == 3) {
fill.setColor(YELLOW);
for (int i = 0; i < 3; i++) c.drawCircle(x + (i - 1) * 7f * u, y + (i % 2) * 7f * u - 2f * u, 1.6f * u, fill);
}
}
// ---------- The stages of its life: the gears ----------
private void drawGears(Canvas c, int w, float u) {
float rr = w - 40f * u, l = rr - 250f * u, cy = 520f * u, rad = 26f * u;
String[] gears = { "P", "R", "N", "D" };
String now = Gear.label(v.gear);
float step = (rr - l) / 4f;
for (int i = 0; i < gears.length; i++) {
float mid = l + (i + 0.5f) * step;
boolean on = gears[i].equals(now);
if (on) {
fill.setColor(0x55FFE066);
c.drawCircle(mid, cy, 40f * u, fill);
}
fill.setColor(0xFF1B2A4A);
c.drawCircle(mid, cy, rad, fill);
line.setColor(on ? YELLOW : 0xFF3A5080);
line.setStrokeWidth(2.5f * u);
c.drawCircle(mid, cy, rad, line);
stage(c, i, mid, cy, u, on);
label(c, gears[i], mid, cy + rad + 22f * u, 22f * u, on ? YELLOW : 0xB0FFFFFF, serif, Paint.Align.CENTER);
}
}
/** The life stage for gear {@code i}: an egg, the caterpillar, a cocoon, a butterfly. */
private void stage(Canvas c, int i, float x, float y, float u, boolean on) {
int alpha = on ? 0xFF : 0x90;
switch (i) {
case 0:
r.set(x - 9f * u, y - 13f * u, x + 9f * u, y + 13f * u);
fill.setColor(AccentColor.withAlpha(0xFFF6F0E4, alpha));
c.drawOval(r, fill);
break;
case 1:
for (int k = 0; k < 3; k++) {
fill.setColor(AccentColor.withAlpha(k == 2 ? HEAD : GREEN, alpha));
c.drawCircle(x + (k - 1) * 10f * u, y + 2f * u, 7f * u, fill);
}
break;
case 2:
r.set(x - 8f * u, y - 16f * u, x + 8f * u, y + 16f * u);
fill.setColor(AccentColor.withAlpha(0xFFB08A5A, alpha));
c.drawOval(r, fill);
line.setColor(AccentColor.withAlpha(0xFF6B4226, alpha));
line.setStrokeWidth(1.5f * u);
c.drawLine(x - 7f * u, y - 4f * u, x + 7f * u, y - 4f * u, line);
c.drawLine(x - 7f * u, y + 5f * u, x + 7f * u, y + 5f * u, line);
break;
default:
fill.setColor(AccentColor.withAlpha(0xFF7E57E0, alpha));
r.set(x - 16f * u, y - 14f * u, x, y + 2f * u);
c.drawOval(r, fill);
r.set(x, y - 14f * u, x + 16f * u, y + 2f * u);
c.drawOval(r, fill);
fill.setColor(AccentColor.withAlpha(0xFFFF5FA2, alpha));
r.set(x - 14f * u, y - 2f * u, x, y + 12f * u);
c.drawOval(r, fill);
r.set(x, y - 2f * u, x + 14f * u, y + 12f * u);
c.drawOval(r, fill);
}
}
// ---------- The caterpillar: speed and power ----------
private void drawCaterpillar(Canvas c, int w, float u, float pulse) {
float hx = w / 2f + 250f * u, hy = 400f * 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.
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);
}
}
// 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);
for (int side = -1; side <= 1; side += 2) {
fill.setColor(0xFFFFFFFF);
c.drawCircle(hx + side * 24f * u, hy - 34f * u, 14f * u, fill);
fill.setColor(INK);
c.drawCircle(hx + side * 24f * u + 3f * u, hy - 33f * u, 6f * 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);
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);
}
// ---------- What plays: a butterfly or a ladybird tows it on tissue paper ----------
/**
* A butterfly and a ladybird take turns towing a waving strip of tissue paper with the song across
* the sky, behind the clock and the leaf, again and again while something plays. The paper's
* colour is fixed by the song. Returns whether it is drawn, so the view keeps animating for it.
*/
private boolean drawSong(Canvas c, int w, float u) {
if (v.songTitle == null) return false;
text.setTypeface(face);
text.setTextSize(19f * u);
text.setStyle(Paint.Style.FILL);
String s = SongBanner.line(text, v.songTitle, v.songArtist, Math.min(560f * u, w * 0.45f));
float t = SystemClock.uptimeMillis() / 1000f;
float bw = text.measureText(s) + 40f * u, bh = 28f * u, tow = 76f * u;
float bx = SongBanner.left(t, w, bw, tow, u);
float yc = 66f * u + (float) Math.sin(t * 1.3f) * 3f * u;
int paper = TISSUE[Math.floorMod((v.songTitle + "\n" + v.songArtist).hashCode(), TISSUE.length)];
boolean darkInk = paper == TISSUE[3] || paper == TISSUE[5];
banner.draw(c, fill, line, text, s, bx, yc, bw, bh, t, u, 9f * u, paper, 0x66FFFFFF, darkInk ? INK : 0xFFFFFFFF);
boolean butterfly = SongBanner.pass(t, w, bw, tow, u) % 2 == 0;
float right = bx + bw, cx = right + tow, cy = yc - 4f * u, size = (butterfly ? 60f : 44f) * u;
float[] tail = butterfly ? SongCarriers.butterflyTail : SongCarriers.ladybirdTail;
float ax = cx + tail[0] * size, ay = cy + tail[1] * size;
line.setColor(0xCCFFFFFF);
line.setStrokeWidth(1.5f * u);
c.drawLine(right, yc - bh / 2f + 3f * u, ax, ay, line);
c.drawLine(right, yc + bh / 2f - 3f * u, ax, ay, line);
float flap = (float) Math.sin(t * (butterfly ? 7f : 18f));
if (butterfly) SongCarriers.butterfly(c, fill, line, path, r, cx, cy, size, flap, u);
else SongCarriers.ladybird(c, fill, line, path, r, cx, cy, size, flap, u);
return true;
}
}
@@ -219,7 +219,7 @@ public class MainActivity extends AppCompatActivity {
static final String PREF_OVERVIEW_FULLSCREEN = "overview_fullscreen";
/** The Overview shows what plays (the styles that have a place for it); on by default. */
static final String PREF_NOW_PLAYING = "now_playing";
/** The Halloween pumpkin's and the Christmas tree's light follows the music; off by default, so the analyser stays off. */
/** The Halloween pumpkin's and the Christmas tree's and the Caterpillar's light follows the music; off by default, so the analyser stays off. */
static final String PREF_MUSIC_LIGHT = "music_light";
/** True while the Overview is full screen: the navigation hidden, the edge swipe armed. */
@@ -366,11 +366,13 @@ public class MainActivity extends AppCompatActivity {
nowPlaying = new NowPlayingWatcher(this);
ChristmasView christmasView = new ChristmasView(this);
halloweenView.setMusic(musicTap);
CaterpillarView caterpillarView = new CaterpillarView(this);
christmasView.setMusic(musicTap);
caterpillarView.setMusic(musicTap);
java.util.Collections.addAll(canvasStyles, new MinimalView(this), terminalView,
new NixieView(this), new ChronographView(this), gridView, gridMapView, new TimeCircuitsView(this),
new QuestView(this), visorView, new EmpireView(this), new MuertosView(this), halloweenView,
christmasView);
christmasView, caterpillarView);
View[] canvasPanes = new View[canvasStyles.size()];
for (int i = 0; i < canvasPanes.length; i++) canvasPanes[i] = drawnPane(canvasStyles.get(i));
// Grid with map: its map layer goes under it, in the same pane.
@@ -1791,7 +1793,7 @@ public class MainActivity extends AppCompatActivity {
return style == OverviewStyle.TILES || style == OverviewStyle.HALLOWEEN
|| style == OverviewStyle.MUERTOS || style == OverviewStyle.QUEST || style == OverviewStyle.EMPIRE
|| style == OverviewStyle.TERMINAL || style == OverviewStyle.SCIFI
|| style == OverviewStyle.CHRISTMAS;
|| style == OverviewStyle.CHRISTMAS || style == OverviewStyle.CATERPILLAR;
}
/** The media sessions are watched only while what plays can be seen, and the switch is on. */
@@ -1839,14 +1841,15 @@ public class MainActivity extends AppCompatActivity {
if (!android.text.TextUtils.equals(view.getText(), text)) view.setText(text);
}
/** The styles with a light that can follow the music: Halloween's pumpkin and Christmas's tree. */
/** The styles with a light that can follow the music: Halloween's pumpkin, Christmas's tree and the Caterpillar's sun. */
private static boolean hasMusicLight(OverviewStyle style) {
return style == OverviewStyle.HALLOWEEN || style == OverviewStyle.CHRISTMAS;
return style == OverviewStyle.HALLOWEEN || style == OverviewStyle.CHRISTMAS
|| style == OverviewStyle.CATERPILLAR;
}
/**
* The light listens only while it can be seen (the app in front, on the Overview, in
* Halloween or Christmas) and only when "Light follows the music" is on.
* Halloween, Christmas or Caterpillar) and only when "Light follows the music" is on.
*/
private void updateMusicTap() {
ViewPager2 pager = findViewById(R.id.content);
@@ -1937,7 +1940,7 @@ public class MainActivity extends AppCompatActivity {
getString(R.string.overview_style_time_circuits), getString(R.string.overview_style_quest),
getString(R.string.overview_style_visor), getString(R.string.overview_style_empire),
getString(R.string.overview_style_muertos), getString(R.string.overview_style_halloween),
getString(R.string.overview_style_christmas) };
getString(R.string.overview_style_christmas), getString(R.string.overview_style_caterpillar) };
}
/** The style drop-down; every style is fed on every reading, only the chosen one is shown. */
@@ -34,7 +34,8 @@ enum OverviewStyle {
EMPIRE(false, false),
MUERTOS(false, false),
HALLOWEEN(false, false),
CHRISTMAS(false, false);
CHRISTMAS(false, false),
CATERPILLAR(false, false);
static final String PREF_KEY = "overview_style";
@@ -8,15 +8,16 @@ import android.graphics.RectF;
/**
* The creatures that tow a {@link SongBanner} in the styles with a sky for it, drawn flying right
* with their centre at (x, y) and about {@code s} wide: a dragon (Quest), an eagle (Empire), Santa's
* sleigh with a reindeer and an angel (Christmas).
* sleigh with a reindeer and an angel (Christmas), a butterfly and a ladybird (Caterpillar).
* {@code flap} runs from −1 to 1 with the wing beat, {@code t} is the time in seconds. The caller
* passes its own paints and scratch objects; the paints' colour and width are set here. Where the
* tow lines tie on is {@link #dragonTail}, {@link #eagleTail}, {@link #sleighTail} and {@link #angelTail}, as offsets in units of {@code s}.
* tow lines tie on is {@link #dragonTail}, {@link #eagleTail}, {@link #sleighTail}, {@link #angelTail}, {@link #butterflyTail} and {@link #ladybirdTail}, as offsets in units of {@code s}.
*/
final class SongCarriers {
static final float[] dragonTail = { -1.0f, -0.25f }, eagleTail = { -0.55f, 0.05f },
sleighTail = { -0.62f, 0f }, angelTail = { -0.45f, 0.1f };
sleighTail = { -0.62f, 0f }, angelTail = { -0.45f, 0.1f }, butterflyTail = { -0.3f, 0f },
ladybirdTail = { -0.4f, 0f };
private SongCarriers() {}
@@ -264,4 +265,74 @@ final class SongCarriers {
c.drawPath(path, fill);
c.restore();
}
/** A big tissue-paper butterfly seen from above, flying right, wings beating. */
static void butterfly(Canvas c, Paint fill, Paint line, Path path, RectF r, float x, float y, float s,
float flap, float u) {
final int purple = 0xFF7E57E0, pink = 0xFFFF5FA2, yellow = 0xFFFFE066, dark = 0xFF1B1530;
float k = 0.3f + 0.7f * Math.abs(flap);
c.save();
c.translate(x, y);
for (int side = 0; side < 2; side++) {
c.save();
if (side == 1) c.scale(1f, -1f);
path.reset();
path.moveTo(0.25f * s, 0);
path.quadTo(0.3f * s, -0.8f * s * k, -0.1f * s, -0.95f * s * k);
path.quadTo(-0.4f * s, -0.6f * s * k, -0.05f * s, 0);
path.close();
fill.setColor(purple);
c.drawPath(path, fill);
path.reset();
path.moveTo(-0.05f * s, 0);
path.quadTo(-0.45f * s, -0.6f * s * k, -0.7f * s, -0.35f * s * k);
path.quadTo(-0.6f * s, -0.05f * s * k, -0.2f * s, 0);
path.close();
fill.setColor(pink);
c.drawPath(path, fill);
fill.setColor(yellow);
c.drawCircle(0.02f * s, -0.58f * s * k, 0.08f * s, fill);
c.drawCircle(-0.4f * s, -0.33f * s * k, 0.06f * s, fill);
c.restore();
}
fill.setColor(dark);
r.set(-0.4f * s, -0.06f * s, 0.3f * s, 0.06f * s);
c.drawOval(r, fill);
c.drawCircle(0.34f * s, 0, 0.07f * s, fill);
line.setColor(dark);
line.setStrokeWidth(1.5f * u);
c.drawLine(0.38f * s, -0.03f * s, 0.55f * s, -0.18f * s, line);
c.drawLine(0.38f * s, 0.03f * s, 0.55f * s, 0.18f * s, line);
c.restore();
}
/** A ladybird seen from above, flying right, with its wings out. */
static void ladybird(Canvas c, Paint fill, Paint line, Path path, RectF r, float x, float y, float s,
float flap, float u) {
final int red = 0xFFE53935, dark = 0xFF1B1530;
c.save();
c.translate(x, y);
float out = (0.3f + 0.25f * Math.abs(flap)) * s;
fill.setColor(0xAAFFFFFF);
r.set(-0.45f * s, -out - 0.12f * s, 0.15f * s, -out + 0.12f * s);
c.drawOval(r, fill);
r.set(-0.45f * s, out - 0.12f * s, 0.15f * s, out + 0.12f * s);
c.drawOval(r, fill);
fill.setColor(red);
r.set(-0.4f * s, -0.26f * s, 0.26f * s, 0.26f * s);
c.drawOval(r, fill);
line.setColor(dark);
line.setStrokeWidth(2f * u);
c.drawLine(-0.4f * s, 0, 0.26f * s, 0, line);
fill.setColor(dark);
c.drawCircle(-0.2f * s, -0.12f * s, 0.045f * s, fill);
c.drawCircle(-0.2f * s, 0.12f * s, 0.045f * s, fill);
c.drawCircle(0.05f * s, -0.14f * s, 0.045f * s, fill);
c.drawCircle(0.05f * s, 0.14f * s, 0.045f * s, fill);
c.drawCircle(0.34f * s, 0, 0.12f * s, fill);
line.setStrokeWidth(1.5f * u);
c.drawLine(0.42f * s, -0.06f * s, 0.58f * s, -0.18f * s, line);
c.drawLine(0.42f * s, 0.06f * s, 0.58f * s, 0.18f * s, line);
c.restore();
}
}
@@ -27,6 +27,7 @@ final class StylePalette {
case MUERTOS: return R.style.ThemeOverlay_EV_Style_Muertos;
case HALLOWEEN: return R.style.ThemeOverlay_EV_Style_Halloween;
case CHRISTMAS: return R.style.ThemeOverlay_EV_Style_Christmas;
case CATERPILLAR: return R.style.ThemeOverlay_EV_Style_Caterpillar;
default: return 0;
}
}
@@ -29,7 +29,7 @@
<!-- Only the styles that show what plays (the styles with a place for it). -->
<com.google.android.material.switchmaterial.SwitchMaterial android:id="@+id/now_playing_switch" android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="72dp" android:text="@string/label_now_playing" android:textAppearance="@style/Text.EV.Body" android:paddingTop="8dp" android:paddingBottom="8dp" app:useMaterialThemeColors="true" app:thumbTint="@color/switch_thumb" app:trackTint="@color/switch_track" android:textColor="@color/ev_text_primary" android:visibility="gone" />
<TextView android:id="@+id/now_playing_hint" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/hint_now_playing" android:textAppearance="@style/Text.EV.Secondary" android:visibility="gone" />
<!-- Only Halloween and Christmas listen to the music. -->
<!-- Only Halloween, Christmas and Caterpillar listen to the music. -->
<com.google.android.material.switchmaterial.SwitchMaterial android:id="@+id/music_light_switch" android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="72dp" android:text="@string/label_music_light" android:textAppearance="@style/Text.EV.Body" android:paddingTop="8dp" android:paddingBottom="8dp" app:useMaterialThemeColors="true" app:thumbTint="@color/switch_thumb" app:trackTint="@color/switch_track" android:textColor="@color/ev_text_primary" android:visibility="gone" />
<TextView android:id="@+id/music_light_hint" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/hint_music_light" android:textAppearance="@style/Text.EV.Secondary" android:visibility="gone" />
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:baselineAligned="false">
+1 -1
View File
@@ -20,7 +20,7 @@
<!-- Only the styles that show what plays (the styles with a place for it). -->
<com.google.android.material.switchmaterial.SwitchMaterial android:id="@+id/now_playing_switch" android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="72dp" android:text="@string/label_now_playing" android:textAppearance="@style/Text.EV.Body" android:paddingTop="8dp" android:paddingBottom="8dp" app:useMaterialThemeColors="true" app:thumbTint="@color/switch_thumb" app:trackTint="@color/switch_track" android:textColor="@color/ev_text_primary" android:visibility="gone" />
<TextView android:id="@+id/now_playing_hint" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/hint_now_playing" android:textAppearance="@style/Text.EV.Secondary" android:visibility="gone" />
<!-- Only Halloween and Christmas listen to the music. -->
<!-- Only Halloween, Christmas and Caterpillar listen to the music. -->
<com.google.android.material.switchmaterial.SwitchMaterial android:id="@+id/music_light_switch" android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="72dp" android:text="@string/label_music_light" android:textAppearance="@style/Text.EV.Body" android:paddingTop="8dp" android:paddingBottom="8dp" app:useMaterialThemeColors="true" app:thumbTint="@color/switch_thumb" app:trackTint="@color/switch_track" android:textColor="@color/ev_text_primary" android:visibility="gone" />
<TextView android:id="@+id/music_light_hint" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/hint_music_light" android:textAppearance="@style/Text.EV.Secondary" android:visibility="gone" />
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/label_tire_unit" android:textAppearance="@style/Text.EV.Body" android:layout_marginTop="20dp" />
+1
View File
@@ -153,6 +153,7 @@
<string name="overview_style_muertos">Día de Muertos</string>
<string name="overview_style_halloween">Halloween</string>
<string name="overview_style_christmas">Christmas</string>
<string name="overview_style_caterpillar">Caterpillar</string>
<string name="overview_style_nixie">Nixie tubes</string>
<string name="overview_style_chronograph">Chronograph</string>
<string name="cockpit_battery">BATTERY</string>
@@ -134,4 +134,14 @@
<item name="evAccentStroke">#80B08A3A</item>
<item name="evAccentTrack">#7A2630</item>
</style>
<!-- Caterpillar: the green of its body. -->
<style name="ThemeOverlay.EV.Style.Caterpillar" parent="">
<item name="evAccent">#5CC15A</item>
<item name="evOnAccent">#07210B</item>
<item name="evAccentLight">#B6F0A8</item>
<item name="evAccentDeep">#123A1A</item>
<item name="evAccentSurface">#10243A</item>
<item name="evAccentStroke">#8057A04A</item>
<item name="evAccentTrack">#2E6B35</item>
</style>
</resources>
Binary file not shown.

After

Width:  |  Height:  |  Size: 249 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 KiB