App 2.27.1: accent colour only for styles drawn in it

Settings → Display now has the Overview style first with the accent
colour beside it; for styles with their own palette the accent field is
greyed out with a note. Styles are an enum, OverviewStyle, with
usesAccent (unit-tested). Emulator only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LhzAVaUyqcw5PkGjz4mWW6
This commit is contained in:
2026-10-24 21:28:39 +02:00
co-authored by Claude Opus 5.5
parent 4ef386a8d9
commit ef2ba89fba
10 changed files with 163 additions and 42 deletions
+1 -1
View File
@@ -73,7 +73,7 @@ Repository: `ssh://git@git.stinnesbeck.com:2222/nils/mg4.git` (remote `origin`),
| 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 |
| Settings | full screen, voice key, tyre unit, accent colour, Overview style, battery, charging price, version, update check, install from USB, About |
| Settings | full screen, voice key, Overview style with the accent colour beside it (offered only for styles drawn in it, `OverviewStyle.usesAccent`), tyre unit, battery, charging price, version, update check, install from USB, About |
| Logs | update log (every install step, with the platform's words), upload log |
Key classes (`app/src/main/java/com/evsuite/abrp/`): `AbrpUploadService` (foreground service,
+12
View File
@@ -131,6 +131,18 @@ 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-10-24 — App 2.27.1 (branch `styles-retro-future`): Settings → Display rearranged:
Overview style first, the accent colour beside it, tyre pressure on its own row below. The
accent colour is only offered for styles drawn in it (Tiles, Gauges, Cockpit, Retro (accent
colour), Futuristic, MG, Minimal, Chronograph); for the styles with their own palette (Retro,
Sci-fi, Cartoon, Blueprint, Terminal, Aviation, Nixie tubes) the field is greyed out and
ignores taps, keeping the colour chosen before, and a line says "… has its own colours. To
change the accent colour, pick a style that uses it." The styles are now an enum,
`OverviewStyle` (stored names, `usesAccent`, the old `DASHBOARD` value; unit-tested), in place
of the string array. The style list is wider than its field so "Retro (accent colour)" fits.
Checked on the emulator: Tiles (accent open), Cartoon (greyed, tap ignored, note), back to
Tiles; the page fits without scrolling
(`screenshots/nilsmg4-2.27.1-settings-own-colours.png`). Not tried on the car.
- 2026-10-24 — App 2.27.0 (branch `styles-retro-future`): the Overview can go full width. A
small round button in its top-right corner (40 dp disc in a 72 dp touch area, over the pages)
hides the app's navigation (sidebar, or the bottom bar on narrow screens); its icon turns to
+2 -2
View File
@@ -34,8 +34,8 @@ android {
applicationId "com.stinnesbeck.nilsmg4"
minSdk 27
targetSdk 36
versionCode 121
versionName "2.27.0"
versionCode 122
versionName "2.27.1"
// Nothing about the server ships in the APK: its URL and ingest token are entered in
// the app's Server tab (ServerConfig), and the database is only reachable by the server.
@@ -68,7 +68,7 @@ public class MainActivity extends AppCompatActivity {
private OctagonView octagonView;
/** The styles built on {@link ClusterCanvasView}, fed together. */
private final java.util.List<ClusterCanvasView> canvasStyles = new java.util.ArrayList<>();
/** The pane of each Overview style, by its index in {@link #OVERVIEW_STYLES}. */
/** The pane of each Overview style, by its {@link OverviewStyle} ordinal. */
private View[] stylePanes;
private View consumptionPane;
private View chargingPane;
@@ -1226,6 +1226,7 @@ public class MainActivity extends AppCompatActivity {
prefs.edit().putString(AccentColor.PREF_KEY, colors[position].name()).apply();
recreate();
});
applyAccentAvailability();
}
/** Tyre pressure unit: display only, read on every refresh of the Overview. */
@@ -1479,35 +1480,45 @@ public class MainActivity extends AppCompatActivity {
// ---------- Gauges (Overview style) ----------
static final String PREF_OVERVIEW_STYLE = "overview_style";
static final String PREF_OVERVIEW_STYLE = OverviewStyle.PREF_KEY;
private GaugeView dashPower, dashSpeed, dashBattery;
private BatteryBarView dashBatteryBar;
private TextView dashGear, dashClimate, dashUsage, dashBatteryText;
/** Kilometres since start, from the range estimator; null until it has a distance. */
private Double dashTripKm;
/** The stored values of the Overview styles, in the order of the drop-down. */
private static final String[] OVERVIEW_STYLES = { "TILES", "GAUGES", "COCKPIT", "RETRO", "RETRO_ACCENT", "FUTURISTIC",
"SCIFI", "CARTOON", "BLUEPRINT", "MG", "MINIMAL", "TERMINAL", "AVIATION", "NIXIE", "CHRONOGRAPH" };
/** The chosen style's index in {@link #OVERVIEW_STYLES}; anything unknown is Tiles. */
private int overviewStyle() {
String style = prefs.getString(PREF_OVERVIEW_STYLE, "TILES");
if ("DASHBOARD".equals(style)) style = "GAUGES"; // its name before 2.22.3
for (int i = 0; i < OVERVIEW_STYLES.length; i++) if (OVERVIEW_STYLES[i].equals(style)) return i;
return 0;
private OverviewStyle overviewStyle() {
return OverviewStyle.fromPref(prefs.getString(PREF_OVERVIEW_STYLE, null));
}
private void applyOverviewStyle() {
int style = overviewStyle();
for (View pane : stylePanes) pane.setVisibility(pane == stylePanes[style] ? View.VISIBLE : View.GONE);
OverviewStyle style = overviewStyle();
for (View pane : stylePanes) pane.setVisibility(pane == stylePanes[style.ordinal()] ? View.VISIBLE : View.GONE);
// Retro in either palette: one pane, the view told which.
retroView.setAccentPalette("RETRO_ACCENT".equals(OVERVIEW_STYLES[style]));
retroView.setAccentPalette(style == OverviewStyle.RETRO_ACCENT);
applyAccentAvailability();
}
/** The style drop-down; every style is fed on every reading, only the chosen one is shown. */
private void bindOverviewStyle() {
String[] labels = { getString(R.string.overview_style_tiles), getString(R.string.overview_style_gauges),
/**
* The accent colour can only be picked for a style drawn in it; for the others the drop-down
* is greyed out (keeping the colour chosen before) and a line says why.
*/
private void applyAccentAvailability() {
if (settingsPane == null) return;
OverviewStyle style = overviewStyle();
View field = settingsPane.findViewById(R.id.accent_field);
if (field == null) return;
field.setEnabled(style.usesAccent);
settingsPane.findViewById(R.id.accent_choices).setEnabled(style.usesAccent);
field.setAlpha(style.usesAccent ? 1f : 0.45f);
TextView note = settingsPane.findViewById(R.id.accent_note);
note.setVisibility(style.usesAccent ? View.GONE : View.VISIBLE);
note.setText(getString(R.string.accent_not_used, styleLabels()[style.ordinal()]));
}
/** The drop-down's labels, by {@link OverviewStyle} ordinal. */
private String[] styleLabels() {
return new String[] { getString(R.string.overview_style_tiles), getString(R.string.overview_style_gauges),
getString(R.string.overview_style_cockpit), getString(R.string.overview_style_retro),
getString(R.string.overview_style_retro_accent), getString(R.string.overview_style_futuristic),
getString(R.string.overview_style_scifi), getString(R.string.overview_style_cartoon),
@@ -1515,9 +1526,18 @@ public class MainActivity extends AppCompatActivity {
getString(R.string.overview_style_minimal), getString(R.string.overview_style_terminal),
getString(R.string.overview_style_aviation), getString(R.string.overview_style_nixie),
getString(R.string.overview_style_chronograph) };
bindChoices(settingsPane.findViewById(R.id.overview_style_choices), labels, overviewStyle(),
}
/** The style drop-down; every style is fed on every reading, only the chosen one is shown. */
private void bindOverviewStyle() {
OverviewStyle[] styles = OverviewStyle.values();
com.google.android.material.textfield.MaterialAutoCompleteTextView field =
settingsPane.findViewById(R.id.overview_style_choices);
// Wider than its field, which shares a row with the accent colour: "Retro (accent colour)".
field.setDropDownWidth(Math.round(360 * getResources().getDisplayMetrics().density));
bindChoices(field, styleLabels(), overviewStyle().ordinal(),
position -> {
prefs.edit().putString(PREF_OVERVIEW_STYLE, OVERVIEW_STYLES[position]).apply();
prefs.edit().putString(PREF_OVERVIEW_STYLE, styles[position].name()).apply();
applyOverviewStyle();
});
}
@@ -0,0 +1,44 @@
package com.evsuite.abrp;
/**
* The Overview styles in the order of the Settings drop-down, the value each is stored as, and
* whether it is drawn in the chosen accent colour. Styles with their own fixed palette (Retro's
* display, the starship console, the cartoon, the blueprint, the terminal, the flight display,
* the nixie tubes) ignore it, so Settings only offers the accent colour for the others. Pure
* Java, unit-tested.
*/
enum OverviewStyle {
TILES(true),
GAUGES(true),
COCKPIT(true),
RETRO(false),
RETRO_ACCENT(true),
FUTURISTIC(true),
SCIFI(false),
CARTOON(false),
BLUEPRINT(false),
MG(true),
MINIMAL(true),
TERMINAL(false),
AVIATION(false),
NIXIE(false),
CHRONOGRAPH(true);
static final String PREF_KEY = "overview_style";
/** Drawn in the accent colour, so the accent choice changes how it looks. */
final boolean usesAccent;
OverviewStyle(boolean usesAccent) {
this.usesAccent = usesAccent;
}
/** The stored style; "DASHBOARD" (Gauges before 2.22.3) and anything unknown handled. */
static OverviewStyle fromPref(String stored) {
if ("DASHBOARD".equals(stored)) return GAUGES;
for (OverviewStyle style : values()) {
if (style.name().equals(stored)) return style;
}
return TILES;
}
}
@@ -10,24 +10,31 @@
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/hint_full_screen" android:textAppearance="@style/Text.EV.Secondary" />
<com.google.android.material.switchmaterial.SwitchMaterial android:id="@+id/voice_key_switch" android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="72dp" android:text="@string/label_voice_key" 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" />
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/hint_voice_key" android:textAppearance="@style/Text.EV.Secondary" />
<!-- The Overview style, and next to it the accent colour, which only styles drawn in it offer. -->
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:baselineAligned="false">
<LinearLayout android:layout_width="0dp" android:layout_weight="2" android:layout_height="wrap_content" android:layout_marginEnd="12dp" android:orientation="vertical">
<LinearLayout android:layout_width="0dp" android:layout_weight="1" android:layout_height="wrap_content" android:layout_marginEnd="12dp" android:orientation="vertical">
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/label_overview_style" android:textAppearance="@style/Text.EV.Body" android:layout_marginTop="8dp" />
<com.google.android.material.textfield.TextInputLayout style="@style/Widget.EV.Dropdown" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="4dp">
<com.google.android.material.textfield.MaterialAutoCompleteTextView android:id="@+id/overview_style_choices" android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="72dp" android:inputType="none" android:maxLines="1" android:ellipsize="end" android:textSize="20sp" app:simpleItemLayout="@layout/dropdown_item" />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
<LinearLayout android:layout_width="0dp" android:layout_weight="1" android:layout_height="wrap_content" android:orientation="vertical">
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/label_accent" android:textAppearance="@style/Text.EV.Body" android:layout_marginTop="8dp" />
<com.google.android.material.textfield.TextInputLayout android:id="@+id/accent_field" style="@style/Widget.EV.Dropdown" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="4dp">
<com.google.android.material.textfield.MaterialAutoCompleteTextView android:id="@+id/accent_choices" android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="72dp" android:inputType="none" android:maxLines="1" android:ellipsize="end" android:textSize="20sp" app:simpleItemLayout="@layout/dropdown_item" />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
</LinearLayout>
<TextView android:id="@+id/accent_note" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="4dp" 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">
<LinearLayout android:layout_width="0dp" android:layout_weight="1" android:layout_height="wrap_content" android:layout_marginEnd="12dp" android:orientation="vertical">
<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="8dp" />
<com.google.android.material.textfield.TextInputLayout style="@style/Widget.EV.Dropdown" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="4dp">
<com.google.android.material.textfield.MaterialAutoCompleteTextView android:id="@+id/tire_unit_choices" android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="72dp" android:inputType="none" android:textSize="20sp" app:simpleItemLayout="@layout/dropdown_item" />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
<LinearLayout android:layout_width="0dp" android:layout_weight="3" android:layout_height="wrap_content" android:orientation="vertical">
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/label_accent" android:textAppearance="@style/Text.EV.Body" android:layout_marginTop="8dp" />
<com.google.android.material.textfield.TextInputLayout style="@style/Widget.EV.Dropdown" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="4dp">
<com.google.android.material.textfield.MaterialAutoCompleteTextView android:id="@+id/accent_choices" android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="72dp" android:inputType="none" android:textSize="20sp" app:simpleItemLayout="@layout/dropdown_item" />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
<Space android:layout_width="0dp" android:layout_weight="1" android:layout_height="1dp" />
</LinearLayout>
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/label_overview_style" android:textAppearance="@style/Text.EV.Body" android:layout_marginTop="8dp" />
<com.google.android.material.textfield.TextInputLayout style="@style/Widget.EV.Dropdown" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="4dp">
<com.google.android.material.textfield.MaterialAutoCompleteTextView android:id="@+id/overview_style_choices" android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="72dp" android:inputType="none" android:textSize="20sp" app:simpleItemLayout="@layout/dropdown_item" />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
</LinearLayout>
<LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:orientation="vertical" android:layout_marginEnd="16dp">
+10 -8
View File
@@ -7,18 +7,20 @@
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/hint_full_screen" android:textAppearance="@style/Text.EV.Secondary" />
<com.google.android.material.switchmaterial.SwitchMaterial android:id="@+id/voice_key_switch" android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="72dp" android:text="@string/label_voice_key" 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" />
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/hint_voice_key" android:textAppearance="@style/Text.EV.Secondary" />
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/label_overview_style" android:textAppearance="@style/Text.EV.Body" android:layout_marginTop="20dp" />
<com.google.android.material.textfield.TextInputLayout style="@style/Widget.EV.Dropdown" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="12dp">
<com.google.android.material.textfield.MaterialAutoCompleteTextView android:id="@+id/overview_style_choices" android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="72dp" android:inputType="none" android:textSize="20sp" app:simpleItemLayout="@layout/dropdown_item" />
</com.google.android.material.textfield.TextInputLayout>
<!-- Only styles drawn in the accent colour offer it. -->
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/label_accent" android:textAppearance="@style/Text.EV.Body" android:layout_marginTop="20dp" />
<com.google.android.material.textfield.TextInputLayout android:id="@+id/accent_field" style="@style/Widget.EV.Dropdown" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="12dp">
<com.google.android.material.textfield.MaterialAutoCompleteTextView android:id="@+id/accent_choices" android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="72dp" android:inputType="none" android:textSize="20sp" app:simpleItemLayout="@layout/dropdown_item" />
</com.google.android.material.textfield.TextInputLayout>
<TextView android:id="@+id/accent_note" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="4dp" 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" />
<com.google.android.material.textfield.TextInputLayout style="@style/Widget.EV.Dropdown" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="12dp">
<com.google.android.material.textfield.MaterialAutoCompleteTextView android:id="@+id/tire_unit_choices" android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="72dp" android:inputType="none" android:textSize="20sp" app:simpleItemLayout="@layout/dropdown_item" />
</com.google.android.material.textfield.TextInputLayout>
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/label_accent" android:textAppearance="@style/Text.EV.Body" android:layout_marginTop="20dp" />
<com.google.android.material.textfield.TextInputLayout style="@style/Widget.EV.Dropdown" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="12dp">
<com.google.android.material.textfield.MaterialAutoCompleteTextView android:id="@+id/accent_choices" android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="72dp" android:inputType="none" android:textSize="20sp" app:simpleItemLayout="@layout/dropdown_item" />
</com.google.android.material.textfield.TextInputLayout>
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/label_overview_style" android:textAppearance="@style/Text.EV.Body" android:layout_marginTop="8dp" />
<com.google.android.material.textfield.TextInputLayout style="@style/Widget.EV.Dropdown" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="4dp">
<com.google.android.material.textfield.MaterialAutoCompleteTextView android:id="@+id/overview_style_choices" android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="72dp" android:inputType="none" android:textSize="20sp" app:simpleItemLayout="@layout/dropdown_item" />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="24dp" android:background="@drawable/bg_glass" android:layout_marginTop="16dp">
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/design_battery_settings_title" android:textAppearance="@style/Text.EV.Eyebrow" />
+1
View File
@@ -131,6 +131,7 @@
<string name="overview_style_cartoon">Cartoon</string>
<string name="overview_style_blueprint">Blueprint</string>
<string name="overview_style_mg">MG</string>
<string name="accent_not_used">%1$s has its own colours. To change the accent colour, pick a style that uses it.</string>
<string name="overview_fullscreen_enter">Overview full screen: hide the navigation</string>
<string name="overview_fullscreen_exit">Show the navigation again</string>
<string name="overview_style_minimal">Minimal</string>
@@ -0,0 +1,35 @@
package com.evsuite.abrp;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
/** The Overview styles — pure JVM. */
public class OverviewStyleTest {
@Test
public void storedValuesRoundTrip() {
for (OverviewStyle style : OverviewStyle.values()) {
assertEquals(style, OverviewStyle.fromPref(style.name()));
}
}
@Test
public void oldAndUnknownValues() {
assertEquals(OverviewStyle.GAUGES, OverviewStyle.fromPref("DASHBOARD"));
assertEquals(OverviewStyle.TILES, OverviewStyle.fromPref(null));
assertEquals(OverviewStyle.TILES, OverviewStyle.fromPref("NOPE"));
}
@Test
public void onlyStylesDrawnInTheAccentOfferIt() {
assertTrue(OverviewStyle.TILES.usesAccent);
assertTrue(OverviewStyle.RETRO_ACCENT.usesAccent);
assertTrue(OverviewStyle.MG.usesAccent);
assertFalse(OverviewStyle.RETRO.usesAccent);
assertFalse(OverviewStyle.CARTOON.usesAccent);
assertFalse(OverviewStyle.NIXIE.usesAccent);
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 370 KiB