App 2.20.0: lease mileage allowance on the Overview

Vehicle info shows the km allowed by today (40,000 km over four years
from 2023-05-01, evenly per day) and the odometer against it.

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-23 22:24:52 +02:00
co-authored by Claude Opus 5.5
parent 71aceaa626
commit 2931b1feed
9 changed files with 118 additions and 5 deletions
@@ -0,0 +1,31 @@
package com.evsuite.abrp;
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
/**
* The lease's mileage allowance: 40,000 km over four years from 1 May 2023, accruing evenly per
* day. The Overview's Vehicle info card shows what is allowed by today and how far the odometer
* is above or below it. Pure Java, so it is unit-tested.
*/
final class LeaseAllowance {
static final LocalDate START = LocalDate.of(2023, 5, 1);
static final LocalDate END = START.plusYears(4);
static final double TOTAL_KM = 40_000d;
private LeaseAllowance() {}
/** Kilometres allowed by {@code today}: none before the start, all of them from the end on. */
static double allowedKm(LocalDate today) {
long total = ChronoUnit.DAYS.between(START, END);
long elapsed = Math.max(0L, Math.min(total, ChronoUnit.DAYS.between(START, today)));
return TOTAL_KM * elapsed / total;
}
/** Odometer minus allowance: positive over, negative under; null when the odometer is unread. */
static Double differenceKm(Float odometerKm, LocalDate today) {
if (odometerKm == null || !Float.isFinite(odometerKm)) return null;
return odometerKm - allowedKm(today);
}
}
@@ -85,7 +85,7 @@ public class MainActivity extends AppCompatActivity {
/** Used only on the overview-read thread. */
private EnergyTelemetryReader vehicleReader;
private TextView vehSoc, vehRange, vehPower, vehSpeed, vehOdometer,
vehCharging, vehHvac,
vehCharging, vehHvac, vehLeaseAllowance, vehLeaseDifference,
vehTireFl, vehTireFr, vehTireRl, vehTireRr,
vehRangeTrip, vehRangeTripNote, vehRangeAverage, vehRangeAverageNote,
vehUsageTrip, vehUsageTripNote, vehUsageAverage, vehUsageAverageNote,
@@ -1243,6 +1243,8 @@ public class MainActivity extends AppCompatActivity {
vehOdometer = vehiclePane.findViewById(R.id.vehicle_odometer);
vehCharging = vehiclePane.findViewById(R.id.vehicle_charging);
vehHvac = vehiclePane.findViewById(R.id.vehicle_hvac);
vehLeaseAllowance = vehiclePane.findViewById(R.id.vehicle_lease_allowance);
vehLeaseDifference = vehiclePane.findViewById(R.id.vehicle_lease_difference);
vehTireFl = vehiclePane.findViewById(R.id.vehicle_tire_fl);
vehTireFr = vehiclePane.findViewById(R.id.vehicle_tire_fr);
vehTireRl = vehiclePane.findViewById(R.id.vehicle_tire_rl);
@@ -1386,13 +1388,30 @@ public class MainActivity extends AppCompatActivity {
}
}
/** The lease allowance by today and the odometer against it (LeaseAllowance). */
private void showLease(Float odometerKm) {
java.time.LocalDate today = java.time.LocalDate.now();
// Locale.US like fmt(), so the card's numbers share one format.
vehLeaseAllowance.setText(String.format(java.util.Locale.US, "%,.0f km",
LeaseAllowance.allowedKm(today)));
Double diff = LeaseAllowance.differenceKm(odometerKm, today);
if (diff == null) {
vehLeaseDifference.setText(R.string.no_value);
} else {
String km = String.format(java.util.Locale.US, "%,.0f", Math.abs(diff));
vehLeaseDifference.setText(Math.round(diff) == 0 ? getString(R.string.lease_on_target)
: getString(diff > 0 ? R.string.lease_over : R.string.lease_under, km));
}
}
private void showVehicle(EnergySnapshot s) {
try {
vehSoc.setText(fmt(s.getSocPercent(), "%.0f %%"));
vehRange.setText(fmt(s.getRangeKm(), "%.0f km"));
vehPower.setText(fmt(s.getBatteryPowerKw(), "%.1f kW"));
vehSpeed.setText(fmt(s.getSpeedKmh(), "%.0f km/h"));
vehOdometer.setText(fmt(s.getOdometerKm(), "%.0f km"));
vehOdometer.setText(fmt(s.getOdometerKm(), "%,.0f km"));
showLease(s.getOdometerKm());
vehHvac.setText(fmt(s.getClimate().getDriverTargetCelsius(), "%.0f °C"));
vehCharging.setText(chargingLabel(s));