App 2.19.8: charging curve labels clear of each other; Charging tab 35/65

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:18:32 +02:00
co-authored by Claude Opus 5.5
parent 93e7e4cb63
commit e2922e4842
4 changed files with 43 additions and 11 deletions
+5
View File
@@ -118,6 +118,11 @@ 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 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` 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). (Android 9 AOSP at 1920×720, 160 dpi; the car build installs as the system user — checked).
- 2026-10-23 — App 2.19.8: the charging curve's labels no longer overlap: "kW" has a row of its
own above the plot, the power values are right-aligned against the axis, and the SOC ticks read
"0 %" … "100 %" (the separate "SOC %" sat on top of "100"). The wide Charging tab splits 35/65,
values | curve. Checked on the emulator with sample AC values and a curve in a throwaway build;
the page still fits 1778×640.
- 2026-10-23 — App 2.19.7: AC charging is metered too, while the app runs (owner's choice; Fatih - 2026-10-23 — App 2.19.7: AC charging is metered too, while the app runs (owner's choice; Fatih
had metered DC only, `e9bfa12`, because an overnight AC charge is slept through). A session runs had metered DC only, `e9bfa12`, because an overnight AC charge is slept through). A session runs
whenever power flows into the pack (`ChargingState.isMeteredCharging`: inbound > 0.3 kW and a whenever power flows into the pack (`ChargingState.isMeteredCharging`: inbound > 0.3 kW and a
+2 -2
View File
@@ -34,8 +34,8 @@ android {
applicationId "com.stinnesbeck.nilsmg4" applicationId "com.stinnesbeck.nilsmg4"
minSdk 27 minSdk 27
targetSdk 36 targetSdk 36
versionCode 106 versionCode 107
versionName "2.19.7" versionName "2.19.8"
// Nothing about the server ships in the APK: its URL and ingest token are entered in // 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. // the app's Server tab (ServerConfig), and the database is only reachable by the server.
@@ -41,12 +41,39 @@ public final class ChargingGraphView extends View {
} }
return found ? out.toString().trim() : "No charging measurements yet"; return found ? out.toString().trim() : "No charging measurements yet";
} }
/**
* Axes with their labels clear of each other: "kW" in a row of its own above the plot, the
* power values right-aligned against the y axis, and the SOC ticks as "0 %" … "100 %",
* centred under their lines, the last one kept inside the view.
*/
@Override protected synchronized void onDraw(Canvas c) { @Override protected synchronized void onDraw(Canvas c) {
super.onDraw(c); float left=58, right=getWidth()-12, top=12, bottom=getHeight()-38; super.onDraw(c);
float max=20; for(float[] p:points) max=Math.max(max,p[1]); max=(float)Math.ceil(max/20f)*20f; float left = 58, right = getWidth() - 12, top = 36, bottom = getHeight() - 38;
for(int i=0;i<=5;i++){float y=bottom-(bottom-top)*i/5f; c.drawLine(left,y,right,y,grid); c.drawText(String.format(Locale.US,"%.0f",max*i/5f),4,y+7,text);} float max = 20; for (float[] p : points) max = Math.max(max, p[1]); max = (float) Math.ceil(max / 20f) * 20f;
for(int soc=0;soc<=100;soc+=20){float x=left+(right-left)*soc/100f; c.drawLine(x,top,x,bottom,grid); c.drawText(String.valueOf(soc),x-10,getHeight()-8,text);} text.setTextAlign(Paint.Align.RIGHT);
c.drawText("kW",5,20,text); c.drawText("SOC %",right-52,getHeight()-8,text); for (int i = 0; i <= 5; i++) {
for(int i=1;i<points.size();i++){float[] a=points.get(i-1),b=points.get(i); float ax=left+a[0]*(right-left)/100, ay=bottom-a[1]*(bottom-top)/max, bx=left+b[0]*(right-left)/100, by=bottom-b[1]*(bottom-top)/max; c.drawLine(ax,ay,bx,by,line); c.drawCircle(bx,by,4,line);} float y = bottom - (bottom - top) * i / 5f;
c.drawLine(left, y, right, y, grid);
c.drawText(String.format(Locale.US, "%.0f", max * i / 5f), left - 8, y + 7, text);
}
text.setTextAlign(Paint.Align.LEFT);
c.drawText("kW", 4, 20, text);
for (int soc = 0; soc <= 100; soc += 20) {
float x = left + (right - left) * soc / 100f;
c.drawLine(x, top, x, bottom, grid);
String label = soc + " %";
float half = text.measureText(label) / 2f;
float cx = Math.min(x, getWidth() - 2 - half);
text.setTextAlign(Paint.Align.CENTER);
c.drawText(label, cx, getHeight() - 8, text);
}
text.setTextAlign(Paint.Align.LEFT);
for (int i = 1; i < points.size(); i++) {
float[] a = points.get(i - 1), b = points.get(i);
float ax = left + a[0] * (right - left) / 100, ay = bottom - a[1] * (bottom - top) / max;
float bx = left + b[0] * (right - left) / 100, by = bottom - b[1] * (bottom - top) / max;
c.drawLine(ax, ay, bx, by, line);
c.drawCircle(bx, by, 4, line);
}
} }
} }
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<com.evsuite.abrp.StableScrollView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:fillViewport="true"> <com.evsuite.abrp.StableScrollView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:fillViewport="true">
<!-- Two columns: live values | curve. The table scrolls inside its card so a long session does not push the page. --> <!-- Two columns, 35/65: live values | curve. The table scrolls inside its card so a long session does not push the page. -->
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:paddingStart="24dp" android:paddingEnd="24dp" android:paddingTop="12dp" android:paddingBottom="12dp" android:baselineAligned="false"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:paddingStart="24dp" android:paddingEnd="24dp" android:paddingTop="12dp" android:paddingBottom="12dp" android:baselineAligned="false">
<LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:layout_marginEnd="16dp" android:orientation="vertical" android:padding="20dp" android:background="@drawable/bg_glass"> <LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="35" android:layout_marginEnd="16dp" android:orientation="vertical" android:padding="20dp" android:background="@drawable/bg_glass">
<TextView android:id="@+id/charge_status" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/charging_not_charging" android:textAppearance="@style/Text.EV.Title" android:textColor="?attr/evAccentLight"/> <TextView android:id="@+id/charge_status" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/charging_not_charging" android:textAppearance="@style/Text.EV.Title" android:textColor="?attr/evAccentLight"/>
<GridLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:columnCount="2" android:layout_marginTop="12dp"> <GridLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:columnCount="2" android:layout_marginTop="12dp">
<TextView android:layout_width="0dp" android:layout_columnWeight="1" android:padding="10dp" android:text="@string/charging_soc" android:textAppearance="@style/DataLabel"/><TextView android:id="@+id/charge_soc" android:layout_width="0dp" android:layout_columnWeight="1" android:padding="10dp" android:text="@string/no_value" android:textAppearance="@style/DataValue"/> <TextView android:layout_width="0dp" android:layout_columnWeight="1" android:padding="10dp" android:text="@string/charging_soc" android:textAppearance="@style/DataLabel"/><TextView android:id="@+id/charge_soc" android:layout_width="0dp" android:layout_columnWeight="1" android:padding="10dp" android:text="@string/no_value" android:textAppearance="@style/DataValue"/>
@@ -16,7 +16,7 @@
<TextView android:layout_width="0dp" android:layout_columnWeight="1" android:padding="10dp" android:text="@string/charging_total_cost" android:textAppearance="@style/DataLabel"/><TextView android:id="@+id/charge_total_cost" android:layout_width="0dp" android:layout_columnWeight="1" android:padding="10dp" android:text="@string/no_value" android:textAppearance="@style/DataValue"/> <TextView android:layout_width="0dp" android:layout_columnWeight="1" android:padding="10dp" android:text="@string/charging_total_cost" android:textAppearance="@style/DataLabel"/><TextView android:id="@+id/charge_total_cost" android:layout_width="0dp" android:layout_columnWeight="1" android:padding="10dp" android:text="@string/no_value" android:textAppearance="@style/DataValue"/>
</GridLayout> </GridLayout>
</LinearLayout> </LinearLayout>
<LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:orientation="vertical" android:padding="20dp" android:background="@drawable/bg_glass"> <LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="65" android:orientation="vertical" android:padding="20dp" android:background="@drawable/bg_glass">
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/charging_curve_title" android:textAppearance="@style/Text.EV.Eyebrow"/> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/charging_curve_title" android:textAppearance="@style/Text.EV.Eyebrow"/>
<com.evsuite.abrp.ChargingGraphView android:id="@+id/charge_graph" android:layout_width="match_parent" android:layout_height="250dp" android:layout_marginTop="12dp"/> <com.evsuite.abrp.ChargingGraphView android:id="@+id/charge_graph" android:layout_width="match_parent" android:layout_height="250dp" android:layout_marginTop="12dp"/>
<androidx.core.widget.NestedScrollView android:layout_width="match_parent" android:layout_height="170dp" android:layout_marginTop="16dp" android:scrollbars="none"> <androidx.core.widget.NestedScrollView android:layout_width="match_parent" android:layout_height="170dp" android:layout_marginTop="16dp" android:scrollbars="none">