Compare commits
4
Commits
car-2.45.0
...
car-2.45.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f9813c2afd | ||
|
|
3a532cd498 | ||
|
|
06a61282a6 | ||
|
|
ed32fbbc1f |
@@ -140,6 +140,10 @@ 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-02 — App 2.45.2: Halloween's song banner **waves** (owner: "Can it also be a bit wavy?"): waves run from the bat to the tail, still where the bat holds it and up to 7 units at the tail, and the text follows them (`drawTextOnPath`). Tested: emulator with the throwaway fake session, unit tests, lint. Not tried on the car.
|
||||
- 2026-11-02 — App 2.45.1 published (`car-2.45.1`).
|
||||
- 2026-11-02 — App 2.45.1: **Halloween's song flies through the sky** (owner: "Instead of taking up screen space on the bottom, can we somehow include the title in the theme itself? ... have text fly through the air"). A bat tows a parchment banner with "♪ title — artist" from left to right across the strip of sky above the sign, behind the moon, one pass about every half minute, again and again while something plays (the title is cut first so the artist stays); the line at the foot of the hill is gone. While a banner flies the view draws about 30 frames a second, as it does while driving. Tested: emulator with the throwaway fake session (banner clear of the sign along the whole pass), unit tests, lint. Not tried on the car.
|
||||
- 2026-11-02 — App 2.45.0 published (`car-2.45.0`).
|
||||
- 2026-11-02 — App 2.45.0: **now playing on the Overview** (owner: "can we also implement music titles etc in our app?"; display only, Tiles and Halloween first). `NowPlayingWatcher` follows the active media sessions as the music-sources check reads them (`MediaSessionManager`, MEDIA_CONTENT_CONTROL as the system user), with a change listener and a callback per session, so reading costs no call into another app; it never connects to a media browser service (2.42.0) and sends no command. It runs only while the Overview is on screen in Tiles or Halloween with Settings → "Show what's playing" on (**on by default**, `PREF_NOW_PLAYING`). `NowPlaying` (pure, unit-tested) picks the session that plays, the one updated last of several, else the one paused last, passing over sessions without text (Bluetooth's empty one); CarPlay's YouTube titles lose their `[videoID]`; the radio shows its text (the artist field, e.g. "BLIND MELON - NO RAIN") with the station below. Tiles: the empty slot under the battery shows source ("· paused" when paused), title, artist, a cover (the session's picture, or Bluetooth's `ALBUM_ART_URI` file, read small off the main thread) and how far the song is; invisible when nothing plays; the narrow layout has no room and shows none. Halloween: "♪ title — artist" along the foot of the hill, the title cut first. Titles stay on screen: not logged, not sent. Tested: unit tests, lint, emulator with a throwaway build publishing a fake session (CarPlay-like title with ID removed, cover, bar; Halloween line; the switch hides and brings back both). With Halloween chosen the Settings card has two switches more and scrolls further. Not tried on the car: whether the car's sessions, Bluetooth's cover file and the radio text show as in the music-sources checks.
|
||||
- 2026-11-02 — App 2.44.0 published (`car-2.44.0`).
|
||||
- 2026-11-02 — App 2.44.0: **the pumpkin light follows the bass, and only when switched on** (owner: "more focussed on the bass", "optional via a settings flag"). `MusicPulse` now reads the kick drum's band (40–120 Hz, was 20–250 Hz) and lights only on a jump since the last reading, against the biggest jumps of the last 4 s; a jump under a fifth of that, or under half the bass's average level, lights nothing. So kicks flash, and a held note, a wobbling bass line or a swelling note stay dark (unit tests with made-up patterns). It flares at once and falls in 0.2 s. New switch under the Overview style, shown only for Halloween: "Pumpkin light follows the music" (`PREF_MUSIC_LIGHT`), **off by default**; off, the analyser never starts. Tuned on made-up signals only: no Bluetooth or CarPlay sound report from the car has arrived yet. Next: now playing (title, artist, source) from the media sessions, in its own version. Tested: unit tests, lint, emulator (switch off by default and the analyser thread absent; on, it runs only on the Overview; a throwaway build playing a pulsing test tone: the glow flares with the switch on, and stays exactly still with it off). With Halloween chosen, the Settings card now scrolls a little to reach the tyre unit. Not tried on the car.
|
||||
|
||||
@@ -267,7 +267,8 @@ you drive the bats fly, the potion bubbles and the ghost floats (where there is
|
||||
this screen). With "Pumpkin light follows the music" on (Settings, shown with this style; off by
|
||||
default) and Bluetooth or CarPlay music playing, the pumpkin's glow flares and its carving
|
||||
brightens on each kick drum; the radio cannot be heard, so it burns as usual then. What plays
|
||||
(Bluetooth, CarPlay or the radio) is carved into the foot of the hill. The app turns the
|
||||
(Bluetooth, CarPlay or the radio) flies across the top of the sky on a waving parchment banner that a
|
||||
bat tows, behind the moon, again and again while it plays. The app turns the
|
||||
pumpkin's orange with it.
|
||||
|
||||

|
||||
|
||||
+2
-2
@@ -34,8 +34,8 @@ android {
|
||||
applicationId "com.stinnesbeck.nilsmg4"
|
||||
minSdk 27
|
||||
targetSdk 36
|
||||
versionCode 160
|
||||
versionName "2.45.0"
|
||||
versionCode 162
|
||||
versionName "2.45.2"
|
||||
|
||||
// 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.
|
||||
|
||||
@@ -6,6 +6,7 @@ import android.graphics.Canvas;
|
||||
import android.graphics.LinearGradient;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.PathMeasure;
|
||||
import android.graphics.RadialGradient;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.Shader;
|
||||
@@ -27,7 +28,7 @@ import java.util.Locale;
|
||||
* of power, rising with it, in orange or, where it regenerated, green. Unread values show "—" and
|
||||
* light nothing. With music playing, the pumpkin's glow swells and its carving brightens on the
|
||||
* beat ({@link MusicTap}); colours and the embers keep their meaning, and with no music it burns as
|
||||
* before. What plays is carved into the foot of the hill ({@link NowPlaying}).
|
||||
* before. What plays flies across the sky on a banner a bat tows ({@link NowPlaying}).
|
||||
*
|
||||
* The night sky is drawn once into a bitmap per size; only the rest goes over it. Bats, bubbles and
|
||||
* the ghost move (about 30 frames a second) only while the car moves and the view is on screen;
|
||||
@@ -50,6 +51,8 @@ final class HalloweenView extends ClusterCanvasView {
|
||||
private final Paint image = new Paint(Paint.FILTER_BITMAP_FLAG);
|
||||
private final Paint glow = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
private final Path path = new Path();
|
||||
private final Path baseline = new Path();
|
||||
private final PathMeasure measure = new PathMeasure();
|
||||
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);
|
||||
@@ -96,10 +99,10 @@ final class HalloweenView extends ClusterCanvasView {
|
||||
if (sky == null || skyW != w || skyH != h) buildSky(w, h, u);
|
||||
c.drawBitmap(sky, 0, 0, image);
|
||||
drawBats(c, w, u);
|
||||
boolean song = drawSong(c, w, u);
|
||||
drawMoon(c, w, u);
|
||||
drawGhost(c, w, u);
|
||||
drawHill(c, w, h, u);
|
||||
drawSong(c, w, h, u);
|
||||
drawEmbers(c, w, u);
|
||||
float pulse = music == null ? 0f : music.pulse();
|
||||
drawPumpkin(c, w, u, pulse);
|
||||
@@ -107,7 +110,7 @@ final class HalloweenView extends ClusterCanvasView {
|
||||
drawFlask(c, u);
|
||||
drawTombstone(c, w, u);
|
||||
drawGears(c, w, u);
|
||||
keepAnimating(moving || pulse > 0f, 33);
|
||||
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) {
|
||||
@@ -277,17 +280,73 @@ final class HalloweenView extends ClusterCanvasView {
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- What plays, carved into the hill ----------
|
||||
// ---------- What plays: a bat tows it across the sky on a banner ----------
|
||||
|
||||
private void drawSong(Canvas c, int w, int h, float u) {
|
||||
if (v.songTitle == null) return;
|
||||
text.setTypeface(thin);
|
||||
text.setTextSize(20f * u);
|
||||
float room = w * 0.44f;
|
||||
String artist = v.songArtist == null ? "" : " — " + cut(v.songArtist, room / 2f);
|
||||
/**
|
||||
* A bat pulls a waving parchment banner with the song from left to right, high in the sky, behind the
|
||||
* sign and the moon, again and again while something plays. 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);
|
||||
float room = Math.min(560f * u, w * 0.45f);
|
||||
String artist = v.songArtist == null ? "" : " — " + cut(v.songArtist, room / 2.5f);
|
||||
// A long title gives way, so the artist stays.
|
||||
String s = "♪ " + cut(v.songTitle, room - text.measureText("♪ " + artist)) + artist;
|
||||
label(c, s, w / 2f, h - 12f * u, 20f * u, AccentColor.withAlpha(BONE, 0xC0), thin, Paint.Align.CENTER);
|
||||
float t = SystemClock.uptimeMillis() / 1000f;
|
||||
float bw = text.measureText(s) + 40f * u, bh = 30f * u, tow = 56f * u;
|
||||
// One pass takes about half a minute; then it comes round again from the left.
|
||||
float travel = w + bw + tow + 80f * u;
|
||||
float bx = (t * 70f * u) % travel - bw - tow - 40f * u;
|
||||
// In the strip of sky above the sign, clear of it.
|
||||
float yc = 34f * u + (float) Math.sin(t * 1.3f) * 4f * u;
|
||||
float top = yc - bh / 2f, bottom = yc + bh / 2f, right = bx + bw, notch = 12f * u;
|
||||
// The ribbon, waving in the wind: still where the bat holds it, rippling more towards its
|
||||
// tail, which is cut as a swallowtail. The text follows the waves.
|
||||
int steps = Math.max(8, Math.round(bw / (12f * u)));
|
||||
path.reset();
|
||||
for (int i = 0; i <= steps; i++) {
|
||||
float x = right - bw * i / steps;
|
||||
if (i == 0) path.moveTo(x, top + wave(x, right, bw, t, u));
|
||||
else path.lineTo(x, top + wave(x, right, bw, t, u));
|
||||
}
|
||||
path.lineTo(bx + notch, yc + wave(bx + notch, right, bw, t, u));
|
||||
for (int i = steps; i >= 0; i--) path.lineTo(right - bw * i / steps, bottom + wave(right - bw * i / steps, right, bw, t, u));
|
||||
path.close();
|
||||
fill.setColor(BONE);
|
||||
c.drawPath(path, fill);
|
||||
line.setColor(0xFF8A6A3A);
|
||||
line.setStrokeWidth(2f * u);
|
||||
c.drawPath(path, line);
|
||||
baseline.reset();
|
||||
for (int i = 0; i <= steps; i++) {
|
||||
float x = bx + notch + (bw - notch) * i / steps;
|
||||
if (i == 0) baseline.moveTo(x, yc + wave(x, right, bw, t, u));
|
||||
else baseline.lineTo(x, yc + wave(x, right, bw, t, u));
|
||||
}
|
||||
measure.setPath(baseline, false);
|
||||
text.setColor(INK);
|
||||
text.setTextAlign(Paint.Align.LEFT);
|
||||
c.drawTextOnPath(s, baseline, Math.max(0f, (measure.getLength() - text.measureText(s)) / 2f), 7f * u, text);
|
||||
// The tow lines, and the bat at their end.
|
||||
float batX = right + tow, batY = yc - 10f * u;
|
||||
line.setColor(BAT_EDGE);
|
||||
line.setStrokeWidth(1.5f * u);
|
||||
c.drawLine(right, top + 3f * u, batX, batY + 6f * u, line);
|
||||
c.drawLine(right, bottom - 3f * u, batX, batY + 6f * u, line);
|
||||
bat(c, batX, batY, 26f * u, (float) Math.sin(t * 12f), u);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* How far the banner bends at {@code x}: nothing at the bat's end ({@code right}), up to 7 units
|
||||
* at the tail, in waves that run from the bat to the tail.
|
||||
*/
|
||||
private static float wave(float x, float right, float bw, float t, float u) {
|
||||
float along = right - x;
|
||||
return 7f * u * (along / bw) * (float) Math.sin(along / (170f * u) * 2f * Math.PI - t * 5f);
|
||||
}
|
||||
|
||||
/** {@code s} cut with "…" to fit {@code room} in the text paint as set. */
|
||||
|
||||
Reference in New Issue
Block a user