diff --git a/app/build.gradle b/app/build.gradle
index c2c0330..09ee60c 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -34,8 +34,8 @@ android {
applicationId "com.stinnesbeck.nilsmg4"
minSdk 27
targetSdk 36
- versionCode 60
- versionName "2.4.2"
+ versionCode 61
+ versionName "2.4.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 3fe59c7..fb7d9d6 100644
--- a/app/src/main/java/com/evsuite/abrp/MainActivity.java
+++ b/app/src/main/java/com/evsuite/abrp/MainActivity.java
@@ -483,10 +483,17 @@ public class MainActivity extends AppCompatActivity {
float density = getResources().getDisplayMetrics().density;
int pad = (int) (24 * density);
+ // On the head unit's wide, low screen the QR code sits left of the text: stacked, the
+ // code and status below a 240 dp QR code were cut off. Scrollable either way, so
+ // nothing can be clipped on any screen.
+ android.util.DisplayMetrics metrics = getResources().getDisplayMetrics();
+ boolean wide = metrics.widthPixels > metrics.heightPixels;
android.widget.LinearLayout panel = new android.widget.LinearLayout(this);
- panel.setOrientation(android.widget.LinearLayout.VERTICAL);
- panel.setGravity(android.view.Gravity.CENTER_HORIZONTAL);
+ panel.setOrientation(wide ? android.widget.LinearLayout.HORIZONTAL : android.widget.LinearLayout.VERTICAL);
+ panel.setGravity(wide ? android.view.Gravity.CENTER_VERTICAL : android.view.Gravity.CENTER_HORIZONTAL);
panel.setPadding(pad, pad, pad, pad);
+ android.widget.LinearLayout column = new android.widget.LinearLayout(this);
+ column.setOrientation(android.widget.LinearLayout.VERTICAL);
TextView instructions = new TextView(this);
instructions.setTextAppearance(R.style.Text_EV_Secondary);
ImageView qr = new ImageView(this);
@@ -501,17 +508,37 @@ public class MainActivity extends AppCompatActivity {
TextView status = new TextView(this);
status.setTextAppearance(R.style.Text_EV_Body);
status.setText(R.string.pair_starting);
- panel.addView(instructions);
android.widget.LinearLayout.LayoutParams qrParams = new android.widget.LinearLayout.LayoutParams(qrSize, qrSize);
- qrParams.topMargin = pad / 2;
- panel.addView(qr, qrParams);
- panel.addView(code);
- panel.addView(status);
+ android.widget.LinearLayout.LayoutParams columnParams;
+ if (wide) {
+ qrParams.rightMargin = pad;
+ columnParams = new android.widget.LinearLayout.LayoutParams(
+ 0, android.view.ViewGroup.LayoutParams.WRAP_CONTENT, 1f);
+ panel.addView(qr, qrParams);
+ panel.addView(column, columnParams);
+ column.addView(instructions);
+ } else {
+ qrParams.topMargin = pad / 2;
+ qrParams.bottomMargin = pad / 2;
+ columnParams = new android.widget.LinearLayout.LayoutParams(
+ android.view.ViewGroup.LayoutParams.MATCH_PARENT, android.view.ViewGroup.LayoutParams.WRAP_CONTENT);
+ panel.addView(instructions);
+ panel.addView(qr, qrParams);
+ panel.addView(column, columnParams);
+ }
+ android.widget.LinearLayout.LayoutParams codeParams = new android.widget.LinearLayout.LayoutParams(
+ android.view.ViewGroup.LayoutParams.WRAP_CONTENT, android.view.ViewGroup.LayoutParams.WRAP_CONTENT);
+ codeParams.topMargin = pad / 2;
+ codeParams.bottomMargin = pad / 2;
+ column.addView(code, codeParams);
+ column.addView(status);
+ android.widget.ScrollView scroll = new android.widget.ScrollView(this);
+ scroll.addView(panel);
final boolean[] cancelled = {false};
androidx.appcompat.app.AlertDialog dialog = new MaterialAlertDialogBuilder(this)
.setTitle(R.string.pair_title)
- .setView(panel)
+ .setView(scroll)
.setNegativeButton(R.string.pair_cancel, null)
.create();
// Shown only once the server has answered, so a failure leaves no empty QR frame.
@@ -527,7 +554,9 @@ public class MainActivity extends AppCompatActivity {
PairingClient.Request request = PairingClient.start(url, installationId);
android.graphics.Bitmap bitmap = QrCode.generate(request.verificationUriComplete, 416);
runOnUiThread(() -> {
- instructions.setText(getString(R.string.pair_scan, request.verificationUri));
+ // The code is in the sentence as well as below it, so it is readable
+ // wherever the text is.
+ instructions.setText(getString(R.string.pair_scan, request.verificationUri, request.userCode));
if (bitmap != null) {
qr.setImageBitmap(bitmap);
qr.setVisibility(View.VISIBLE);
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index f942d80..b92d940 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -58,7 +58,7 @@
Pair with phone
Shows a QR code: scan it with your phone and approve with the approval password. The token below is then filled in for you.
Pair with phone
- Scan with your phone, or open %1$s and enter this code:
+ Scan the QR code with your phone, or open %1$s and enter the code %2$s:
Waiting for approval…
Contacting the server…
Paired — this car now has its own token