1
0

Merge pull request #10178 from f321x/android15_edge_to_edge_compatibility

android: make app compatible with edge-to-edge layout
This commit is contained in:
ghost43
2025-09-01 14:02:26 +00:00
committed by GitHub
17 changed files with 148 additions and 2 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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'])

View File

@@ -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()
@@ -31,6 +32,10 @@ ElDialog {
running: Daemon.loading
}
Item {
Layout.preferredHeight: 20
}
}
Connections {

View File

@@ -21,6 +21,7 @@ ElDialog {
anchors.centerIn: parent
padding: 0
needsSystemBarPadding: false
width: rootLayout.width

View File

@@ -15,6 +15,8 @@ ElDialog {
property string selectedPubkey
needsSystemBarPadding: false
anchors.centerIn: parent
padding: 0

View File

@@ -25,6 +25,7 @@ ElDialog {
anchors.centerIn: parent
padding: 0
needsSystemBarPadding: false
ColumnLayout {
spacing: 0

View File

@@ -20,6 +20,7 @@ ElDialog {
anchors.centerIn: parent
width: parent.width * 4/5
padding: 0
needsSystemBarPadding: false
ColumnLayout {
id: rootLayout

View File

@@ -25,6 +25,7 @@ ElDialog {
focus: true
closePolicy: canCancel ? Popup.CloseOnEscape | Popup.CloseOnPressOutside : Popup.NoAutoClose
allowClose: canCancel
needsSystemBarPadding: false
anchors.centerIn: parent

View File

@@ -20,6 +20,7 @@ ElDialog {
property bool isLightning: false
padding: 0
needsSystemBarPadding: false
ColumnLayout {
width: parent.width

View File

@@ -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

View File

@@ -16,6 +16,7 @@ ElDialog {
anchors.centerIn: parent
padding: 0
needsSystemBarPadding: false
width: rootPane.width

View File

@@ -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 {

View File

@@ -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;
});
}
}

View File

@@ -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.02)
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):

View File

@@ -26,6 +26,7 @@ ElDialog {
anchors.centerIn: parent
padding: 0
needsSystemBarPadding: false
width: rootLayout.width