Compare commits
2
Commits
car-2.48.0
...
car-2.49.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bf6038fd37 | ||
|
|
5cbd6699aa |
@@ -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 |
|
||||
|
||||
@@ -140,6 +140,8 @@ 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`).
|
||||
- 2026-11-02 — App 2.47.0: **now playing in four more styles** (owner: "let's implement Now playing in more styles"; Spanish labels declined). Quest: a red dragon tows a waving scroll across the top of the sky, behind the frames. Empire: an eagle flies a scroll across the top of the world, behind the speed. Terminal: a PLAYING row above the prompt. Sci-fi: the song in black on the top bar. The creatures are in the new `SongCarriers` (dragon, eagle) beside `SongBanner`. Same switch as before (Settings → "Show what's playing"). Not yet in the rest: Gauges, Cockpit, Retro, Futuristic, MG, Minimal, Nixie, Chronograph, Grid, Grid with map, Time circuits, Visor, Cartoon. Tested: unit tests, lint, emulator with the throwaway fake session. Not tried on the car.
|
||||
|
||||
@@ -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.
|
||||
|
||||

|
||||

|
||||
|
||||
## 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.
|
||||
|
||||

|
||||

|
||||
|
||||
+2
-2
@@ -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">
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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 |
Reference in New Issue
Block a user