From c36527274598455a30e350632c4fe08f41409258 Mon Sep 17 00:00:00 2001 From: f321x Date: Mon, 25 Aug 2025 14:38:44 +0200 Subject: [PATCH 1/7] android: bump target sdk to 35 --- contrib/android/buildozer_qml.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/android/buildozer_qml.spec b/contrib/android/buildozer_qml.spec index a0e4e6709..e19bcac5a 100644 --- a/contrib/android/buildozer_qml.spec +++ b/contrib/android/buildozer_qml.spec @@ -108,7 +108,7 @@ android.permissions = INTERNET, CAMERA, WRITE_EXTERNAL_STORAGE, POST_NOTIFICATIO android.api = 31 # (int) Android targetSdkVersion -android.target_sdk_version = 34 +android.target_sdk_version = 35 # (int) Minimum API required. You will need to set the android.ndk_api to be as low as this value. android.minapi = 23 From 4b30b097a9cd01be61e424dfd818ffbb6afa27b1 Mon Sep 17 00:00:00 2001 From: f321x Date: Mon, 25 Aug 2025 15:07:45 +0200 Subject: [PATCH 2/7] qml: qeapp: add methods to retrive system bar heights --- electrum/gui/qml/qeapp.py | 50 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/electrum/gui/qml/qeapp.py b/electrum/gui/qml/qeapp.py index 45e9feaf2..28764c0d4 100644 --- a/electrum/gui/qml/qeapp.py +++ b/electrum/gui/qml/qeapp.py @@ -22,6 +22,7 @@ from electrum.base_crash_reporter import BaseCrashReporter, EarlyExceptionsQueue from electrum.network import Network from electrum.plugin import run_hook from electrum.gui.common_qt.util import get_font_id +from electrum.util import profiler from .qeconfig import QEConfig from .qedaemon import QEDaemon @@ -60,6 +61,7 @@ if 'ANDROID_DATA' in os.environ: jString = autoclass('java.lang.String') jIntent = autoclass('android.content.Intent') jview = jpythonActivity.getWindow().getDecorView() + systemSdkVersion = autoclass('android.os.Build$VERSION').SDK_INT notification = None @@ -414,6 +416,54 @@ class QEAppController(BaseCrashReporter, QObject): self._secureWindow = secure self.secureWindowChanged.emit() + @pyqtSlot(result=bool) + def enforcesEdgeToEdge(self) -> bool: + if not self.isAndroid(): + return False + return bool(systemSdkVersion >= 35) + + @profiler(min_threshold=0.05) + def _getSystemBarHeight(self, bar_type: str) -> int: + if not self.enforcesEdgeToEdge(): + return 0 + assert systemSdkVersion >= 30, \ + f"Android WindowInsets unavailable on {systemSdkVersion=}" + try: + root_insets = jview.getRootWindowInsets() + window_insets_type = autoclass('android.view.WindowInsets$Type') + + if bar_type == 'status': + ins = root_insets.getInsets(window_insets_type.statusBars()) + elif bar_type == 'navigation': + ins = root_insets.getInsets(window_insets_type.navigationBars()) + else: + raise ValueError(f"Invalid bar_type: {bar_type}") + + # Get the display metrics to convert pixels to dp + display_metrics = jpythonActivity.getResources().getDisplayMetrics() + density = display_metrics.density + + height = int(max(ins.bottom, ins.right, ins.left, ins.top, 0)) + if not height > 0: + return 0 + + # Convert from pixels to dp for QML + height_dp = int(height / density) + + self.logger.debug(f"_getSystemBarHeight: {height=}, {height_dp=}, {bar_type=}") + return max(0, height_dp) + except Exception as e: + self.logger.debug(f"{bar_type} fallback due to: {e!r}") + return 0 + + @pyqtSlot(result=int) + def getStatusBarHeight(self) -> int: + return self._getSystemBarHeight('status') + + @pyqtSlot(result=int) + def getNavigationBarHeight(self) -> int: + return self._getSystemBarHeight('navigation') + class ElectrumQmlApplication(QGuiApplication): From 53595e1be7b88e3426ec456574c86408f2f3dd9c Mon Sep 17 00:00:00 2001 From: f321x Date: Tue, 26 Aug 2025 17:32:20 +0200 Subject: [PATCH 3/7] qml: pad main view if e2e enforcement is active --- electrum/gui/qml/components/main.qml | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/electrum/gui/qml/components/main.qml b/electrum/gui/qml/components/main.qml index 826d7a27d..cfdbf4eeb 100644 --- a/electrum/gui/qml/components/main.qml +++ b/electrum/gui/qml/components/main.qml @@ -19,6 +19,9 @@ ApplicationWindow visible: false // initial value + readonly property int statusBarHeight: AppController ? AppController.getStatusBarHeight() : 0 + readonly property int navigationBarHeight: AppController ? AppController.getNavigationBarHeight() : 0 + // dimensions ignored on android width: 480 height: 800 @@ -117,6 +120,9 @@ ApplicationWindow header: ToolBar { id: toolbar + + // Add top margin for status bar on Android when using edge-to-edge + topPadding: app.statusBarHeight background: Rectangle { implicitHeight: 48 @@ -133,7 +139,7 @@ ApplicationWindow spacing: 0 anchors.left: parent.left anchors.right: parent.right - height: toolbar.height + height: toolbar.availableHeight RowLayout { id: toolbarTopLayout @@ -277,6 +283,13 @@ ApplicationWindow } } } + + // Add bottom padding for navigation bar on Android when UI is edge-to-edge + Item { + visible: app.navigationBarHeight > 0 + Layout.fillWidth: true + Layout.preferredHeight: app.navigationBarHeight + } } Timer { From 5f0180910c1e420e665e81147e132a7a3baee838 Mon Sep 17 00:00:00 2001 From: f321x Date: Wed, 27 Aug 2025 09:24:08 +0200 Subject: [PATCH 4/7] qml: add padding to ElDialog for android e2e --- electrum/gui/qml/components/BIP39RecoveryDialog.qml | 2 ++ .../gui/qml/components/LnurlPayRequestDialog.qml | 1 + electrum/gui/qml/components/LoadingWalletDialog.qml | 1 + electrum/gui/qml/components/MessageDialog.qml | 1 + .../gui/qml/components/NostrSwapServersDialog.qml | 2 ++ electrum/gui/qml/components/OpenWalletDialog.qml | 1 + electrum/gui/qml/components/PasswordDialog.qml | 1 + electrum/gui/qml/components/Pin.qml | 1 + electrum/gui/qml/components/ReceiveDetailsDialog.qml | 1 + electrum/gui/qml/components/controls/ElDialog.qml | 12 ++++++++++++ electrum/gui/qml/components/controls/HelpDialog.qml | 1 + electrum/gui/qml/qeapp.py | 2 +- .../plugins/psbt_nostr/qml/PsbtReceiveDialog.qml | 1 + 13 files changed, 26 insertions(+), 1 deletion(-) diff --git a/electrum/gui/qml/components/BIP39RecoveryDialog.qml b/electrum/gui/qml/components/BIP39RecoveryDialog.qml index cda047a78..6910d3de8 100644 --- a/electrum/gui/qml/components/BIP39RecoveryDialog.qml +++ b/electrum/gui/qml/components/BIP39RecoveryDialog.qml @@ -18,6 +18,8 @@ ElDialog { property string derivationPath property string scriptType + needsSystemBarPadding: false + z: 1 // raise z so it also covers wizard dialog anchors.centerIn: parent diff --git a/electrum/gui/qml/components/LnurlPayRequestDialog.qml b/electrum/gui/qml/components/LnurlPayRequestDialog.qml index ce6b85f5f..338dc08d8 100644 --- a/electrum/gui/qml/components/LnurlPayRequestDialog.qml +++ b/electrum/gui/qml/components/LnurlPayRequestDialog.qml @@ -16,6 +16,7 @@ ElDialog { property InvoiceParser invoiceParser padding: 0 + needsSystemBarPadding: false property bool commentValid: comment.text.length <= invoiceParser.lnurlData['comment_allowed'] property bool amountValid: amountBtc.textAsSats.satsInt >= parseInt(invoiceParser.lnurlData['min_sendable_sat']) diff --git a/electrum/gui/qml/components/LoadingWalletDialog.qml b/electrum/gui/qml/components/LoadingWalletDialog.qml index f40327f1d..2cd07ac7a 100644 --- a/electrum/gui/qml/components/LoadingWalletDialog.qml +++ b/electrum/gui/qml/components/LoadingWalletDialog.qml @@ -18,6 +18,7 @@ ElDialog { x: Math.floor((parent.width - implicitWidth) / 2) y: Math.floor((parent.height - implicitHeight) / 2) // anchors.centerIn: parent // this strangely pixelates the spinner + needsSystemBarPadding: false function open() { showTimer.start() diff --git a/electrum/gui/qml/components/MessageDialog.qml b/electrum/gui/qml/components/MessageDialog.qml index e80d4bb88..a2211ff75 100644 --- a/electrum/gui/qml/components/MessageDialog.qml +++ b/electrum/gui/qml/components/MessageDialog.qml @@ -21,6 +21,7 @@ ElDialog { anchors.centerIn: parent padding: 0 + needsSystemBarPadding: false width: rootLayout.width diff --git a/electrum/gui/qml/components/NostrSwapServersDialog.qml b/electrum/gui/qml/components/NostrSwapServersDialog.qml index 384789e0f..3330d782c 100644 --- a/electrum/gui/qml/components/NostrSwapServersDialog.qml +++ b/electrum/gui/qml/components/NostrSwapServersDialog.qml @@ -15,6 +15,8 @@ ElDialog { property string selectedPubkey + needsSystemBarPadding: false + anchors.centerIn: parent padding: 0 diff --git a/electrum/gui/qml/components/OpenWalletDialog.qml b/electrum/gui/qml/components/OpenWalletDialog.qml index 59319bcff..b6209dec5 100644 --- a/electrum/gui/qml/components/OpenWalletDialog.qml +++ b/electrum/gui/qml/components/OpenWalletDialog.qml @@ -25,6 +25,7 @@ ElDialog { anchors.centerIn: parent padding: 0 + needsSystemBarPadding: false ColumnLayout { spacing: 0 diff --git a/electrum/gui/qml/components/PasswordDialog.qml b/electrum/gui/qml/components/PasswordDialog.qml index 442f44a19..71a43d9a4 100644 --- a/electrum/gui/qml/components/PasswordDialog.qml +++ b/electrum/gui/qml/components/PasswordDialog.qml @@ -20,6 +20,7 @@ ElDialog { anchors.centerIn: parent width: parent.width * 4/5 padding: 0 + needsSystemBarPadding: false ColumnLayout { id: rootLayout diff --git a/electrum/gui/qml/components/Pin.qml b/electrum/gui/qml/components/Pin.qml index 261a37574..1f222c0a8 100644 --- a/electrum/gui/qml/components/Pin.qml +++ b/electrum/gui/qml/components/Pin.qml @@ -25,6 +25,7 @@ ElDialog { focus: true closePolicy: canCancel ? Popup.CloseOnEscape | Popup.CloseOnPressOutside : Popup.NoAutoClose allowClose: canCancel + needsSystemBarPadding: false anchors.centerIn: parent diff --git a/electrum/gui/qml/components/ReceiveDetailsDialog.qml b/electrum/gui/qml/components/ReceiveDetailsDialog.qml index 136af00a3..ab000913d 100644 --- a/electrum/gui/qml/components/ReceiveDetailsDialog.qml +++ b/electrum/gui/qml/components/ReceiveDetailsDialog.qml @@ -20,6 +20,7 @@ ElDialog { property bool isLightning: false padding: 0 + needsSystemBarPadding: false ColumnLayout { width: parent.width diff --git a/electrum/gui/qml/components/controls/ElDialog.qml b/electrum/gui/qml/components/controls/ElDialog.qml index 1a3abd4f1..74bd44c21 100644 --- a/electrum/gui/qml/components/controls/ElDialog.qml +++ b/electrum/gui/qml/components/controls/ElDialog.qml @@ -9,11 +9,16 @@ Dialog { property bool allowClose: true property string iconSource property bool resizeWithKeyboard: true + // inheriting classes can set needsSystemBarPadding this false to disable padding + property bool needsSystemBarPadding: true property bool _result: false // workaround: remember opened state, to inhibit closed -> closed event property bool _wasOpened: false + // Add bottom padding for Android navigation bar if needed + bottomPadding: needsSystemBarPadding ? app.navigationBarHeight : 0 + // called to finally close dialog after checks by onClosing handler in main.qml function doClose() { doReject() @@ -65,6 +70,13 @@ Dialog { header: ColumnLayout { spacing: 0 + // Add top padding for status bar on Android when using edge-to-edge + Item { + visible: needsSystemBarPadding && app.statusBarHeight > 0 + Layout.fillWidth: true + Layout.preferredHeight: app.statusBarHeight + } + RowLayout { spacing: 0 diff --git a/electrum/gui/qml/components/controls/HelpDialog.qml b/electrum/gui/qml/components/controls/HelpDialog.qml index f11a0d138..8d1b87d86 100644 --- a/electrum/gui/qml/components/controls/HelpDialog.qml +++ b/electrum/gui/qml/components/controls/HelpDialog.qml @@ -16,6 +16,7 @@ ElDialog { anchors.centerIn: parent padding: 0 + needsSystemBarPadding: false width: rootPane.width diff --git a/electrum/gui/qml/qeapp.py b/electrum/gui/qml/qeapp.py index 28764c0d4..3232cf6ca 100644 --- a/electrum/gui/qml/qeapp.py +++ b/electrum/gui/qml/qeapp.py @@ -422,7 +422,7 @@ class QEAppController(BaseCrashReporter, QObject): return False return bool(systemSdkVersion >= 35) - @profiler(min_threshold=0.05) + @profiler(min_threshold=0.02) def _getSystemBarHeight(self, bar_type: str) -> int: if not self.enforcesEdgeToEdge(): return 0 diff --git a/electrum/plugins/psbt_nostr/qml/PsbtReceiveDialog.qml b/electrum/plugins/psbt_nostr/qml/PsbtReceiveDialog.qml index 286324ff8..bb59d3fbc 100644 --- a/electrum/plugins/psbt_nostr/qml/PsbtReceiveDialog.qml +++ b/electrum/plugins/psbt_nostr/qml/PsbtReceiveDialog.qml @@ -26,6 +26,7 @@ ElDialog { anchors.centerIn: parent padding: 0 + needsSystemBarPadding: false width: rootLayout.width From 9871931bf3b836b6540276a1c8beb50bf9adc631 Mon Sep 17 00:00:00 2001 From: f321x Date: Wed, 27 Aug 2025 10:55:24 +0200 Subject: [PATCH 5/7] android: make QR code scanner e2e compatible Makes the Java QR code scanner edge-to-edge compatible by padding the hintTextView and the pasteButton. --- .../electrum/qr/SimpleScannerActivity.java | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/electrum/gui/qml/java_classes/org/electrum/qr/SimpleScannerActivity.java b/electrum/gui/qml/java_classes/org/electrum/qr/SimpleScannerActivity.java index 58eeeb81e..d6db0973e 100644 --- a/electrum/gui/qml/java_classes/org/electrum/qr/SimpleScannerActivity.java +++ b/electrum/gui/qml/java_classes/org/electrum/qr/SimpleScannerActivity.java @@ -2,6 +2,7 @@ package org.electrum.qr; import android.app.Activity; import android.os.Bundle; +import android.os.Build; import android.util.Log; import android.content.Intent; import android.Manifest; @@ -12,6 +13,7 @@ import android.content.Context; import android.content.pm.PackageManager; import android.view.View; import android.view.ViewGroup; +import android.view.WindowInsets; import android.widget.Button; import android.widget.TextView; import android.widget.Toast; @@ -68,6 +70,7 @@ public class SimpleScannerActivity extends Activity { } } }); + setupEdgeToEdge(); } @Override @@ -156,4 +159,51 @@ public class SimpleScannerActivity extends Activity { } } + private boolean enforcesEdgeToEdge() { + // if true the UI needs to be padded to be e2e compatible + return Build.VERSION.SDK_INT >= 35; + } + + private void setupEdgeToEdge() { + if (!enforcesEdgeToEdge()) { + return; + } + + // Get the root view and set up insets listener + getWindow().getDecorView().setOnApplyWindowInsetsListener((v, insets) -> { + android.graphics.Insets systemBars = insets.getInsets(WindowInsets.Type.systemBars()); + + // Apply padding to content frame to keep scanner focus area centered + ViewGroup contentFrame = findViewById(R.id.content_frame); + if (contentFrame != null) { + contentFrame.setPadding( + systemBars.left, + systemBars.top, + systemBars.right, + systemBars.bottom + ); + } + + // Apply top padding to hint text for status bar + TextView hintTextView = findViewById(R.id.hint); + if (hintTextView != null) { + hintTextView.setPadding( + hintTextView.getPaddingLeft(), + systemBars.top, + hintTextView.getPaddingRight(), + hintTextView.getPaddingBottom() + ); + } + + // Apply bottom margin to paste button for navigation bar + Button pasteButton = findViewById(R.id.paste_btn); + if (pasteButton != null) { + ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) pasteButton.getLayoutParams(); + params.bottomMargin = systemBars.bottom; + pasteButton.setLayoutParams(params); + } + + return insets; + }); + } } From f1dfe5e2486cfe1f464cf6041d07f52eae32030c Mon Sep 17 00:00:00 2001 From: f321x Date: Wed, 27 Aug 2025 14:02:28 +0200 Subject: [PATCH 6/7] qml: LoadingWalletDialog: add some padding add some padding at the bottom of the LoadingWalletDialog so the spinning circle is not directly at the bottom of the dialog, looks a bit nicer this way. --- electrum/gui/qml/components/LoadingWalletDialog.qml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/electrum/gui/qml/components/LoadingWalletDialog.qml b/electrum/gui/qml/components/LoadingWalletDialog.qml index 2cd07ac7a..2969cdc4d 100644 --- a/electrum/gui/qml/components/LoadingWalletDialog.qml +++ b/electrum/gui/qml/components/LoadingWalletDialog.qml @@ -32,6 +32,10 @@ ElDialog { running: Daemon.loading } + + Item { + Layout.preferredHeight: 20 + } } Connections { From 0263b5ecc139ff06d79dccb026643c094418cbe4 Mon Sep 17 00:00:00 2001 From: f321x Date: Thu, 28 Aug 2025 10:09:12 +0200 Subject: [PATCH 7/7] qml: adapt exception dialog to edge-to-edge layout Even though the exception dialog inherits from ElDialog the padding didn't work as it overwrites the properties of the ElDialog. So the padding has to be applied separately to the ExceptionDialog. --- electrum/gui/qml/components/ExceptionDialog.qml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/electrum/gui/qml/components/ExceptionDialog.qml b/electrum/gui/qml/components/ExceptionDialog.qml index e4159232a..9bc3a7b5b 100644 --- a/electrum/gui/qml/components/ExceptionDialog.qml +++ b/electrum/gui/qml/components/ExceptionDialog.qml @@ -18,10 +18,14 @@ ElDialog width: parent.width height: parent.height z: 1000 // assure topmost of all other dialogs. note: child popups need even higher! + // disable padding in ElDialog as it is overwritten here and shows no effect, this dialog needs padding though + needsSystemBarPadding: false header: null ColumnLayout { + anchors.topMargin: app.statusBarHeight // edge-to-edge layout padding + anchors.bottomMargin: app.navigationBarHeight anchors.fill: parent enabled: !_sending