App 2.21.0: Overview style Dashboard, an instrument cluster

Power, speed and battery as gauges and a strip of gear, climate,
clock, usage and battery; Settings picks Tiles or Dashboard.

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 12:21:58 +02:00
co-authored by Claude Opus 5.5
parent 7b557e8372
commit 234a7e1b60
10 changed files with 383 additions and 5 deletions
@@ -66,6 +66,7 @@ public class MainActivity extends AppCompatActivity {
private View serverPane;
private View logPane;
private View vehiclePane;
private View dashboardPane;
private View consumptionPane;
private View chargingPane;
/** Display, battery, charging price and Wi-Fi. */
@@ -242,15 +243,24 @@ public class MainActivity extends AppCompatActivity {
servicePane = inflatePane(R.layout.pane_service);
serverPane = inflatePane(R.layout.pane_server);
vehiclePane = inflatePane(R.layout.pane_vehicle);
dashboardPane = inflatePane(R.layout.pane_dashboard);
// The Overview page holds both styles; Settings → Overview style picks the one shown.
android.widget.FrameLayout overviewPage = new android.widget.FrameLayout(this);
overviewPage.setLayoutParams(new android.view.ViewGroup.LayoutParams(
android.view.ViewGroup.LayoutParams.MATCH_PARENT,
android.view.ViewGroup.LayoutParams.MATCH_PARENT));
overviewPage.addView(vehiclePane);
overviewPage.addView(dashboardPane);
consumptionPane = inflatePane(R.layout.pane_consumption);
chargingPane = inflatePane(R.layout.pane_charging);
settingsPane = inflatePane(R.layout.pane_settings);
logPane = inflatePane(R.layout.pane_log);
panes = new View[] { vehiclePane, consumptionPane, chargingPane, servicePane, serverPane,
panes = new View[] { overviewPage, consumptionPane, chargingPane, servicePane, serverPane,
settingsPane, logPane };
UpdateLog.noteVersion(this, BuildConfig.VERSION_NAME);
bindVehiclePane();
bindDashboard();
bindConsumptionPane();
bindChargingPane();
bindServerPane();
@@ -1053,6 +1063,7 @@ public class MainActivity extends AppCompatActivity {
voiceKeySwitch.setOnCheckedChangeListener((btn, checked) ->
prefs.edit().putBoolean(UploadSettings.KEY_VOICE_KEY_OPENS, checked).apply());
bindTireUnit();
bindOverviewStyle();
bindAccentColor();
fullScreenSwitch = settingsPane.findViewById(R.id.full_screen_switch);
fullScreenSwitch.setChecked(UploadSettings.fullScreen(prefs));
@@ -1292,9 +1303,12 @@ public class MainActivity extends AppCompatActivity {
showEstimate(vehRangeTrip, vehRangeTripNote, vehUsageTrip, vehUsageTripNote, trip,
trip == null ? "" : getString(R.string.usage_over_distance, trip.distanceKm),
R.string.range_trip_waiting);
RangeEstimator.Estimate average = estimator.average(remaining, monotonicMs);
showEstimate(vehRangeAverage, vehRangeAverageNote, vehUsageAverage, vehUsageAverageNote,
estimator.average(remaining, monotonicMs), getString(R.string.usage_last_5_min),
R.string.range_waiting);
average, getString(R.string.usage_last_5_min), R.string.range_waiting);
dashTripKm = trip == null ? null : trip.distanceKm;
if (dashUsage != null) dashUsage.setText(average == null ? "—"
: String.format(java.util.Locale.US, "%.1f kWh/100 km", average.kwhPer100Km));
}
private void showEstimate(TextView range, TextView rangeNote, TextView usage, TextView usageNote,
@@ -1404,6 +1418,73 @@ public class MainActivity extends AppCompatActivity {
}
}
// ---------- Dashboard (Overview style) ----------
static final String PREF_OVERVIEW_STYLE = "overview_style";
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;
private boolean dashboardStyle() {
return "DASHBOARD".equals(prefs.getString(PREF_OVERVIEW_STYLE, "TILES"));
}
private void applyOverviewStyle() {
boolean dash = dashboardStyle();
dashboardPane.setVisibility(dash ? View.VISIBLE : View.GONE);
vehiclePane.setVisibility(dash ? View.GONE : View.VISIBLE);
}
/** Tiles (the cards) or Dashboard (gauges); both are fed on every reading. */
private void bindOverviewStyle() {
String[] labels = { getString(R.string.overview_style_tiles), getString(R.string.overview_style_dashboard) };
bindChoices(settingsPane.findViewById(R.id.overview_style_choices), labels, dashboardStyle() ? 1 : 0,
position -> {
prefs.edit().putString(PREF_OVERVIEW_STYLE, position == 1 ? "DASHBOARD" : "TILES").apply();
applyOverviewStyle();
});
}
private void bindDashboard() {
dashPower = dashboardPane.findViewById(R.id.dash_power);
dashSpeed = dashboardPane.findViewById(R.id.dash_speed);
dashBattery = dashboardPane.findViewById(R.id.dash_battery);
dashBatteryBar = dashboardPane.findViewById(R.id.dash_battery_bar);
dashGear = dashboardPane.findViewById(R.id.dash_gear);
dashClimate = dashboardPane.findViewById(R.id.dash_climate);
dashUsage = dashboardPane.findViewById(R.id.dash_usage);
dashBatteryText = dashboardPane.findViewById(R.id.dash_battery_text);
dashPower.configure(-50f, 150f, 50f, 5, getString(R.string.dash_unit_kw));
dashSpeed.configure(0f, 180f, 20f, 4, getString(R.string.dash_unit_kmh));
dashBattery.configure(0f, 100f, 25f, 5, getString(R.string.dash_unit_battery));
dashBattery.setEndLabels("E", "F");
dashBattery.setWarnBelow(20f);
applyOverviewStyle();
}
/** The gauges and the strip; every value null-safe ("—", no needle). */
private void showDashboard(EnergySnapshot s) {
if (dashPower == null) return;
Float power = s.getBatteryPowerKw();
dashPower.setValue(power, power == null ? null : String.format(java.util.Locale.US, "%.1f", power), null);
Float speed = s.getSpeedKmh();
String total = getString(R.string.dash_total, fmt(s.getOdometerKm(), "%,.0f km"));
String trip = getString(R.string.dash_trip, dashTripKm == null ? "—"
: String.format(java.util.Locale.US, "%.1f km", dashTripKm));
dashSpeed.setValue(speed, speed == null ? null : String.format(java.util.Locale.US, "%.0f", speed),
total + "\n" + trip);
Float soc = s.getSocPercent();
dashBattery.setValue(soc, soc == null ? null : String.format(java.util.Locale.US, "%.0f %%", soc),
fmt(s.getRangeKm(), "%.0f km"));
dashBatteryBar.setSoc(soc);
dashBatteryText.setText(fmt(soc, "%.0f %%"));
Boolean parked = s.getParked();
dashGear.setText(parked == null ? "—" : parked ? "P" : "D");
dashClimate.setText(fmt(s.getClimate().getDriverTargetCelsius(), "%.0f °C"));
}
private void showVehicle(EnergySnapshot s) {
try {
vehSoc.setText(fmt(s.getSocPercent(), "%.0f %%"));
@@ -1422,6 +1503,7 @@ public class MainActivity extends AppCompatActivity {
vehTireFr.setText(unit.format(s.getTirePressures().getFrontRightKpa()));
vehTireRl.setText(unit.format(s.getTirePressures().getRearLeftKpa()));
vehTireRr.setText(unit.format(s.getTirePressures().getRearRightKpa()));
showDashboard(s);
} catch (Throwable ignored) {
// Leave the last good reading on screen.
}