diff --git a/app/androidutils.cpp b/app/androidutils.cpp index 03a3e85c1..c6fe3c9e6 100644 --- a/app/androidutils.cpp +++ b/app/androidutils.cpp @@ -186,40 +186,6 @@ QString AndroidUtils::getDeviceModel() return deviceModel; } -QVector AndroidUtils::getSafeArea() -{ - QVector ret; - -#ifdef ANDROID - const auto activity = QJniObject( QNativeInterface::QAndroidApplication::context() ); - const auto safeArrayStringObj = activity.callMethod( "getSafeArea", "()Ljava/lang/String;" ); - - if ( safeArrayStringObj.isValid() ) - { - const QString safeArrayString = safeArrayStringObj.toString(); - - QStringList stringParts = safeArrayString.split( "," ); - if ( stringParts.length() != 4 ) - { - CoreUtils::log( "SafeArea", "Android returned malformed string from getSafeArea method" ); - return ret; - } - - const int top = stringParts[0].toInt(); // top inset - const int right = stringParts[1].toInt(); // right inset - const int bottom = stringParts[2].toInt(); // bottom inset - const int left = stringParts[3].toInt(); // left inset - - ret << top << right << bottom << left; - return ret; - } - - CoreUtils::log( "SafeArea", "Android returned null from getSafeArea method" ); - return ret; -#endif - return ret; -} - void AndroidUtils::hideSplashScreen() { #ifdef ANDROID diff --git a/app/androidutils.h b/app/androidutils.h index f5f3752bf..b98e7a44f 100644 --- a/app/androidutils.h +++ b/app/androidutils.h @@ -58,8 +58,6 @@ class AndroidUtils: public QObject static QString getManufacturer(); static QString getDeviceModel(); - Q_INVOKABLE QVector getSafeArea(); - static void hideSplashScreen(); /** diff --git a/app/ios/iosutils.cpp b/app/ios/iosutils.cpp index e261ac7c6..c2612978c 100644 --- a/app/ios/iosutils.cpp +++ b/app/ios/iosutils.cpp @@ -59,14 +59,6 @@ QString IosUtils::readExif( const QString &filepath, const QString &tag ) #endif } -QVector IosUtils::getSafeArea() -{ -#ifdef Q_OS_IOS - return getSafeAreaImpl(); -#endif - return QVector(); -} - QString IosUtils::getManufacturer() { #ifdef Q_OS_IOS diff --git a/app/ios/iosutils.h b/app/ios/iosutils.h index 9bcb0b474..1a6e06aa9 100644 --- a/app/ios/iosutils.h +++ b/app/ios/iosutils.h @@ -40,8 +40,6 @@ class IosUtils: public QObject IOSImagePicker *imagePicker() const; static QString readExif( const QString &filepath, const QString &tag ); - Q_INVOKABLE QVector getSafeArea(); - Q_INVOKABLE static bool openFile( const QString &filePath ); static Q_INVOKABLE QString getManufacturer(); @@ -68,8 +66,6 @@ class IosUtils: public QObject */ void setIdleTimerDisabled(); - QVector getSafeAreaImpl(); - static QString getManufacturerImpl(); static QString getDeviceModelImpl(); static bool openFileImpl( const QString &filePath ); diff --git a/app/ios/iosutils.mm b/app/ios/iosutils.mm index 23e4b0856..163b5ffc5 100644 --- a/app/ios/iosutils.mm +++ b/app/ios/iosutils.mm @@ -26,26 +26,6 @@ [[UIApplication sharedApplication] setIdleTimerDisabled:YES]; } -QVector IosUtils::getSafeAreaImpl() -{ - QVector ret; - - if ( @available( iOS 11.0, * ) ) - { - UIWindow *window = UIApplication.sharedApplication.windows.firstObject; - - int top = window.safeAreaInsets.top; - int right = window.safeAreaInsets.right; - int bottom = window.safeAreaInsets.bottom; - int left = window.safeAreaInsets.left; - - ret << top << right << bottom << left; - return ret; - } - - return ret; -} - QString IosUtils::getManufacturerImpl() { return "APPLE INC."; diff --git a/app/main.cpp b/app/main.cpp index e91cb18ec..164adaaf6 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -812,46 +812,6 @@ int main( int argc, char *argv[] ) engine.rootContext()->setContextProperty( "__version", version ); - // Set safe areas for mobile devices -#ifdef ANDROID - auto safeAreaInsets = androidUtils.getSafeArea(); - - if ( safeAreaInsets.length() == 4 ) - { - // Values from Android API must be scaled with dpr - qreal dpr = QGuiApplication::primaryScreen()->devicePixelRatio(); - style->setSafeAreaTop( safeAreaInsets[0] / dpr ); - style->setSafeAreaRight( safeAreaInsets[1] / dpr ); - style->setSafeAreaBottom( safeAreaInsets[2] / dpr ); - style->setSafeAreaLeft( safeAreaInsets[3] / dpr ); - } -#elif defined( Q_OS_IOS ) - - // - // After migration to Qt 6.8.3, we can no longer reliably read the safe area on app startup (on iOS). - // It appears the UIWindow is not fully initialized, returning zero safe area insets. - // However, the window is correctly initialized once the event loop begins processing events. - // Therefore, we delay the safe area retrieval until after the event loop starts. - // This is a temporary workaround and might be replaced in the future by - // the more robust approach described in https://www.qt.io/blog/expanded-client-areas-and-safe-areas-in-qt-6.9. - // - - const int SAFE_AREA_REFRESH_DELAY_MS = 10; - - QTimer::singleShot( SAFE_AREA_REFRESH_DELAY_MS, &lambdaContext, [&iosUtils, &style]() - { - auto safeAreaInsets = iosUtils.getSafeArea(); - - if ( safeAreaInsets.length() == 4 ) - { - style->setSafeAreaTop( safeAreaInsets[0] ); - style->setSafeAreaRight( safeAreaInsets[1] ); - style->setSafeAreaBottom( safeAreaInsets[2] ); - style->setSafeAreaLeft( safeAreaInsets[3] ); - } - } ); - -#endif // Set simulated position for desktop builds #ifdef DESKTOP_OS @@ -914,6 +874,22 @@ int main( int argc, char *argv[] ) if ( QQuickWindow *quickWindow = qobject_cast( object ) ) { quickWindow->setIcon( QIcon( logoUrl ) ); + +#if defined( ANDROID ) || defined( Q_OS_IOS ) + // Qt 6.9+ provides QWindow::safeAreaMargins() natively for both Android and iOS. + + auto applyMargins = [style, quickWindow]() + { + const QMargins m = quickWindow->safeAreaMargins(); + style->setSafeAreaTop( m.top() ); + style->setSafeAreaRight( m.right() ); + style->setSafeAreaBottom( m.bottom() ); + style->setSafeAreaLeft( m.left() ); + }; + + QObject::connect( quickWindow, &QWindow::safeAreaMarginsChanged, &lambdaContext, applyMargins ); + QTimer::singleShot( 10, &lambdaContext, applyMargins ); +#endif } #ifdef DESKTOP_OS diff --git a/app/qml/components/MMPage.qml b/app/qml/components/MMPage.qml index 4328e88da..d2da1cbc8 100644 --- a/app/qml/components/MMPage.qml +++ b/app/qml/components/MMPage.qml @@ -27,6 +27,13 @@ Page { enum BottomMarginPolicy { UseMargin, PaintBehindSystemBar } + // Qt 6.9+ Page automatically adds safe area margins as padding. + // MMPageHeader and page content already handle safe areas manually. + topPadding: 0 + bottomPadding: 0 + leftPadding: 0 + rightPadding: 0 + implicitHeight: ApplicationWindow.window?.height ?? 0 implicitWidth: ApplicationWindow.window?.width ?? 0 diff --git a/app/qml/form/MMFormController.qml b/app/qml/form/MMFormController.qml index c0abccd6d..6b098ac52 100644 --- a/app/qml/form/MMFormController.qml +++ b/app/qml/form/MMFormController.qml @@ -59,6 +59,13 @@ Item { Drawer { id: drawer + // Qt 6.9+ Popup/Drawer automatically applies safe area margins as padding. + // MMFormPage and MMPreviewDrawer already handle safe areas manually. + topPadding: 0 + bottomPadding: 0 + leftPadding: 0 + rightPadding: 0 + StateGroup { id: statesManager diff --git a/app/qml/form/MMFormPage.qml b/app/qml/form/MMFormPage.qml index fe13097ba..8a1cf0037 100644 --- a/app/qml/form/MMFormPage.qml +++ b/app/qml/form/MMFormPage.qml @@ -24,6 +24,14 @@ import "../dialogs" Page { id: root + // Qt 6.9+ Page automatically adds safe area margins as padding. + // MMPageHeader and MMToolbar already handle safe areas manually, so + // we set them to zero to avoid double-applying the insets. + topPadding: 0 + bottomPadding: 0 + leftPadding: 0 + rightPadding: 0 + /** * When feature in the form is saved. */ diff --git a/app/qml/layers/MMLayerDetailPage.qml b/app/qml/layers/MMLayerDetailPage.qml index cc9ff8380..9f29d5dc8 100644 --- a/app/qml/layers/MMLayerDetailPage.qml +++ b/app/qml/layers/MMLayerDetailPage.qml @@ -20,6 +20,12 @@ import "../inputs" Page { id: root + // Qt 6.9+ auto-applies safe area padding; child MMPage instances handle it manually. + topPadding: 0 + bottomPadding: 0 + leftPadding: 0 + rightPadding: 0 + signal close() signal featureClicked( var featurePair ) signal addFeatureClicked( var targetLayer ) diff --git a/app/qml/main.qml b/app/qml/main.qml index 8c77c4610..cfeedeb51 100644 --- a/app/qml/main.qml +++ b/app/qml/main.qml @@ -52,10 +52,13 @@ ApplicationWindow { title: "Mergin Maps" // Do not translate - readonly property bool isPortraitOrientation: ( Screen.primaryOrientation === Qt.PortraitOrientation - || Screen.primaryOrientation === Qt.InvertedPortraitOrientation ) - - onIsPortraitOrientationChanged: recalculateSafeArea() + // Qt 6.9+ ApplicationWindow automatically adds safe area margins as padding to its + // contentItem. Since this app manages safe areas manually via __style, + // we override the automatic padding to avoid double-applying the insets. + topPadding: 0 + bottomPadding: 0 + leftPadding: 0 + rightPadding: 0 // start window where it was closed last time onXChanged: storeWindowPosition() @@ -1231,31 +1234,6 @@ ApplicationWindow { } } - function recalculateSafeArea() { - let safeArea = [] - - // Should be merged in future with the same code in main.cpp - if ( Qt.platform.os === "ios" ) { - safeArea = Array.from( __iosUtils.getSafeArea() ) - } - else if ( Qt.platform.os === "android" ) { - safeArea = Array.from( __androidUtils.getSafeArea() ) - - // Values from Android API must be divided by dpr - safeArea[0] = safeArea[0] / Screen.devicePixelRatio - safeArea[1] = safeArea[1] / Screen.devicePixelRatio - safeArea[2] = safeArea[2] / Screen.devicePixelRatio - safeArea[3] = safeArea[3] / Screen.devicePixelRatio - } - - if ( safeArea.length === 4 ) { - __style.safeAreaTop = safeArea[0] - __style.safeAreaRight = safeArea[1] - __style.safeAreaBottom = safeArea[2] - __style.safeAreaLeft = safeArea[3] - } - } - function storeWindowPosition() { if ( Qt.platform.os !== "ios" && Qt.platform.os !== "android") {