App 2.31.0: full screen from the navigation, swipe in from the left to return

The Overview's full-screen button moved from the page's corner into the
navigation, beside the MG4 logo. A swipe in from the left edge (EdgeSwipe,
unit-tested) brings the navigation back; a faint handle marks the edge.
Sideways swipes no longer change the page: only the navigation does.

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-25 08:53:41 +01:00
co-authored by Claude Opus 5.5
parent 4a1f178b5c
commit 4cd5f7a7ba
13 changed files with 179 additions and 34 deletions
+1 -1
View File
@@ -69,7 +69,7 @@ Repository: `ssh://git@git.stinnesbeck.com:2222/nils/mg4.git` (remote `origin`),
| Tab | Contents | | Tab | Contents |
|---|---| |---|---|
| Overview | value tiles (battery, range, range/usage/energy since start, 5-min averages of range, usage, power, speed), vehicle info (odometer, lease allowance and the odometer against it), tyre pressures; read every 0.2 s on a background thread. Styles (Settings → Overview style): Tiles, Gauges (`GaugeView` needles), Cockpit (`RingView` rings, `TraceView` power trace), Retro (`RetroView`, seven-segment VFD), Futuristic (`FlowView`, animated energy flow), Sci-fi (`SciFiView`, starship console), Cartoon (`CartoonView`), Blueprint (`BlueprintView`, top-view drawing with tyre pressures), MG (`OctagonView`, octagonal dials after the classic MG dashboards), and on `ClusterCanvasView`: Minimal, Terminal, Aviation, Nixie tubes, Chronograph, Mascot (`MascotView`, an anime co-pilot whose face follows `MascotMood`; classic, `ChibiFigure` or `FoxFigure` on `ChibiBase`, chosen by `MascotCharacter`); the drawn styles share `ClusterValues`. A button in the top-right corner hides the app's navigation for a full-width Overview (`PREF_OVERVIEW_FULLSCREEN`) | | Overview | value tiles (battery, range, range/usage/energy since start, 5-min averages of range, usage, power, speed), vehicle info (odometer, lease allowance and the odometer against it), tyre pressures; read every 0.2 s on a background thread. Styles (Settings → Overview style): Tiles, Gauges (`GaugeView` needles), Cockpit (`RingView` rings, `TraceView` power trace), Retro (`RetroView`, seven-segment VFD), Futuristic (`FlowView`, animated energy flow), Sci-fi (`SciFiView`, starship console), Cartoon (`CartoonView`), Blueprint (`BlueprintView`, top-view drawing with tyre pressures), MG (`OctagonView`, octagonal dials after the classic MG dashboards), and on `ClusterCanvasView`: Minimal, Terminal, Aviation, Nixie tubes, Chronograph, Mascot (`MascotView`, an anime co-pilot whose face follows `MascotMood`; classic, `ChibiFigure` or `FoxFigure` on `ChibiBase`, chosen by `MascotCharacter`); the drawn styles share `ClusterValues`. A button in the navigation's header hides the navigation for a full-width Overview (`PREF_OVERVIEW_FULLSCREEN`); a swipe in from the left edge (`EdgeSwipe`) brings it back. Pages change only by the navigation (the pager's own swiping is off) |
| Consumption / Charging | period totals, daily log, charging sessions and curve | | Consumption / Charging | period totals, daily log, charging sessions and curve |
| Live data | on/off, ABRP API key and user token, upload frequency | | 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 | | Server | URL, pairing by QR, ingest token, installation ID (read-only), config import |
+1 -1
View File
@@ -79,7 +79,7 @@ with the server using a token of its own, obtained by pairing with a phone (belo
| Tab | What it shows | | Tab | What it shows |
|---|---| |---|---|
| **Overview** | Battery, range, power and speed; range, usage and energy since start; 5-minute averages of range, usage, power and speed; vehicle info (odometer, climate setpoint) and tyre pressures. Read five times a second. Looks to choose from in Settings → Overview style: Tiles, Gauges, Cockpit, Retro, Futuristic, Sci-fi, Cartoon, Blueprint, MG, Minimal, Terminal, Aviation, Nixie tubes, Chronograph and Mascot (an anime co-pilot who reacts to the drive, classic, chibi or a chibi fox). The small button in the top-right corner hides the app's navigation for a full-width Overview; tap it again to bring it back | | **Overview** | Battery, range, power and speed; range, usage and energy since start; 5-minute averages of range, usage, power and speed; vehicle info (odometer, climate setpoint) and tyre pressures. Read five times a second. Looks to choose from in Settings → Overview style: Tiles, Gauges, Cockpit, Retro, Futuristic, Sci-fi, Cartoon, Blueprint, MG, Minimal, Terminal, Aviation, Nixie tubes, Chronograph and Mascot (an anime co-pilot who reacts to the drive, classic, chibi or a chibi fox). The small button next to the MG4 logo in the navigation hides the navigation for a full-width Overview; swipe in from the left edge (a faint handle marks it) to bring it back. Pages change only by the navigation, not by swiping sideways |
| **Consumption** | Lifetime, last 7 and 30 days, Trip A and B (distance, energy, average consumption and speed, SOC change), and a daily log of the last four months | | **Consumption** | Lifetime, last 7 and 30 days, Trip A and B (distance, energy, average consumption and speed, SOC change), and a daily log of the last four months |
| **Charging** | The current session (SOC, voltage, current, power, energy into the battery and from the grid, time, cost) and its SOC/power curve | | **Charging** | The current session (SOC, voltage, current, power, energy into the battery and from the grid, time, cost) and its SOC/power curve |
| **Live data** | Live data on/off, ABRP credentials for sending directly from the car, upload frequency | | **Live data** | Live data on/off, ABRP credentials for sending directly from the car, upload frequency |
+10
View File
@@ -131,6 +131,16 @@ 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-25 — App 2.31.0 (branch `styles-retro-future`): the Overview's full screen moved into
the navigation: its button now sits beside the MG4 logo (at the end of the bottom bar on narrow
screens) instead of over the page's top-right corner, and only shows on the Overview. A swipe
in from the left edge brings the navigation back (`EdgeSwipe`, pure and unit-tested: starts
within 48 dp of the edge, 64 dp to the right, more sideways than up or down; the page under it
gets a cancel). A faint handle at the left edge marks it in full screen. Swiping sideways no
longer changes the page (`ViewPager2.setUserInputEnabled(false)`); the navigation does. The
exit icon and its string went. Checked on the emulator: the button enters full screen, a
swipe in the middle does nothing, a swipe from the edge brings the navigation back, sideways
swipes keep the page, the narrow layout opens. Not tried on the car.
- 2026-10-24 — App 2.30.0 (branch `styles-retro-future`): a third Mascot, the **chibi fox** - 2026-10-24 — App 2.30.0 (branch `styles-retro-future`): a third Mascot, the **chibi fox**
(`FoxFigure`): a sitting fox with a big head, pointed ears, cream cheeks, chest and tail tip, (`FoxFigure`): a sitting fox with a big head, pointed ears, cream cheeks, chest and tail tip,
dark paws and a scarf in the accent colour. Ears and tail act the mood with the face: the dark paws and a scarf in the accent colour. Ears and tail act the mood with the face: the
+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 127 versionCode 128
versionName "2.30.0" versionName "2.31.0"
// 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.
@@ -0,0 +1,44 @@
package com.evsuite.abrp;
/**
* The swipe in from the left edge that brings the navigation back in the Overview's full
* screen: a touch that starts within {@code edge} of the left side and travels at least
* {@code travel} to the right, more sideways than up or down. Distances in pixels.
* Pure Java, unit-tested.
*/
final class EdgeSwipe {
private final float edge, travel;
private boolean tracking;
private float startX, startY;
EdgeSwipe(float edge, float travel) {
this.edge = edge;
this.travel = travel;
}
/** A finger went down: only one near the left edge can become the swipe. */
void down(float x, float y) {
tracking = x <= edge;
startX = x;
startY = y;
}
/** The finger moved; true once, when this touch has become the swipe. */
boolean move(float x, float y) {
if (!tracking) return false;
float dx = x - startX, dy = Math.abs(y - startY);
if (dx >= travel && dx > dy) {
tracking = false;
return true;
}
// Mostly up or down already: a scroll, not this swipe.
if (dy > travel) tracking = false;
return false;
}
/** The finger lifted or the touch was cancelled. */
void up() {
tracking = false;
}
}
@@ -183,6 +183,9 @@ public class MainActivity extends AppCompatActivity {
// Keep all pages alive: the log and vehicle readings are refreshed // Keep all pages alive: the log and vehicle readings are refreshed
// by the tick even when their page is off screen. // by the tick even when their page is off screen.
pager.setOffscreenPageLimit(panes.length - 1); pager.setOffscreenPageLimit(panes.length - 1);
// Pages change only by the navigation: a sideways swipe on a page (a slider, a chart,
// the swipe that brings the navigation back) must not turn it.
pager.setUserInputEnabled(false);
pager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() { pager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
@Override public void onPageSelected(int position) { @Override public void onPageSelected(int position) {
markCurrentPage(position); markCurrentPage(position);
@@ -196,30 +199,72 @@ public class MainActivity extends AppCompatActivity {
for (int i = 0; i < TAB_IDS.length; i++) { for (int i = 0; i < TAB_IDS.length; i++) {
final int index = i; final int index = i;
// Animated, so the button does the same thing the swipe does: the direction of
// travel is what tells the driver where they are in the row.
findViewById(TAB_IDS[i]).setOnClickListener(v -> pager.setCurrentItem(index, false)); findViewById(TAB_IDS[i]).setOnClickListener(v -> pager.setCurrentItem(index, false));
} }
markCurrentPage(pager.getCurrentItem()); markCurrentPage(pager.getCurrentItem());
applyOverviewFullScreen(pager.getCurrentItem()); applyOverviewFullScreen(pager.getCurrentItem());
} }
/** Whether the Overview hides the navigation (the button in its corner). */ /** Whether the Overview hides the navigation (the button in the navigation's header). */
static final String PREF_OVERVIEW_FULLSCREEN = "overview_fullscreen"; static final String PREF_OVERVIEW_FULLSCREEN = "overview_fullscreen";
/** True while the Overview is full screen: the navigation hidden, the edge swipe armed. */
private boolean overviewFull;
/** /**
* The Overview full screen: the navigation hidden while the Overview is the page and the * The Overview full screen: the navigation hidden while the Overview is the page and the
* driver chose it; every other page always has its navigation, and swiping still works. * driver chose it, with a handle at the left edge where a swipe brings it back. Every
* The button only shows on the Overview, its icon saying what a tap does. * other page always has its navigation. The button sits in the navigation and only shows
* on the Overview (invisible elsewhere, so the header keeps its layout).
*/ */
private void applyOverviewFullScreen(int position) { private void applyOverviewFullScreen(int position) {
boolean overview = position == 0; boolean overview = position == 0;
boolean full = overview && prefs.getBoolean(PREF_OVERVIEW_FULLSCREEN, false); overviewFull = overview && prefs.getBoolean(PREF_OVERVIEW_FULLSCREEN, false);
findViewById(R.id.nav_bar).setVisibility(full ? View.GONE : View.VISIBLE); findViewById(R.id.nav_bar).setVisibility(overviewFull ? View.GONE : View.VISIBLE);
android.widget.ImageButton button = findViewById(R.id.overview_fullscreen); findViewById(R.id.edge_handle).setVisibility(overviewFull ? View.VISIBLE : View.GONE);
button.setVisibility(overview ? View.VISIBLE : View.GONE); findViewById(R.id.overview_fullscreen).setVisibility(overview ? View.VISIBLE : View.INVISIBLE);
button.setImageResource(full ? R.drawable.ic_fullscreen_exit : R.drawable.ic_fullscreen); }
button.setContentDescription(getString(full ? R.string.overview_fullscreen_exit : R.string.overview_fullscreen_enter));
/** The swipe in from the left edge, set up with this screen's density. */
private EdgeSwipe edgeSwipe;
/** The rest of a touch that became the edge swipe: kept from the page underneath. */
private boolean swallowTouch;
/**
* Watches every touch in the Overview's full screen for the swipe in from the left edge,
* which brings the navigation back. The page still gets the touch until it becomes the
* swipe; then it gets a cancel and nothing more of that touch.
*/
@Override
public boolean dispatchTouchEvent(android.view.MotionEvent ev) {
int action = ev.getActionMasked();
if (swallowTouch) {
if (action == android.view.MotionEvent.ACTION_UP
|| action == android.view.MotionEvent.ACTION_CANCEL) swallowTouch = false;
return true;
}
if (overviewFull) {
if (edgeSwipe == null) {
float density = getResources().getDisplayMetrics().density;
edgeSwipe = new EdgeSwipe(48 * density, 64 * density);
}
if (action == android.view.MotionEvent.ACTION_DOWN) {
edgeSwipe.down(ev.getX(), ev.getY());
} else if (action == android.view.MotionEvent.ACTION_MOVE && edgeSwipe.move(ev.getX(), ev.getY())) {
android.view.MotionEvent cancel = android.view.MotionEvent.obtain(ev);
cancel.setAction(android.view.MotionEvent.ACTION_CANCEL);
super.dispatchTouchEvent(cancel);
cancel.recycle();
swallowTouch = true;
prefs.edit().putBoolean(PREF_OVERVIEW_FULLSCREEN, false).apply();
applyOverviewFullScreen(((ViewPager2) findViewById(R.id.content)).getCurrentItem());
return true;
} else if (action == android.view.MotionEvent.ACTION_UP
|| action == android.view.MotionEvent.ACTION_CANCEL) {
edgeSwipe.up();
}
}
return super.dispatchTouchEvent(ev);
} }
/** /**
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- The handle at the left edge in full screen: where the navigation swipes back in. -->
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<solid android:color="@color/ev_text_secondary" />
<corners android:radius="3dp" />
</shape>
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- The full-screen button: a small dark disc inside its 72 dp touch area. --> <!-- The full-screen button in the navigation: a small dark disc inside its touch area. -->
<inset xmlns:android="http://schemas.android.com/apk/res/android" android:inset="16dp"> <inset xmlns:android="http://schemas.android.com/apk/res/android" android:inset="16dp">
<shape android:shape="oval"> <shape android:shape="oval">
<solid android:color="#99121212" /> <solid android:color="#99121212" />
@@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Shrink, for the Overview's full-screen button (brings the navigation back). -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@color/ev_text_primary"
android:pathData="M5,16h3v3h2v-5H5v2zM8,8H5v2h5V5H8v3zM14,19h2v-3h3v-2h-5v5zM16,8V5h-2v5h5V8h-3z" />
</vector>
@@ -4,8 +4,14 @@
<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal"> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal">
<ScrollView android:id="@+id/nav_bar" android:layout_width="192dp" android:layout_height="match_parent" android:layout_margin="16dp" android:fillViewport="true" android:background="@drawable/bg_sidebar" android:scrollbars="none"> <ScrollView android:id="@+id/nav_bar" android:layout_width="192dp" android:layout_height="match_parent" android:layout_margin="16dp" android:fillViewport="true" android:background="@drawable/bg_sidebar" android:scrollbars="none">
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="12dp"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="12dp">
<!-- The brand, and beside it the button that hides this panel on the Overview. -->
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:gravity="center_vertical" android:layout_marginBottom="24dp">
<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/design_brand" android:textAppearance="@style/Text.EV.Hero" android:textColor="?attr/evAccent" android:paddingStart="14dp" android:paddingTop="8dp" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/design_brand" android:textAppearance="@style/Text.EV.Hero" android:textColor="?attr/evAccent" android:paddingStart="14dp" android:paddingTop="8dp" />
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/design_brand_caption" android:textAppearance="@style/Text.EV.Secondary" android:paddingStart="14dp" android:layout_marginBottom="24dp" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/design_brand_caption" android:textAppearance="@style/Text.EV.Secondary" android:paddingStart="14dp" />
</LinearLayout>
<ImageButton android:id="@+id/overview_fullscreen" android:layout_width="64dp" android:layout_height="72dp" android:background="@drawable/bg_fullscreen_button" android:src="@drawable/ic_fullscreen" android:padding="22dp" android:scaleType="fitCenter" android:contentDescription="@string/overview_fullscreen_enter" />
</LinearLayout>
<com.google.android.material.button.MaterialButton android:id="@+id/tabVehicle" style="@style/Widget.EV.NavTab" android:layout_width="match_parent" android:layout_height="64dp" android:layout_marginBottom="6dp" android:text="@string/tab_vehicle" app:icon="@drawable/ic_tab_vehicle" /> <com.google.android.material.button.MaterialButton android:id="@+id/tabVehicle" style="@style/Widget.EV.NavTab" android:layout_width="match_parent" android:layout_height="64dp" android:layout_marginBottom="6dp" android:text="@string/tab_vehicle" app:icon="@drawable/ic_tab_vehicle" />
<com.google.android.material.button.MaterialButton android:id="@+id/tabConsumption" style="@style/Widget.EV.NavTab" android:layout_width="match_parent" android:layout_height="64dp" android:layout_marginBottom="6dp" android:text="@string/tab_consumption" app:icon="@drawable/ic_tab_vehicle" /> <com.google.android.material.button.MaterialButton android:id="@+id/tabConsumption" style="@style/Widget.EV.NavTab" android:layout_width="match_parent" android:layout_height="64dp" android:layout_marginBottom="6dp" android:text="@string/tab_consumption" app:icon="@drawable/ic_tab_vehicle" />
<com.google.android.material.button.MaterialButton android:id="@+id/tabCharging" style="@style/Widget.EV.NavTab" android:layout_width="match_parent" android:layout_height="64dp" android:layout_marginBottom="6dp" android:text="@string/tab_charging" app:icon="@drawable/ic_tab_charging" /> <com.google.android.material.button.MaterialButton android:id="@+id/tabCharging" style="@style/Widget.EV.NavTab" android:layout_width="match_parent" android:layout_height="64dp" android:layout_marginBottom="6dp" android:text="@string/tab_charging" app:icon="@drawable/ic_tab_charging" />
@@ -16,10 +22,10 @@
</LinearLayout> </LinearLayout>
</ScrollView> </ScrollView>
<LinearLayout android:layout_width="0dp" android:layout_weight="1" android:layout_height="match_parent" android:orientation="vertical"> <LinearLayout android:layout_width="0dp" android:layout_weight="1" android:layout_height="match_parent" android:orientation="vertical">
<!-- The pages, and over them on the Overview the button that hides the navigation. --> <!-- The pages, and over them in full screen the handle that says "swipe in from here". -->
<FrameLayout android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1"> <FrameLayout android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1">
<androidx.viewpager2.widget.ViewPager2 android:id="@+id/content" android:layout_width="match_parent" android:layout_height="match_parent" /> <androidx.viewpager2.widget.ViewPager2 android:id="@+id/content" android:layout_width="match_parent" android:layout_height="match_parent" />
<ImageButton android:id="@+id/overview_fullscreen" android:layout_width="72dp" android:layout_height="72dp" android:layout_gravity="top|end" android:background="@drawable/bg_fullscreen_button" android:src="@drawable/ic_fullscreen" android:padding="24dp" android:scaleType="fitCenter" android:alpha="0.75" android:contentDescription="@string/overview_fullscreen_enter" /> <View android:id="@+id/edge_handle" android:layout_width="5dp" android:layout_height="96dp" android:layout_gravity="start|center_vertical" android:layout_marginStart="4dp" android:background="@drawable/bg_edge_handle" android:alpha="0.35" android:visibility="gone" />
</FrameLayout> </FrameLayout>
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
+3 -2
View File
@@ -2,10 +2,10 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/app_root" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" android:background="@color/ev_background"> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/app_root" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" android:background="@color/ev_background">
<com.evsuite.abrp.PremiumBackdropView android:layout_width="match_parent" android:layout_height="match_parent"/> <com.evsuite.abrp.PremiumBackdropView android:layout_width="match_parent" android:layout_height="match_parent"/>
<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical">
<!-- The pages, and over them on the Overview the button that hides the navigation. --> <!-- The pages, and over them in full screen the handle that says "swipe in from here". -->
<FrameLayout android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1"> <FrameLayout android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1">
<androidx.viewpager2.widget.ViewPager2 android:id="@+id/content" android:layout_width="match_parent" android:layout_height="match_parent"/> <androidx.viewpager2.widget.ViewPager2 android:id="@+id/content" android:layout_width="match_parent" android:layout_height="match_parent"/>
<ImageButton android:id="@+id/overview_fullscreen" android:layout_width="72dp" android:layout_height="72dp" android:layout_gravity="top|end" android:background="@drawable/bg_fullscreen_button" android:src="@drawable/ic_fullscreen" android:padding="24dp" android:scaleType="fitCenter" android:alpha="0.75" android:contentDescription="@string/overview_fullscreen_enter" /> <View android:id="@+id/edge_handle" android:layout_width="5dp" android:layout_height="96dp" android:layout_gravity="start|center_vertical" android:layout_marginStart="4dp" android:background="@drawable/bg_edge_handle" android:alpha="0.35" android:visibility="gone"/>
</FrameLayout> </FrameLayout>
<LinearLayout android:id="@+id/nav_bar" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:padding="8dp" android:background="@drawable/bg_sidebar"> <LinearLayout android:id="@+id/nav_bar" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:padding="8dp" android:background="@drawable/bg_sidebar">
<com.google.android.material.button.MaterialButton android:id="@+id/tabVehicle" style="@style/Widget.EV.NavTab.Compact" android:layout_width="0dp" android:layout_height="76dp" android:layout_weight="1" android:text="@string/tab_vehicle" app:icon="@drawable/ic_tab_vehicle"/> <com.google.android.material.button.MaterialButton android:id="@+id/tabVehicle" style="@style/Widget.EV.NavTab.Compact" android:layout_width="0dp" android:layout_height="76dp" android:layout_weight="1" android:text="@string/tab_vehicle" app:icon="@drawable/ic_tab_vehicle"/>
@@ -15,6 +15,7 @@
<com.google.android.material.button.MaterialButton android:id="@+id/tabServer" style="@style/Widget.EV.NavTab.Compact" android:layout_width="0dp" android:layout_height="76dp" android:layout_weight="1" android:text="@string/tab_server" app:icon="@drawable/ic_tab_server"/> <com.google.android.material.button.MaterialButton android:id="@+id/tabServer" style="@style/Widget.EV.NavTab.Compact" android:layout_width="0dp" android:layout_height="76dp" android:layout_weight="1" android:text="@string/tab_server" app:icon="@drawable/ic_tab_server"/>
<com.google.android.material.button.MaterialButton android:id="@+id/tabSettings" style="@style/Widget.EV.NavTab.Compact" android:layout_width="0dp" android:layout_height="76dp" android:layout_weight="1" android:text="@string/tab_settings" app:icon="@drawable/ic_tab_service"/> <com.google.android.material.button.MaterialButton android:id="@+id/tabSettings" style="@style/Widget.EV.NavTab.Compact" android:layout_width="0dp" android:layout_height="76dp" android:layout_weight="1" android:text="@string/tab_settings" app:icon="@drawable/ic_tab_service"/>
<com.google.android.material.button.MaterialButton android:id="@+id/tabLog" style="@style/Widget.EV.NavTab.Compact" android:layout_width="0dp" android:layout_height="76dp" android:layout_weight="1" android:text="@string/tab_log" app:icon="@drawable/ic_tab_log"/> <com.google.android.material.button.MaterialButton android:id="@+id/tabLog" style="@style/Widget.EV.NavTab.Compact" android:layout_width="0dp" android:layout_height="76dp" android:layout_weight="1" android:text="@string/tab_log" app:icon="@drawable/ic_tab_log"/>
<ImageButton android:id="@+id/overview_fullscreen" android:layout_width="64dp" android:layout_height="76dp" android:background="@drawable/bg_fullscreen_button" android:src="@drawable/ic_fullscreen" android:padding="22dp" android:scaleType="fitCenter" android:contentDescription="@string/overview_fullscreen_enter"/>
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
</FrameLayout> </FrameLayout>
-1
View File
@@ -132,7 +132,6 @@
<string name="overview_style_mg">MG</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="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_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> <string name="overview_style_minimal">Minimal</string>
<string name="overview_style_terminal">Terminal</string> <string name="overview_style_terminal">Terminal</string>
<string name="accent_original">Original</string> <string name="accent_original">Original</string>
@@ -0,0 +1,45 @@
package com.evsuite.abrp;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
/** The swipe from the left edge that brings the navigation back — pure JVM. */
public class EdgeSwipeTest {
@Test
public void swipeFromTheEdgeCounts() {
EdgeSwipe swipe = new EdgeSwipe(48f, 80f);
swipe.down(10f, 300f);
assertFalse(swipe.move(50f, 305f));
assertTrue(swipe.move(95f, 310f));
// Only once per touch.
assertFalse(swipe.move(200f, 310f));
}
@Test
public void touchAwayFromTheEdgeIsIgnored() {
EdgeSwipe swipe = new EdgeSwipe(48f, 80f);
swipe.down(200f, 300f);
assertFalse(swipe.move(400f, 300f));
}
@Test
public void verticalScrollIsNotTheSwipe() {
EdgeSwipe swipe = new EdgeSwipe(48f, 80f);
swipe.down(10f, 100f);
assertFalse(swipe.move(30f, 200f));
// Gone sideways afterwards: still the scroll.
assertFalse(swipe.move(150f, 210f));
}
@Test
public void leftwardOrLiftedIsNotTheSwipe() {
EdgeSwipe swipe = new EdgeSwipe(48f, 80f);
swipe.down(40f, 300f);
assertFalse(swipe.move(0f, 300f));
swipe.up();
assertFalse(swipe.move(200f, 300f));
}
}