diff --git a/SPECS.md b/SPECS.md index af3ee3e..410abca 100644 --- a/SPECS.md +++ b/SPECS.md @@ -112,6 +112,11 @@ tyre pressures, GPS (lat, lon, elevation, heading) and the is_* flags. It does * INGEST_TOKEN) into git and pushed it. Commit amended and force-pushed; both values regenerated in the now gitignored `k8s/secrets.yaml` (since moved to `k8s/secrets/secrets.yaml`, outside `kubectl apply -f k8s/`). Filled-in secrets live only there from now on. +- 2026-10-18 — App 2.16.3: full screen stays while choosing or reading a dialog. Drop-downs (upload + frequency, tyre unit, accent colour) are now buttons on the page: a drop-down is a window of its + own without the full-screen flags, so the car showed its bars while it was open. Dialogs are + shown unfocusable, given the activity's full-screen flags, then allowed focus + (`showKeepingFullScreen`); none of them takes typing. - 2026-10-18 — From the car (2.16.2): screen 1920×720 px at 160 dpi with a 1778×720 px app window, so EVProfile's 1280×480 does not apply; colour probe found only `vehicle_type = AS28`. FIRMWARE.md, mise.toml and SPECS.md say so. diff --git a/app/build.gradle b/app/build.gradle index c1329a4..baed5bc 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -34,8 +34,8 @@ android { applicationId "com.stinnesbeck.nilsmg4" minSdk 27 targetSdk 36 - versionCode 86 - versionName "2.16.2" + versionCode 87 + versionName "2.16.3" // 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. diff --git a/app/src/main/java/com/evsuite/abrp/MainActivity.java b/app/src/main/java/com/evsuite/abrp/MainActivity.java index ff4e99f..eb48a7b 100644 --- a/app/src/main/java/com/evsuite/abrp/MainActivity.java +++ b/app/src/main/java/com/evsuite/abrp/MainActivity.java @@ -126,7 +126,7 @@ public class MainActivity extends AppCompatActivity { /** Upload page: live telemetry switch and cadence. */ private SwitchMaterial liveTelemetrySwitch, boostSwitch, voiceKeySwitch, fullScreenSwitch; private TextView steeringKeysText; - private android.widget.Spinner intervalSpinner; + private com.google.android.material.chip.ChipGroup intervalChoices; private TextInputLayout lowSocLayout; private TextInputEditText lowSocInput; @@ -305,6 +305,26 @@ public class MainActivity extends AppCompatActivity { window.widthPixels, window.heightPixels, config.screenWidthDp, config.screenHeightDp)); } + /** + * Shows a dialog without bringing the car's bars back. A dialog is a window of its own, and + * the system shows the bars when a window without the full-screen flags takes focus. So it + * is shown unfocusable, given the activity's flags, and only then allowed focus. None of the + * dialogs here take typing, which an unfocusable window would block. + */ + @SuppressWarnings("deprecation") // the WindowInsetsController API starts at 30; this is 28 + private void showKeepingFullScreen(android.app.Dialog dialog) { + android.view.Window window = dialog.getWindow(); + if (window == null || !UploadSettings.fullScreen(prefs)) { + dialog.show(); + return; + } + window.setFlags(android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, + android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE); + dialog.show(); + window.getDecorView().setSystemUiVisibility(getWindow().getDecorView().getSystemUiVisibility()); + window.clearFlags(android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE); + } + private static final String STATE_PAGE = "page"; @Override @@ -332,7 +352,7 @@ public class MainActivity extends AppCompatActivity { .setView(content) .create(); content.findViewById(R.id.about_close).setOnClickListener(v -> dialog.dismiss()); - dialog.show(); + showKeepingFullScreen(dialog); if (dialog.getWindow() != null) { dialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); android.util.DisplayMetrics metrics = getResources().getDisplayMetrics(); @@ -440,7 +460,7 @@ public class MainActivity extends AppCompatActivity { .setView(panel) .setCancelable(false) .create(); - dialog.show(); + showKeepingFullScreen(dialog); UpdateHook.checkInBackground(this, new UpdateHook.Listener() { @Override public void onProgress(int percent) { if (percent < 0) { @@ -457,11 +477,11 @@ public class MainActivity extends AppCompatActivity { @Override public void onResult(String message) { button.setEnabled(true); dialog.dismiss(); - new MaterialAlertDialogBuilder(MainActivity.this) + showKeepingFullScreen(new MaterialAlertDialogBuilder(MainActivity.this) .setTitle(R.string.update_dialog_title) .setMessage(message) .setPositiveButton(android.R.string.ok, null) - .show(); + .create()); } }); } @@ -796,7 +816,7 @@ public class MainActivity extends AppCompatActivity { qr.setVisibility(View.GONE); code.setVisibility(View.GONE); dialog.setOnDismissListener(d -> cancelled[0] = true); - dialog.show(); + showKeepingFullScreen(dialog); } /** Shows the request: QR code, the code in the sentence and below it. UI thread. */ @@ -1032,7 +1052,7 @@ public class MainActivity extends AppCompatActivity { applyFullScreen(); }); liveTelemetrySwitch = servicePane.findViewById(R.id.abrp_upload_switch); - intervalSpinner = servicePane.findViewById(R.id.interval_spinner); + intervalChoices = servicePane.findViewById(R.id.interval_choices); boostSwitch = servicePane.findViewById(R.id.boost_switch); lowSocInput = servicePane.findViewById(R.id.low_soc_input); lowSocLayout = servicePane.findViewById(R.id.low_soc_layout); @@ -1105,47 +1125,52 @@ public class MainActivity extends AppCompatActivity { }, "abrp-test").start(); } + /** + * A single choice as buttons on the page (choice_chip.xml), in place of a drop-down: a + * drop-down opens a window of its own that is not full screen, and the car's bars came back + * while it was open. Rebinding replaces the buttons, so it may be called again. + */ + private void bindChoices(com.google.android.material.chip.ChipGroup group, String[] labels, int selected, + java.util.function.IntConsumer onPick) { + group.setOnCheckedStateChangeListener(null); + group.removeAllViews(); + for (int i = 0; i < labels.length; i++) { + com.google.android.material.chip.Chip chip = (com.google.android.material.chip.Chip) + getLayoutInflater().inflate(R.layout.choice_chip, group, false); + chip.setId(View.generateViewId()); + chip.setText(labels[i]); + chip.setTag(i); + group.addView(chip); + if (i == selected) chip.setChecked(true); + } + group.setOnCheckedStateChangeListener((g, checkedIds) -> { + if (checkedIds.isEmpty()) return; + View chosen = g.findViewById(checkedIds.get(0)); + if (chosen != null) onPick.accept((Integer) chosen.getTag()); + }); + } + /** The accent colour; a change recreates the activity so every view takes it. */ private void bindAccentColor() { - android.widget.Spinner spinner = settingsPane.findViewById(R.id.accent_spinner); AccentColor[] colors = AccentColor.values(); String[] labels = new String[colors.length]; for (int i = 0; i < colors.length; i++) labels[i] = colors[i].label; - android.widget.ArrayAdapter adapter = new android.widget.ArrayAdapter<>( - this, R.layout.spinner_value, labels); - adapter.setDropDownViewResource(R.layout.spinner_dropdown); - spinner.setAdapter(adapter); AccentColor current = AccentColor.fromPref(prefs.getString(AccentColor.PREF_KEY, null)); - spinner.setSelection(current.ordinal()); - spinner.setOnItemSelectedListener(new android.widget.AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(android.widget.AdapterView parent, View view, int position, long id) { - if (colors[position] == current) return; - prefs.edit().putString(AccentColor.PREF_KEY, colors[position].name()).apply(); - recreate(); - } - @Override public void onNothingSelected(android.widget.AdapterView parent) { } + bindChoices(settingsPane.findViewById(R.id.accent_choices), labels, current.ordinal(), position -> { + if (colors[position] == current) return; + prefs.edit().putString(AccentColor.PREF_KEY, colors[position].name()).apply(); + recreate(); }); } /** Tyre pressure unit: display only, read on every refresh of the Overview. */ private void bindTireUnit() { - android.widget.Spinner spinner = settingsPane.findViewById(R.id.tire_unit_spinner); PressureUnit[] units = PressureUnit.values(); String[] labels = new String[units.length]; for (int i = 0; i < units.length; i++) labels[i] = units[i].label; - android.widget.ArrayAdapter adapter = new android.widget.ArrayAdapter<>( - this, R.layout.spinner_value, labels); - adapter.setDropDownViewResource(R.layout.spinner_dropdown); - spinner.setAdapter(adapter); - spinner.setSelection(PressureUnit.fromPref(prefs.getString(PressureUnit.PREF_KEY, null)).ordinal()); - spinner.setOnItemSelectedListener(new android.widget.AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(android.widget.AdapterView parent, View view, int position, long id) { - prefs.edit().putString(PressureUnit.PREF_KEY, units[position].name()).apply(); - } - @Override public void onNothingSelected(android.widget.AdapterView parent) { } - }); + int current = PressureUnit.fromPref(prefs.getString(PressureUnit.PREF_KEY, null)).ordinal(); + bindChoices(settingsPane.findViewById(R.id.tire_unit_choices), labels, current, position -> + prefs.edit().putString(PressureUnit.PREF_KEY, units[position].name()).apply()); } /** The service reads these settings on every tick, so a change applies without a restart. */ @@ -1161,19 +1186,9 @@ public class MainActivity extends AppCompatActivity { : getResources().getQuantityString(R.plurals.interval_seconds, sec, sec); if (sec == current.intervalSec) selected = i; } - android.widget.ArrayAdapter adapter = new android.widget.ArrayAdapter<>( - this, R.layout.spinner_value, labels); - adapter.setDropDownViewResource(R.layout.spinner_dropdown); - intervalSpinner.setAdapter(adapter); - intervalSpinner.setSelection(selected); - intervalSpinner.setOnItemSelectedListener(new android.widget.AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(android.widget.AdapterView parent, View view, int position, long id) { + bindChoices(intervalChoices, labels, selected, position -> prefs.edit().putInt(UploadSettings.KEY_INTERVAL_SEC, - UploadSettings.INTERVAL_CHOICES_SEC[position]).apply(); - } - @Override public void onNothingSelected(android.widget.AdapterView parent) { } - }); + UploadSettings.INTERVAL_CHOICES_SEC[position]).apply()); boostSwitch.setOnCheckedChangeListener(null); boostSwitch.setChecked(current.boostLowSoc); @@ -1462,13 +1477,14 @@ public class MainActivity extends AppCompatActivity { if (consumptionPeriod != ConsumptionTracker.Period.TRIP_A && consumptionPeriod != ConsumptionTracker.Period.TRIP_B) return; String name = getString(consumptionPeriod == ConsumptionTracker.Period.TRIP_A ? R.string.consumption_trip_a : R.string.consumption_trip_b); - new MaterialAlertDialogBuilder(this).setTitle(getString(R.string.trip_reset_title, name)) + showKeepingFullScreen(new MaterialAlertDialogBuilder(this) + .setTitle(getString(R.string.trip_reset_title, name)) .setMessage(R.string.trip_reset_message) .setNegativeButton(R.string.pair_cancel, null) .setPositiveButton(R.string.trip_reset_confirm, (d, w) -> { consumptionTracker.reset(consumptionPeriod); refreshConsumption(); - }).show(); + }).create()); }); } @@ -1671,7 +1687,7 @@ public class MainActivity extends AppCompatActivity { dialog.getDatePicker().setMaxDate(System.currentTimeMillis()); dialog.getDatePicker().setMinDate(java.time.LocalDate.now().minusMonths(4) .atStartOfDay(zone).toInstant().toEpochMilli()); - dialog.show(); + showKeepingFullScreen(dialog); } /** diff --git a/app/src/main/res/color/choice_background.xml b/app/src/main/res/color/choice_background.xml new file mode 100644 index 0000000..c941703 --- /dev/null +++ b/app/src/main/res/color/choice_background.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/color/choice_stroke.xml b/app/src/main/res/color/choice_stroke.xml new file mode 100644 index 0000000..c0e21ef --- /dev/null +++ b/app/src/main/res/color/choice_stroke.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/color/choice_text.xml b/app/src/main/res/color/choice_text.xml new file mode 100644 index 0000000..60b46ae --- /dev/null +++ b/app/src/main/res/color/choice_text.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/layout-w1000dp/pane_service.xml b/app/src/main/res/layout-w1000dp/pane_service.xml index c4dee5b..bdff841 100644 --- a/app/src/main/res/layout-w1000dp/pane_service.xml +++ b/app/src/main/res/layout-w1000dp/pane_service.xml @@ -27,7 +27,7 @@ - + diff --git a/app/src/main/res/layout-w1000dp/pane_settings.xml b/app/src/main/res/layout-w1000dp/pane_settings.xml index 6e9df62..a096c71 100644 --- a/app/src/main/res/layout-w1000dp/pane_settings.xml +++ b/app/src/main/res/layout-w1000dp/pane_settings.xml @@ -9,9 +9,9 @@ - + - + diff --git a/app/src/main/res/layout/choice_chip.xml b/app/src/main/res/layout/choice_chip.xml new file mode 100644 index 0000000..f0f8440 --- /dev/null +++ b/app/src/main/res/layout/choice_chip.xml @@ -0,0 +1,8 @@ + + + diff --git a/app/src/main/res/layout/pane_service.xml b/app/src/main/res/layout/pane_service.xml index d1b1d05..d29a516 100644 --- a/app/src/main/res/layout/pane_service.xml +++ b/app/src/main/res/layout/pane_service.xml @@ -24,7 +24,7 @@ - + diff --git a/app/src/main/res/layout/pane_settings.xml b/app/src/main/res/layout/pane_settings.xml index 978fbd8..0a5b350 100644 --- a/app/src/main/res/layout/pane_settings.xml +++ b/app/src/main/res/layout/pane_settings.xml @@ -8,9 +8,9 @@ - + - + diff --git a/app/src/main/res/layout/spinner_dropdown.xml b/app/src/main/res/layout/spinner_dropdown.xml deleted file mode 100644 index 562c36f..0000000 --- a/app/src/main/res/layout/spinner_dropdown.xml +++ /dev/null @@ -1,8 +0,0 @@ - - diff --git a/app/src/main/res/layout/spinner_value.xml b/app/src/main/res/layout/spinner_value.xml deleted file mode 100644 index 59ab3bf..0000000 --- a/app/src/main/res/layout/spinner_value.xml +++ /dev/null @@ -1,8 +0,0 @@ - - diff --git a/app/src/main/res/values/styles_ev.xml b/app/src/main/res/values/styles_ev.xml index 0defd39..97533f0 100644 --- a/app/src/main/res/values/styles_ev.xml +++ b/app/src/main/res/values/styles_ev.xml @@ -96,4 +96,20 @@ + + +