App 2.7.0: voice-key double press opens the app; no scroll jumps
Steering wheel: a double press of the voice key (code 287) opens the app. The floating button from 2.6.0 does not show over the MG4 launcher, so this is the way back into the app. The head unit reports keys as the broadcast com.saic.keyevent.hardkey.report, whose extras are not documented: the receiver tries the usual names (and an embedded KeyEvent), and counts one press per report whichever way the up/down field turns out to work; long presses never open the app. The last 20 reports are listed raw on the Logs tab, so if the guess misses, a photo of that list gives the real format. A switch in the Upload tab turns it off. DoublePressDetector (500 ms window) has 5 tests; on the emulator, simulated double presses open the app and single presses, other keys and long presses do not. Scrolling: pages jumped back to the top on every 2 s refresh. A stack trace on the emulator showed why: ViewPager2's RecyclerView lays the pages out while measuring, at a provisional height, and ScrollView.onLayout clamps the position to 0. StableScrollView keeps the driver's position across layouts and is now the root of every page; the scrolled Charging page stays put across refreshes. Vehicle tab: all six value tiles, including both range estimates, sit in one row at the top, so nothing needs scrolling at 1920x720. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QLJfZQZeAKAwSqViTmLzki
This commit is contained in:
+2
-2
@@ -34,8 +34,8 @@ android {
|
||||
applicationId "com.stinnesbeck.nilsmg4"
|
||||
minSdk 27
|
||||
targetSdk 36
|
||||
versionCode 63
|
||||
versionName "2.6.0"
|
||||
versionCode 64
|
||||
versionName "2.7.0"
|
||||
|
||||
// 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,6 +68,7 @@ public class AbrpUploadService extends Service {
|
||||
private ChargeSessionTracker chargeSessionTracker;
|
||||
private TelemetryUploader telemetryUploader;
|
||||
private FloatingButton floatingButton;
|
||||
private SteeringKeyListener steeringKeys;
|
||||
private WifiAutoConnect wifi;
|
||||
private ScheduledExecutorService scheduler;
|
||||
private SharedPreferences prefs;
|
||||
@@ -102,6 +103,8 @@ public class AbrpUploadService extends Service {
|
||||
// onCreate runs on the main thread, where the overlay window must be added.
|
||||
floatingButton = new FloatingButton(this, prefs);
|
||||
FloatingButton.attach(floatingButton);
|
||||
steeringKeys = new SteeringKeyListener(this, prefs);
|
||||
steeringKeys.register();
|
||||
|
||||
scheduler.scheduleWithFixedDelay(
|
||||
this::safeUploadCycle,
|
||||
@@ -144,6 +147,7 @@ public class AbrpUploadService extends Service {
|
||||
if (scheduler != null) scheduler.shutdownNow();
|
||||
if (telemetryUploader != null) telemetryUploader.stop();
|
||||
if (floatingButton != null) FloatingButton.detach(floatingButton);
|
||||
if (steeringKeys != null) steeringKeys.unregister();
|
||||
Log.i(TAG, "Service stopped");
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.evsuite.abrp;
|
||||
|
||||
/**
|
||||
* Spots a double press of one steering-wheel key from the head unit's key broadcasts.
|
||||
*
|
||||
* The broadcast format is not documented, so this copes with all the shapes it could take:
|
||||
* - no up/down information at all: every broadcast is one press;
|
||||
* - separate down and up broadcasts: only releases count. If the meaning of the action field
|
||||
* is the other way round, downs count instead — still exactly one per press, so a double
|
||||
* press is found either way;
|
||||
* - a long press resets, so holding the key never opens anything.
|
||||
*
|
||||
* Android-free and unit-tested; the window is longer than the stock 300 ms, because a
|
||||
* deliberate double press on a steering wheel is slower than one on a touch screen.
|
||||
*/
|
||||
final class DoublePressDetector {
|
||||
|
||||
static final long WINDOW_MS = 500;
|
||||
|
||||
private final int keyCode;
|
||||
private long lastPressAt = Long.MIN_VALUE;
|
||||
|
||||
DoublePressDetector(int keyCode) {
|
||||
this.keyCode = keyCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param down true for a key-down, false for a release, null when the broadcast does not say
|
||||
* @param longPress true when the broadcast marks a long press
|
||||
* @return true exactly once per double press
|
||||
*/
|
||||
synchronized boolean accept(int code, Boolean down, boolean longPress, long atMillis) {
|
||||
if (code != keyCode) return false;
|
||||
if (longPress) {
|
||||
lastPressAt = Long.MIN_VALUE;
|
||||
return false;
|
||||
}
|
||||
if (Boolean.TRUE.equals(down)) return false;
|
||||
if (lastPressAt != Long.MIN_VALUE && atMillis - lastPressAt <= WINDOW_MS) {
|
||||
lastPressAt = Long.MIN_VALUE; // a third press starts over
|
||||
return true;
|
||||
}
|
||||
lastPressAt = atMillis;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -120,7 +120,8 @@ public class MainActivity extends AppCompatActivity {
|
||||
private TextView connectionStatusText;
|
||||
|
||||
/** Upload page: live telemetry switch and cadence. */
|
||||
private SwitchMaterial liveTelemetrySwitch, boostSwitch, floatingButtonSwitch;
|
||||
private SwitchMaterial liveTelemetrySwitch, boostSwitch, floatingButtonSwitch, voiceKeySwitch;
|
||||
private TextView steeringKeysText;
|
||||
private View overlayPermissionNote;
|
||||
private android.widget.Spinner intervalSpinner;
|
||||
private TextInputLayout lowSocLayout;
|
||||
@@ -210,6 +211,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
@Override public void run() {
|
||||
refreshStatus();
|
||||
refreshCallLog();
|
||||
refreshSteeringKeys();
|
||||
refreshVehicle();
|
||||
refreshConsumption();
|
||||
refreshWifi();
|
||||
@@ -254,6 +256,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
|
||||
statusText = servicePane.findViewById(R.id.status_text);
|
||||
callLogText = logPane.findViewById(R.id.call_log_text);
|
||||
steeringKeysText = logPane.findViewById(R.id.steering_keys_text);
|
||||
logPane.findViewById(R.id.flush_now_button).setOnClickListener(v -> manualFlush());
|
||||
|
||||
setUpPager();
|
||||
@@ -795,6 +798,10 @@ public class MainActivity extends AppCompatActivity {
|
||||
// ---------- Upload page ----------
|
||||
|
||||
private void bindServicePane() {
|
||||
voiceKeySwitch = servicePane.findViewById(R.id.voice_key_switch);
|
||||
voiceKeySwitch.setChecked(UploadSettings.voiceKeyOpensApp(prefs));
|
||||
voiceKeySwitch.setOnCheckedChangeListener((btn, checked) ->
|
||||
prefs.edit().putBoolean(UploadSettings.KEY_VOICE_KEY_OPENS, checked).apply());
|
||||
floatingButtonSwitch = servicePane.findViewById(R.id.floating_button_switch);
|
||||
overlayPermissionNote = servicePane.findViewById(R.id.overlay_permission_note);
|
||||
floatingButtonSwitch.setChecked(UploadSettings.floatingButtonEnabled(prefs));
|
||||
@@ -1340,6 +1347,15 @@ public class MainActivity extends AppCompatActivity {
|
||||
}, "cloud-refresh").start();
|
||||
}
|
||||
|
||||
/** What the car's steering-wheel key broadcasts looked like, newest first. */
|
||||
private void refreshSteeringKeys() {
|
||||
if (steeringKeysText == null) return;
|
||||
java.util.List<String> keys = SteeringKeyListener.recent();
|
||||
String text = keys.isEmpty() ? getString(R.string.steering_keys_empty)
|
||||
: android.text.TextUtils.join("\n", keys);
|
||||
if (!text.contentEquals(steeringKeysText.getText())) steeringKeysText.setText(text);
|
||||
}
|
||||
|
||||
private void refreshCallLog() {
|
||||
java.util.List<UploadLog.Entry> entries = AbrpUploadService.log().recent();
|
||||
if (entries.isEmpty()) {
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.evsuite.abrp;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.ScrollView;
|
||||
|
||||
/**
|
||||
* A ScrollView that keeps the position the driver scrolled to.
|
||||
*
|
||||
* The pages live in ViewPager2's RecyclerView. When the live values change, the tiles ask for
|
||||
* a new layout, and the RecyclerView lays the pages out once already while it is still
|
||||
* measuring, with a provisional height at which the content seems to fit. ScrollView.onLayout
|
||||
* then clamps the scroll position to 0, and the real layout that follows keeps it there: every
|
||||
* refresh threw the page back to the top (seen in a stack trace on the emulator).
|
||||
*
|
||||
* Here only scrolling that is not part of a layout counts as the driver's position, and every
|
||||
* layout restores it. ScrollView.scrollTo clamps against the real content height, so a
|
||||
* position that no longer exists still ends at the bottom rather than past it.
|
||||
*/
|
||||
public class StableScrollView extends ScrollView {
|
||||
|
||||
private int wantedScrollY;
|
||||
private boolean inLayout;
|
||||
|
||||
public StableScrollView(Context context) { super(context); }
|
||||
|
||||
public StableScrollView(Context context, AttributeSet attrs) { super(context, attrs); }
|
||||
|
||||
public StableScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
|
||||
super.onScrollChanged(l, t, oldl, oldt);
|
||||
if (!inLayout) wantedScrollY = t;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int l, int t, int r, int b) {
|
||||
inLayout = true;
|
||||
try {
|
||||
super.onLayout(changed, l, t, r, b);
|
||||
if (getScrollY() != wantedScrollY) scrollTo(getScrollX(), wantedScrollY);
|
||||
} finally {
|
||||
inLayout = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
package com.evsuite.abrp;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Opens the app on a double press of the steering wheel's voice key.
|
||||
*
|
||||
* The head unit reports steering-wheel keys as the broadcast {@link #ACTION}; code 287 is the
|
||||
* voice key (see EVHardware's PhysicalButtonEventDecoder). How the broadcast carries the code
|
||||
* and the press is not documented, so the usual extras are tried, and every broadcast is kept
|
||||
* raw for the Logs tab — if the guess misses, that list says exactly what the car sends.
|
||||
*
|
||||
* Registered by the always-on service on the main thread; never throws into the head unit.
|
||||
*/
|
||||
final class SteeringKeyListener {
|
||||
|
||||
static final String ACTION = "com.saic.keyevent.hardkey.report";
|
||||
static final int VOICE_KEY = 287;
|
||||
private static final String TAG = "NilsMG4.SteeringKey";
|
||||
private static final int MAX_LOG = 20;
|
||||
|
||||
private static final ArrayDeque<String> RECENT = new ArrayDeque<>();
|
||||
|
||||
private final Context context;
|
||||
private final SharedPreferences prefs;
|
||||
private final DoublePressDetector detector = new DoublePressDetector(VOICE_KEY);
|
||||
private boolean registered;
|
||||
|
||||
private final BroadcastReceiver receiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context c, Intent intent) {
|
||||
try {
|
||||
handle(intent);
|
||||
} catch (RuntimeException e) {
|
||||
Log.w(TAG, "Key broadcast not handled: " + e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
SteeringKeyListener(Context context, SharedPreferences prefs) {
|
||||
this.context = context.getApplicationContext();
|
||||
this.prefs = prefs;
|
||||
}
|
||||
|
||||
void register() {
|
||||
try {
|
||||
// Exported on purpose: the report comes from the head unit's own key service, another
|
||||
// app. The flag is required from API 33 and absent on this API-28 head unit;
|
||||
// ContextCompat picks the right call for the platform underneath.
|
||||
androidx.core.content.ContextCompat.registerReceiver(context, receiver, new IntentFilter(ACTION),
|
||||
androidx.core.content.ContextCompat.RECEIVER_EXPORTED);
|
||||
registered = true;
|
||||
} catch (RuntimeException e) {
|
||||
Log.w(TAG, "Key broadcasts not available: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
void unregister() {
|
||||
if (!registered) return;
|
||||
try {
|
||||
context.unregisterReceiver(receiver);
|
||||
} catch (RuntimeException ignored) {
|
||||
// already gone
|
||||
}
|
||||
registered = false;
|
||||
}
|
||||
|
||||
/** The last broadcasts, newest first, for the Logs tab. */
|
||||
static synchronized List<String> recent() {
|
||||
List<String> out = new ArrayList<>(RECENT);
|
||||
java.util.Collections.reverse(out);
|
||||
return out;
|
||||
}
|
||||
|
||||
private static synchronized void remember(String line) {
|
||||
if (RECENT.size() >= MAX_LOG) RECENT.removeFirst();
|
||||
RECENT.addLast(line);
|
||||
}
|
||||
|
||||
private void handle(Intent intent) {
|
||||
Bundle extras = intent.getExtras();
|
||||
Parsed key = parse(extras);
|
||||
boolean opens = false;
|
||||
if (key.code != null && UploadSettings.voiceKeyOpensApp(prefs)) {
|
||||
opens = detector.accept(key.code, key.down, key.longPress, android.os.SystemClock.elapsedRealtime());
|
||||
}
|
||||
remember(new SimpleDateFormat("HH:mm:ss.SSS", Locale.US).format(new Date())
|
||||
+ " " + describe(extras)
|
||||
+ "\n -> code " + (key.code == null ? "?" : key.code)
|
||||
+ ", down " + (key.down == null ? "?" : key.down)
|
||||
+ ", long " + key.longPress + (opens ? " OPENS APP" : ""));
|
||||
if (opens) {
|
||||
try {
|
||||
context.startActivity(new Intent(context, MainActivity.class)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_REORDER_TO_FRONT));
|
||||
} catch (RuntimeException e) {
|
||||
Log.w(TAG, "App not opened: " + e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- Reading an undocumented broadcast ----------
|
||||
|
||||
static final class Parsed {
|
||||
Integer code;
|
||||
Boolean down;
|
||||
boolean longPress;
|
||||
}
|
||||
|
||||
private static final String[] CODE_KEYS = {"keyCode", "keycode", "key_code", "KEY_CODE", "keyValue",
|
||||
"key_value", "code", "key"};
|
||||
private static final String[] ACTION_KEYS = {"keyAction", "key_action", "KEY_ACTION", "action",
|
||||
"keyState", "key_state", "state", "event", "type"};
|
||||
private static final String[] DOWN_KEYS = {"down", "isDown", "pressed", "isPressed"};
|
||||
private static final String[] LONG_KEYS = {"longPress", "isLongPress", "long_press", "isLong", "long"};
|
||||
|
||||
static Parsed parse(Bundle extras) {
|
||||
Parsed p = new Parsed();
|
||||
if (extras == null) return p;
|
||||
for (String k : extras.keySet()) {
|
||||
Object v = extras.get(k);
|
||||
if (v instanceof KeyEvent) {
|
||||
KeyEvent e = (KeyEvent) v;
|
||||
p.code = e.getKeyCode();
|
||||
p.down = e.getAction() == KeyEvent.ACTION_DOWN;
|
||||
p.longPress = e.isLongPress();
|
||||
return p;
|
||||
}
|
||||
}
|
||||
p.code = firstInt(extras, CODE_KEYS);
|
||||
Integer action = firstInt(extras, ACTION_KEYS);
|
||||
// KeyEvent's own convention: 0 down, 1 up; anything else (often 2) is a long press.
|
||||
if (action != null) {
|
||||
if (action == 0) p.down = true;
|
||||
else if (action == 1) p.down = false;
|
||||
else p.longPress = true;
|
||||
}
|
||||
Boolean down = firstBoolean(extras, DOWN_KEYS);
|
||||
if (down != null) p.down = down;
|
||||
Boolean longPress = firstBoolean(extras, LONG_KEYS);
|
||||
if (longPress != null) p.longPress = longPress;
|
||||
return p;
|
||||
}
|
||||
|
||||
private static Integer firstInt(Bundle b, String[] keys) {
|
||||
for (String k : keys) {
|
||||
Object v = b.get(k);
|
||||
if (v instanceof Integer) return (Integer) v;
|
||||
if (v instanceof Long) return (int) (long) (Long) v;
|
||||
if (v instanceof String) {
|
||||
try { return Integer.parseInt(((String) v).trim()); } catch (NumberFormatException ignored) { }
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Boolean firstBoolean(Bundle b, String[] keys) {
|
||||
for (String k : keys) {
|
||||
Object v = b.get(k);
|
||||
if (v instanceof Boolean) return (Boolean) v;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** All extras as name=value(type), so the Logs tab shows the real format. */
|
||||
static String describe(Bundle extras) {
|
||||
if (extras == null || extras.isEmpty()) return "(no extras)";
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (String k : extras.keySet()) {
|
||||
Object v = extras.get(k);
|
||||
if (sb.length() > 0) sb.append(", ");
|
||||
sb.append(k).append('=').append(v).append(" (")
|
||||
.append(v == null ? "null" : v.getClass().getSimpleName()).append(')');
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,13 @@ final class UploadSettings {
|
||||
static final String KEY_FLOATING_BUTTON = "floating_button";
|
||||
static final boolean DEFAULT_FLOATING_BUTTON = true;
|
||||
|
||||
/** A double press of the steering wheel's voice key opens the app. */
|
||||
static final String KEY_VOICE_KEY_OPENS = "voice_key_opens_app";
|
||||
|
||||
static boolean voiceKeyOpensApp(SharedPreferences prefs) {
|
||||
return prefs.getBoolean(KEY_VOICE_KEY_OPENS, true);
|
||||
}
|
||||
|
||||
static boolean floatingButtonEnabled(SharedPreferences prefs) {
|
||||
return prefs.getBoolean(KEY_FLOATING_BUTTON, DEFAULT_FLOATING_BUTTON);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView 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" android:clipToPadding="false">
|
||||
<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" android:clipToPadding="false">
|
||||
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:padding="24dp" android:baselineAligned="false">
|
||||
<LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:orientation="vertical" android:layout_marginEnd="16dp" android:padding="24dp" android:background="@drawable/bg_glass">
|
||||
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/design_service_controls" android:textAppearance="@style/Text.EV.Eyebrow" />
|
||||
<TextView android:id="@+id/status_text" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@drawable/bg_input_panel" android:padding="16dp" android:layout_marginTop="20dp" android:layout_marginBottom="12dp" android:text="@string/state_stopped" android:textAppearance="@style/Text.EV.Body" />
|
||||
<com.google.android.material.switchmaterial.SwitchMaterial android:id="@+id/abrp_upload_switch" android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="72dp" android:text="@string/label_abrp_upload_enabled" 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_abrp_upload_enabled" 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" />
|
||||
<com.google.android.material.switchmaterial.SwitchMaterial android:id="@+id/floating_button_switch" android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="72dp" android:text="@string/label_floating_button" 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_floating_button" android:textAppearance="@style/Text.EV.Secondary" />
|
||||
<LinearLayout android:id="@+id/overlay_permission_note" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:layout_marginTop="8dp" android:visibility="gone">
|
||||
@@ -24,4 +26,4 @@
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
</com.evsuite.abrp.StableScrollView>
|
||||
|
||||
@@ -1,6 +1,36 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:fillViewport="true" android:clipToPadding="false">
|
||||
<com.evsuite.abrp.StableScrollView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:fillViewport="true" android:clipToPadding="false">
|
||||
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="24dp">
|
||||
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:baselineAligned="false">
|
||||
<LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:orientation="vertical" android:minHeight="112dp" android:padding="16dp" android:background="@drawable/bg_battery_card" android:layout_weight="1" android:layout_marginEnd="10dp">
|
||||
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/design_battery_short" android:textAppearance="@style/Text.EV.Secondary" />
|
||||
<TextView android:id="@+id/vehicle_soc" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="—" android:layout_marginTop="6dp" android:textAppearance="@style/Text.EV.Metric.Compact" android:maxLines="1" android:autoSizeTextType="uniform" android:autoSizeMinTextSize="18sp" android:autoSizeMaxTextSize="28sp" android:autoSizeStepGranularity="1sp" />
|
||||
<TextView android:id="@+id/vehicle_charging" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="—" android:textAppearance="@style/Text.EV.Secondary" android:textColor="@color/ev_cyan" android:textSize="16sp" android:maxLines="1" android:ellipsize="end" />
|
||||
</LinearLayout>
|
||||
<LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:orientation="vertical" android:minHeight="112dp" android:padding="16dp" android:background="@drawable/bg_glass" android:layout_weight="1" android:layout_marginEnd="10dp">
|
||||
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/vehicle_row_range" android:textAppearance="@style/Text.EV.Secondary" />
|
||||
<TextView android:id="@+id/vehicle_range" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="—" android:layout_marginTop="6dp" android:textAppearance="@style/Text.EV.Metric.Compact" android:maxLines="1" android:autoSizeTextType="uniform" android:autoSizeMinTextSize="18sp" android:autoSizeMaxTextSize="28sp" android:autoSizeStepGranularity="1sp" />
|
||||
</LinearLayout>
|
||||
<LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:layout_marginEnd="10dp" android:orientation="vertical" android:minHeight="112dp" android:padding="16dp" android:background="@drawable/bg_glass">
|
||||
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/vehicle_row_range_power" android:textAppearance="@style/Text.EV.Secondary" />
|
||||
<TextView android:id="@+id/vehicle_range_power" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="—" android:layout_marginTop="6dp" android:textAppearance="@style/Text.EV.Metric.Compact" android:maxLines="1" android:autoSizeTextType="uniform" android:autoSizeMinTextSize="18sp" android:autoSizeMaxTextSize="28sp" android:autoSizeStepGranularity="1sp" />
|
||||
<TextView android:id="@+id/vehicle_range_power_note" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="" android:textAppearance="@style/Text.EV.Secondary" android:textColor="@color/ev_cyan" android:textSize="16sp" android:maxLines="1" android:ellipsize="end" />
|
||||
</LinearLayout>
|
||||
<LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:layout_marginEnd="10dp" android:orientation="vertical" android:minHeight="112dp" android:padding="16dp" android:background="@drawable/bg_glass">
|
||||
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/vehicle_row_range_average" android:textAppearance="@style/Text.EV.Secondary" />
|
||||
<TextView android:id="@+id/vehicle_range_average" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="—" android:layout_marginTop="6dp" android:textAppearance="@style/Text.EV.Metric.Compact" android:maxLines="1" android:autoSizeTextType="uniform" android:autoSizeMinTextSize="18sp" android:autoSizeMaxTextSize="28sp" android:autoSizeStepGranularity="1sp" />
|
||||
<TextView android:id="@+id/vehicle_range_average_note" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="" android:textAppearance="@style/Text.EV.Secondary" android:textColor="@color/ev_cyan" android:textSize="16sp" android:maxLines="1" android:ellipsize="end" />
|
||||
</LinearLayout>
|
||||
<LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:orientation="vertical" android:minHeight="112dp" android:padding="16dp" android:background="@drawable/bg_glass" android:layout_weight="1" android:layout_marginEnd="10dp">
|
||||
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/vehicle_row_power" android:textAppearance="@style/Text.EV.Secondary" />
|
||||
<TextView android:id="@+id/vehicle_power" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="—" android:layout_marginTop="6dp" android:textAppearance="@style/Text.EV.Metric.Compact" android:maxLines="1" android:autoSizeTextType="uniform" android:autoSizeMinTextSize="18sp" android:autoSizeMaxTextSize="28sp" android:autoSizeStepGranularity="1sp" />
|
||||
</LinearLayout>
|
||||
<LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:orientation="vertical" android:minHeight="112dp" android:padding="16dp" android:background="@drawable/bg_glass" android:layout_weight="1" android:layout_marginEnd="10dp">
|
||||
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/vehicle_row_speed" android:textAppearance="@style/Text.EV.Secondary" />
|
||||
<TextView android:id="@+id/vehicle_speed" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="—" android:layout_marginTop="6dp" android:textAppearance="@style/Text.EV.Metric.Compact" android:maxLines="1" android:autoSizeTextType="uniform" android:autoSizeMinTextSize="18sp" android:autoSizeMaxTextSize="28sp" android:autoSizeStepGranularity="1sp" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
<Space android:layout_width="1dp" android:layout_height="16dp" />
|
||||
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:baselineAligned="false">
|
||||
<LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:orientation="vertical" android:layout_marginEnd="16dp" android:padding="20dp" android:background="@drawable/bg_glass">
|
||||
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/design_energy" android:textAppearance="@style/Text.EV.Eyebrow" android:layout_marginBottom="12dp" />
|
||||
@@ -37,38 +67,5 @@
|
||||
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/design_no_data" android:textAppearance="@style/Text.EV.Secondary" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
<Space android:layout_width="1dp" android:layout_height="16dp" />
|
||||
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:baselineAligned="false">
|
||||
<LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:orientation="vertical" android:minHeight="112dp" android:padding="16dp" android:background="@drawable/bg_battery_card" android:layout_weight="1" android:layout_marginEnd="10dp">
|
||||
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/design_battery_short" android:textAppearance="@style/Text.EV.Secondary" />
|
||||
<TextView android:id="@+id/vehicle_soc" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="—" android:layout_marginTop="6dp" android:textAppearance="@style/Text.EV.Metric.Compact" android:maxLines="1" android:autoSizeTextType="uniform" android:autoSizeMinTextSize="18sp" android:autoSizeMaxTextSize="28sp" android:autoSizeStepGranularity="1sp" />
|
||||
<TextView android:id="@+id/vehicle_charging" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="—" android:textAppearance="@style/Text.EV.Secondary" android:textColor="@color/ev_cyan" android:textSize="16sp" android:maxLines="1" android:ellipsize="end" />
|
||||
</LinearLayout>
|
||||
<LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:orientation="vertical" android:minHeight="112dp" android:padding="16dp" android:background="@drawable/bg_glass" android:layout_weight="1" android:layout_marginEnd="10dp">
|
||||
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/vehicle_row_range" android:textAppearance="@style/Text.EV.Secondary" />
|
||||
<TextView android:id="@+id/vehicle_range" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="—" android:layout_marginTop="6dp" android:textAppearance="@style/Text.EV.Metric.Compact" android:maxLines="1" android:autoSizeTextType="uniform" android:autoSizeMinTextSize="18sp" android:autoSizeMaxTextSize="28sp" android:autoSizeStepGranularity="1sp" />
|
||||
</LinearLayout>
|
||||
<LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:orientation="vertical" android:minHeight="112dp" android:padding="16dp" android:background="@drawable/bg_glass" android:layout_weight="1" android:layout_marginEnd="10dp">
|
||||
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/vehicle_row_power" android:textAppearance="@style/Text.EV.Secondary" />
|
||||
<TextView android:id="@+id/vehicle_power" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="—" android:layout_marginTop="6dp" android:textAppearance="@style/Text.EV.Metric.Compact" android:maxLines="1" android:autoSizeTextType="uniform" android:autoSizeMinTextSize="18sp" android:autoSizeMaxTextSize="28sp" android:autoSizeStepGranularity="1sp" />
|
||||
</LinearLayout>
|
||||
<LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:orientation="vertical" android:minHeight="112dp" android:padding="16dp" android:background="@drawable/bg_glass" android:layout_weight="1" android:layout_marginEnd="10dp">
|
||||
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/vehicle_row_speed" android:textAppearance="@style/Text.EV.Secondary" />
|
||||
<TextView android:id="@+id/vehicle_speed" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="—" android:layout_marginTop="6dp" android:textAppearance="@style/Text.EV.Metric.Compact" android:maxLines="1" android:autoSizeTextType="uniform" android:autoSizeMinTextSize="18sp" android:autoSizeMaxTextSize="28sp" android:autoSizeStepGranularity="1sp" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
<Space android:layout_width="1dp" android:layout_height="10dp" />
|
||||
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:baselineAligned="false">
|
||||
<LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:layout_marginEnd="10dp" android:orientation="vertical" android:minHeight="112dp" android:padding="16dp" android:background="@drawable/bg_glass">
|
||||
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/vehicle_row_range_power" android:textAppearance="@style/Text.EV.Secondary" />
|
||||
<TextView android:id="@+id/vehicle_range_power" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="—" android:layout_marginTop="6dp" android:textAppearance="@style/Text.EV.Metric.Compact" android:maxLines="1" android:autoSizeTextType="uniform" android:autoSizeMinTextSize="18sp" android:autoSizeMaxTextSize="28sp" android:autoSizeStepGranularity="1sp" />
|
||||
<TextView android:id="@+id/vehicle_range_power_note" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="" android:textAppearance="@style/Text.EV.Secondary" android:textColor="@color/ev_cyan" android:textSize="16sp" android:maxLines="1" android:ellipsize="end" />
|
||||
</LinearLayout>
|
||||
<LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:layout_marginEnd="10dp" android:orientation="vertical" android:minHeight="112dp" android:padding="16dp" android:background="@drawable/bg_glass">
|
||||
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/vehicle_row_range_average" android:textAppearance="@style/Text.EV.Secondary" />
|
||||
<TextView android:id="@+id/vehicle_range_average" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="—" android:layout_marginTop="6dp" android:textAppearance="@style/Text.EV.Metric.Compact" android:maxLines="1" android:autoSizeTextType="uniform" android:autoSizeMinTextSize="18sp" android:autoSizeMaxTextSize="28sp" android:autoSizeStepGranularity="1sp" />
|
||||
<TextView android:id="@+id/vehicle_range_average_note" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="" android:textAppearance="@style/Text.EV.Secondary" android:textColor="@color/ev_cyan" android:textSize="16sp" android:maxLines="1" android:ellipsize="end" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
</com.evsuite.abrp.StableScrollView>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView 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" android:clipToPadding="false">
|
||||
<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" android:clipToPadding="false">
|
||||
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:padding="24dp" android:baselineAligned="false">
|
||||
<LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1.3" android:orientation="vertical" android:layout_marginEnd="16dp" android:padding="24dp" android:background="@drawable/bg_glass">
|
||||
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/wifi_card_title" android:textAppearance="@style/Text.EV.Eyebrow" />
|
||||
@@ -14,4 +14,4 @@
|
||||
<TextView android:id="@+id/wifi_saved_list" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="20dp" android:textAppearance="@style/Text.EV.Body" android:lineSpacingExtra="16dp" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
</com.evsuite.abrp.StableScrollView>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView 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">
|
||||
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="24dp">
|
||||
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" 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="Not charging" android:textAppearance="@style/Text.EV.Title" android:textColor="@color/ev_cyan"/>
|
||||
@@ -28,4 +28,4 @@
|
||||
<TextView android:id="@+id/charge_curve_table" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="16dp" android:fontFamily="monospace" android:text="No charging measurements yet" android:textAppearance="@style/Text.EV.Body" android:textColor="@color/ev_cyan"/>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
</com.evsuite.abrp.StableScrollView>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView 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">
|
||||
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="24dp">
|
||||
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="20dp" android:background="@drawable/bg_glass">
|
||||
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:gravity="center_vertical">
|
||||
@@ -45,4 +45,4 @@
|
||||
<TextView android:id="@+id/consumption_daily_history" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="12dp" android:fontFamily="monospace" android:text="No daily data yet" android:textAppearance="@style/Text.EV.Body" android:textColor="@color/ev_cyan"/>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
</com.evsuite.abrp.StableScrollView>
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:fillViewport="true" android:clipToPadding="false">
|
||||
<com.evsuite.abrp.StableScrollView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:fillViewport="true" android:clipToPadding="false">
|
||||
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="24dp" android:background="@drawable/bg_glass">
|
||||
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/label_call_log" android:textAppearance="@style/Text.EV.Eyebrow" />
|
||||
<com.google.android.material.button.MaterialButton android:id="@+id/flush_now_button" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="16dp" android:text="@string/btn_flush_now" app:icon="@drawable/ic_refresh" app:iconGravity="textStart" style="@style/Widget.EV.Button.Outlined" xmlns:app="http://schemas.android.com/apk/res-auto" />
|
||||
<TextView android:id="@+id/call_log_text" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="20dp" android:minHeight="180dp" android:text="@string/call_log_empty" android:textAppearance="@style/Text.EV.Body" android:fontFamily="monospace" android:textSize="17sp" android:lineSpacingExtra="12dp" android:textIsSelectable="true" />
|
||||
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="32dp" android:text="@string/label_steering_keys" android:textAppearance="@style/Text.EV.Eyebrow" />
|
||||
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:text="@string/hint_steering_keys" android:textAppearance="@style/Text.EV.Secondary" />
|
||||
<TextView android:id="@+id/steering_keys_text" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="12dp" android:text="@string/steering_keys_empty" android:textAppearance="@style/Text.EV.Body" android:fontFamily="monospace" android:textSize="14sp" android:lineSpacingExtra="4dp" />
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
</com.evsuite.abrp.StableScrollView>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView 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" android:clipToPadding="false">
|
||||
<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" android:clipToPadding="false">
|
||||
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="24dp">
|
||||
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="24dp" android:background="@drawable/bg_glass">
|
||||
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/server_card_title" android:textAppearance="@style/Text.EV.Eyebrow" />
|
||||
@@ -29,4 +29,4 @@
|
||||
<TextView android:id="@+id/import_hint" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="12dp" android:textAppearance="@style/Text.EV.Secondary" android:textIsSelectable="true" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
</com.evsuite.abrp.StableScrollView>
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView 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" android:clipToPadding="false">
|
||||
<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" android:clipToPadding="false">
|
||||
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="24dp">
|
||||
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="24dp" android:background="@drawable/bg_glass">
|
||||
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/design_service_controls" android:textAppearance="@style/Text.EV.Eyebrow" />
|
||||
<TextView android:id="@+id/status_text" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@drawable/bg_input_panel" android:padding="16dp" android:layout_marginTop="20dp" android:layout_marginBottom="12dp" android:text="@string/state_stopped" android:textAppearance="@style/Text.EV.Body" />
|
||||
<com.google.android.material.switchmaterial.SwitchMaterial android:id="@+id/abrp_upload_switch" android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="72dp" android:text="@string/label_abrp_upload_enabled" 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_abrp_upload_enabled" 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" />
|
||||
<com.google.android.material.switchmaterial.SwitchMaterial android:id="@+id/floating_button_switch" android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="72dp" android:text="@string/label_floating_button" 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_floating_button" android:textAppearance="@style/Text.EV.Secondary" />
|
||||
<LinearLayout android:id="@+id/overlay_permission_note" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:layout_marginTop="8dp" android:visibility="gone">
|
||||
@@ -25,4 +27,4 @@
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
</com.evsuite.abrp.StableScrollView>
|
||||
|
||||
@@ -1,6 +1,36 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:fillViewport="true" android:clipToPadding="false">
|
||||
<com.evsuite.abrp.StableScrollView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:fillViewport="true" android:clipToPadding="false">
|
||||
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="24dp">
|
||||
<GridLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:columnCount="2">
|
||||
<LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:orientation="vertical" android:minHeight="112dp" android:padding="16dp" android:background="@drawable/bg_battery_card" android:layout_columnWeight="1" android:layout_margin="4dp">
|
||||
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/design_battery_short" android:textAppearance="@style/Text.EV.Secondary" />
|
||||
<TextView android:id="@+id/vehicle_soc" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="—" android:layout_marginTop="6dp" android:textAppearance="@style/Text.EV.Metric.Compact" android:maxLines="1" android:autoSizeTextType="uniform" android:autoSizeMinTextSize="18sp" android:autoSizeMaxTextSize="28sp" android:autoSizeStepGranularity="1sp" />
|
||||
<TextView android:id="@+id/vehicle_charging" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="—" android:textAppearance="@style/Text.EV.Secondary" android:textColor="@color/ev_cyan" android:textSize="16sp" android:maxLines="1" android:ellipsize="end" />
|
||||
</LinearLayout>
|
||||
<LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:orientation="vertical" android:minHeight="112dp" android:padding="16dp" android:background="@drawable/bg_glass" android:layout_columnWeight="1" android:layout_margin="4dp">
|
||||
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/vehicle_row_range" android:textAppearance="@style/Text.EV.Secondary" />
|
||||
<TextView android:id="@+id/vehicle_range" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="—" android:layout_marginTop="6dp" android:textAppearance="@style/Text.EV.Metric.Compact" android:maxLines="1" android:autoSizeTextType="uniform" android:autoSizeMinTextSize="18sp" android:autoSizeMaxTextSize="28sp" android:autoSizeStepGranularity="1sp" />
|
||||
</LinearLayout>
|
||||
<LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_columnWeight="1" android:layout_margin="4dp" android:orientation="vertical" android:minHeight="112dp" android:padding="16dp" android:background="@drawable/bg_glass">
|
||||
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/vehicle_row_range_power" android:textAppearance="@style/Text.EV.Secondary" />
|
||||
<TextView android:id="@+id/vehicle_range_power" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="—" android:layout_marginTop="6dp" android:textAppearance="@style/Text.EV.Metric.Compact" android:maxLines="1" android:autoSizeTextType="uniform" android:autoSizeMinTextSize="18sp" android:autoSizeMaxTextSize="28sp" android:autoSizeStepGranularity="1sp" />
|
||||
<TextView android:id="@+id/vehicle_range_power_note" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="" android:textAppearance="@style/Text.EV.Secondary" android:textColor="@color/ev_cyan" android:textSize="16sp" android:maxLines="1" android:ellipsize="end" />
|
||||
</LinearLayout>
|
||||
<LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_columnWeight="1" android:layout_margin="4dp" android:orientation="vertical" android:minHeight="112dp" android:padding="16dp" android:background="@drawable/bg_glass">
|
||||
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/vehicle_row_range_average" android:textAppearance="@style/Text.EV.Secondary" />
|
||||
<TextView android:id="@+id/vehicle_range_average" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="—" android:layout_marginTop="6dp" android:textAppearance="@style/Text.EV.Metric.Compact" android:maxLines="1" android:autoSizeTextType="uniform" android:autoSizeMinTextSize="18sp" android:autoSizeMaxTextSize="28sp" android:autoSizeStepGranularity="1sp" />
|
||||
<TextView android:id="@+id/vehicle_range_average_note" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="" android:textAppearance="@style/Text.EV.Secondary" android:textColor="@color/ev_cyan" android:textSize="16sp" android:maxLines="1" android:ellipsize="end" />
|
||||
</LinearLayout>
|
||||
<LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:orientation="vertical" android:minHeight="112dp" android:padding="16dp" android:background="@drawable/bg_glass" android:layout_columnWeight="1" android:layout_margin="4dp">
|
||||
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/vehicle_row_power" android:textAppearance="@style/Text.EV.Secondary" />
|
||||
<TextView android:id="@+id/vehicle_power" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="—" android:layout_marginTop="6dp" android:textAppearance="@style/Text.EV.Metric.Compact" android:maxLines="1" android:autoSizeTextType="uniform" android:autoSizeMinTextSize="18sp" android:autoSizeMaxTextSize="28sp" android:autoSizeStepGranularity="1sp" />
|
||||
</LinearLayout>
|
||||
<LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:orientation="vertical" android:minHeight="112dp" android:padding="16dp" android:background="@drawable/bg_glass" android:layout_columnWeight="1" android:layout_margin="4dp">
|
||||
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/vehicle_row_speed" android:textAppearance="@style/Text.EV.Secondary" />
|
||||
<TextView android:id="@+id/vehicle_speed" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="—" android:layout_marginTop="6dp" android:textAppearance="@style/Text.EV.Metric.Compact" android:maxLines="1" android:autoSizeTextType="uniform" android:autoSizeMinTextSize="18sp" android:autoSizeMaxTextSize="28sp" android:autoSizeStepGranularity="1sp" />
|
||||
</LinearLayout>
|
||||
</GridLayout>
|
||||
<Space android:layout_width="1dp" android:layout_height="16dp" />
|
||||
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" 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/design_energy" android:textAppearance="@style/Text.EV.Eyebrow" android:layout_marginBottom="12dp" />
|
||||
<TableLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:stretchColumns="0" android:shrinkColumns="0">
|
||||
@@ -35,35 +65,5 @@
|
||||
</LinearLayout>
|
||||
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/design_no_data" android:textAppearance="@style/Text.EV.Secondary" />
|
||||
</LinearLayout>
|
||||
<Space android:layout_width="1dp" android:layout_height="16dp" />
|
||||
<GridLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:columnCount="2">
|
||||
<LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:orientation="vertical" android:minHeight="112dp" android:padding="16dp" android:background="@drawable/bg_battery_card" android:layout_columnWeight="1" android:layout_margin="4dp">
|
||||
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/design_battery_short" android:textAppearance="@style/Text.EV.Secondary" />
|
||||
<TextView android:id="@+id/vehicle_soc" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="—" android:layout_marginTop="6dp" android:textAppearance="@style/Text.EV.Metric.Compact" android:maxLines="1" android:autoSizeTextType="uniform" android:autoSizeMinTextSize="18sp" android:autoSizeMaxTextSize="28sp" android:autoSizeStepGranularity="1sp" />
|
||||
<TextView android:id="@+id/vehicle_charging" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="—" android:textAppearance="@style/Text.EV.Secondary" android:textColor="@color/ev_cyan" android:textSize="16sp" android:maxLines="1" android:ellipsize="end" />
|
||||
</LinearLayout>
|
||||
<LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:orientation="vertical" android:minHeight="112dp" android:padding="16dp" android:background="@drawable/bg_glass" android:layout_columnWeight="1" android:layout_margin="4dp">
|
||||
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/vehicle_row_range" android:textAppearance="@style/Text.EV.Secondary" />
|
||||
<TextView android:id="@+id/vehicle_range" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="—" android:layout_marginTop="6dp" android:textAppearance="@style/Text.EV.Metric.Compact" android:maxLines="1" android:autoSizeTextType="uniform" android:autoSizeMinTextSize="18sp" android:autoSizeMaxTextSize="28sp" android:autoSizeStepGranularity="1sp" />
|
||||
</LinearLayout>
|
||||
<LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:orientation="vertical" android:minHeight="112dp" android:padding="16dp" android:background="@drawable/bg_glass" android:layout_columnWeight="1" android:layout_margin="4dp">
|
||||
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/vehicle_row_power" android:textAppearance="@style/Text.EV.Secondary" />
|
||||
<TextView android:id="@+id/vehicle_power" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="—" android:layout_marginTop="6dp" android:textAppearance="@style/Text.EV.Metric.Compact" android:maxLines="1" android:autoSizeTextType="uniform" android:autoSizeMinTextSize="18sp" android:autoSizeMaxTextSize="28sp" android:autoSizeStepGranularity="1sp" />
|
||||
</LinearLayout>
|
||||
<LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:orientation="vertical" android:minHeight="112dp" android:padding="16dp" android:background="@drawable/bg_glass" android:layout_columnWeight="1" android:layout_margin="4dp">
|
||||
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/vehicle_row_speed" android:textAppearance="@style/Text.EV.Secondary" />
|
||||
<TextView android:id="@+id/vehicle_speed" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="—" android:layout_marginTop="6dp" android:textAppearance="@style/Text.EV.Metric.Compact" android:maxLines="1" android:autoSizeTextType="uniform" android:autoSizeMinTextSize="18sp" android:autoSizeMaxTextSize="28sp" android:autoSizeStepGranularity="1sp" />
|
||||
</LinearLayout>
|
||||
<LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_columnWeight="1" android:layout_margin="4dp" android:orientation="vertical" android:minHeight="112dp" android:padding="16dp" android:background="@drawable/bg_glass">
|
||||
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/vehicle_row_range_power" android:textAppearance="@style/Text.EV.Secondary" />
|
||||
<TextView android:id="@+id/vehicle_range_power" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="—" android:layout_marginTop="6dp" android:textAppearance="@style/Text.EV.Metric.Compact" android:maxLines="1" android:autoSizeTextType="uniform" android:autoSizeMinTextSize="18sp" android:autoSizeMaxTextSize="28sp" android:autoSizeStepGranularity="1sp" />
|
||||
<TextView android:id="@+id/vehicle_range_power_note" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="" android:textAppearance="@style/Text.EV.Secondary" android:textColor="@color/ev_cyan" android:textSize="16sp" android:maxLines="1" android:ellipsize="end" />
|
||||
</LinearLayout>
|
||||
<LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_columnWeight="1" android:layout_margin="4dp" android:orientation="vertical" android:minHeight="112dp" android:padding="16dp" android:background="@drawable/bg_glass">
|
||||
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/vehicle_row_range_average" android:textAppearance="@style/Text.EV.Secondary" />
|
||||
<TextView android:id="@+id/vehicle_range_average" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="—" android:layout_marginTop="6dp" android:textAppearance="@style/Text.EV.Metric.Compact" android:maxLines="1" android:autoSizeTextType="uniform" android:autoSizeMinTextSize="18sp" android:autoSizeMaxTextSize="28sp" android:autoSizeStepGranularity="1sp" />
|
||||
<TextView android:id="@+id/vehicle_range_average_note" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="" android:textAppearance="@style/Text.EV.Secondary" android:textColor="@color/ev_cyan" android:textSize="16sp" android:maxLines="1" android:ellipsize="end" />
|
||||
</LinearLayout>
|
||||
</GridLayout>
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
</com.evsuite.abrp.StableScrollView>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView 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" android:clipToPadding="false">
|
||||
<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" android:clipToPadding="false">
|
||||
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="24dp">
|
||||
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="24dp" android:background="@drawable/bg_glass">
|
||||
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/wifi_card_title" android:textAppearance="@style/Text.EV.Eyebrow" />
|
||||
@@ -15,4 +15,4 @@
|
||||
<TextView android:id="@+id/wifi_saved_list" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="20dp" android:textAppearance="@style/Text.EV.Body" android:lineSpacingExtra="16dp" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
</com.evsuite.abrp.StableScrollView>
|
||||
|
||||
@@ -107,6 +107,11 @@
|
||||
<string name="conn_err_timeout">Connection timed out</string>
|
||||
<string name="conn_err_http">Server error (HTTP %1$d)</string>
|
||||
|
||||
<string name="label_voice_key">Double-press the voice key to open this app</string>
|
||||
<string name="hint_voice_key">Press the voice key on the steering wheel twice quickly. The voice assistant may still react to the presses.</string>
|
||||
<string name="label_steering_keys">Steering-wheel keys</string>
|
||||
<string name="hint_steering_keys">The last key reports the car sent, newest first. If double-pressing the voice key does not open the app, a photo of this list shows how the car reports it.</string>
|
||||
<string name="steering_keys_empty">No key reports received yet. Press a steering-wheel key.</string>
|
||||
<string name="label_floating_button">Show floating button</string>
|
||||
<string name="hint_floating_button">A small round button over the car\'s screens that opens this app. Drag it to move it; it hides while the app is open.</string>
|
||||
<string name="overlay_permission_missing">This build may not draw over other apps, so the button cannot appear.</string>
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
package com.evsuite.abrp;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
/** Double-press detection for the voice key (287) — pure JVM. */
|
||||
public class DoublePressDetectorTest {
|
||||
|
||||
private static final int VOICE = 287;
|
||||
|
||||
@Test
|
||||
public void broadcastsWithoutUpDownCountAsPresses() {
|
||||
DoublePressDetector d = new DoublePressDetector(VOICE);
|
||||
assertFalse(d.accept(VOICE, null, false, 1_000));
|
||||
assertTrue(d.accept(VOICE, null, false, 1_350));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withDownAndUpOnlyReleasesCount() {
|
||||
DoublePressDetector d = new DoublePressDetector(VOICE);
|
||||
assertFalse(d.accept(VOICE, true, false, 1_000));
|
||||
assertFalse(d.accept(VOICE, false, false, 1_080));
|
||||
assertFalse(d.accept(VOICE, true, false, 1_250));
|
||||
assertTrue(d.accept(VOICE, false, false, 1_330));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void tooSlowIsTwoSinglePresses() {
|
||||
DoublePressDetector d = new DoublePressDetector(VOICE);
|
||||
assertFalse(d.accept(VOICE, null, false, 1_000));
|
||||
assertFalse(d.accept(VOICE, null, false, 1_000 + DoublePressDetector.WINDOW_MS + 1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void otherKeysAndLongPressesDoNotCount() {
|
||||
DoublePressDetector d = new DoublePressDetector(VOICE);
|
||||
assertFalse(d.accept(17, null, false, 1_000));
|
||||
assertFalse(d.accept(VOICE, null, false, 1_100));
|
||||
assertFalse(d.accept(17, null, false, 1_200)); // another key in between is ignored
|
||||
assertTrue(d.accept(VOICE, null, false, 1_300));
|
||||
|
||||
DoublePressDetector held = new DoublePressDetector(VOICE);
|
||||
assertFalse(held.accept(VOICE, null, false, 1_000));
|
||||
assertFalse(held.accept(VOICE, true, true, 1_100)); // long press resets
|
||||
assertFalse(held.accept(VOICE, false, false, 1_300));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void threePressesOpenOnceAndStartOver() {
|
||||
DoublePressDetector d = new DoublePressDetector(VOICE);
|
||||
assertFalse(d.accept(VOICE, null, false, 1_000));
|
||||
assertTrue(d.accept(VOICE, null, false, 1_200));
|
||||
assertFalse(d.accept(VOICE, null, false, 1_400));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user