diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index 1384eae42..3ae725637 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -470,7 +470,7 @@ jobs:
env:
ANDROID_BUILD_PLATFORM: android-36
- QT_VERSION: 6.9.3
+ QT_VERSION: 6.8.3
QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
diff --git a/client/android/AndroidManifest.xml b/client/android/AndroidManifest.xml
index 04001f63d..a0cddbb35 100644
--- a/client/android/AndroidManifest.xml
+++ b/client/android/AndroidManifest.xml
@@ -45,7 +45,7 @@
android:configChanges="uiMode|screenSize|smallestScreenSize|screenLayout|orientation|density
|fontScale|layoutDirection|locale|keyboard|keyboardHidden|navigation|mcc|mnc"
android:launchMode="singleInstance"
- android:windowSoftInputMode="stateUnchanged|adjustResize"
+ android:windowSoftInputMode="adjustResize|stateUnchanged"
android:enableOnBackInvokedCallback="false"
android:exported="true">
@@ -215,4 +215,4 @@
-
+
\ No newline at end of file
diff --git a/client/android/src/org/amnezia/vpn/AmneziaActivity.kt b/client/android/src/org/amnezia/vpn/AmneziaActivity.kt
index 4260c05e8..4b684215a 100644
--- a/client/android/src/org/amnezia/vpn/AmneziaActivity.kt
+++ b/client/android/src/org/amnezia/vpn/AmneziaActivity.kt
@@ -35,6 +35,10 @@ import android.widget.Toast
import androidx.annotation.MainThread
import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat
+import androidx.core.graphics.Insets
+import androidx.core.view.OnApplyWindowInsetsListener
+import androidx.core.view.ViewCompat
+import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import java.io.IOException
import kotlin.LazyThreadSafetyMode.NONE
@@ -300,6 +304,11 @@ class AmneziaActivity : QtActivity() {
isAppearanceLightStatusBars = false
isAppearanceLightNavigationBars = false
}
+
+ // Workaround for Android 14 (API 34+) IME adjustResize bug
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+ setupImeInsetsListener()
+ }
} else {
window.apply {
addFlags(LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
@@ -308,6 +317,34 @@ class AmneziaActivity : QtActivity() {
}
}
+ private fun setupImeInsetsListener() {
+ ViewCompat.setOnApplyWindowInsetsListener(window.decorView) { view, windowInsets ->
+ val imeInsets = windowInsets.getInsets(WindowInsetsCompat.Type.ime())
+ val imeVisible = windowInsets.isVisible(WindowInsetsCompat.Type.ime())
+
+ val imeHeight = if (imeVisible) imeInsets.bottom else 0
+
+ val density = resources.displayMetrics.density
+ val imeHeightDp = (imeHeight / density).toInt()
+
+ // Also track system bars (navigation bar, status bar) changes
+ val systemBarsInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
+ val navBarHeight = systemBarsInsets.bottom
+ val navBarHeightDp = (navBarHeight / density).toInt()
+ val statusBarHeight = systemBarsInsets.top
+ val statusBarHeightDp = (statusBarHeight / density).toInt()
+
+ mainScope.launch {
+ qtInitialized.await()
+ QtAndroidController.onImeInsetsChanged(imeHeightDp)
+ QtAndroidController.onSystemBarsInsetsChanged(navBarHeightDp, statusBarHeightDp)
+ }
+
+ // Return windowInsets instead of CONSUMED to allow proper handling
+ windowInsets
+ }
+ }
+
override fun onDestroy() {
Log.d(TAG, "Destroy Amnezia activity")
unregisterBroadcastReceiver(notificationStateReceiver)
diff --git a/client/android/src/org/amnezia/vpn/qt/QtAndroidController.kt b/client/android/src/org/amnezia/vpn/qt/QtAndroidController.kt
index 4af138a28..b77e77d66 100644
--- a/client/android/src/org/amnezia/vpn/qt/QtAndroidController.kt
+++ b/client/android/src/org/amnezia/vpn/qt/QtAndroidController.kt
@@ -28,4 +28,7 @@ object QtAndroidController {
external fun onAuthResult(result: Boolean)
external fun decodeQrCode(data: String): Boolean
+
+ external fun onImeInsetsChanged(heightDp: Int)
+ external fun onSystemBarsInsetsChanged(navBarHeightDp: Int, statusBarHeightDp: Int)
}
\ No newline at end of file
diff --git a/client/platforms/android/android_controller.cpp b/client/platforms/android/android_controller.cpp
index ee2e43e29..2d60ad849 100644
--- a/client/platforms/android/android_controller.cpp
+++ b/client/platforms/android/android_controller.cpp
@@ -99,7 +99,9 @@ bool AndroidController::initialize()
{"onFileOpened", "(Ljava/lang/String;)V", reinterpret_cast(onFileOpened)},
{"onConfigImported", "(Ljava/lang/String;)V", reinterpret_cast(onConfigImported)},
{"onAuthResult", "(Z)V", reinterpret_cast(onAuthResult)},
- {"decodeQrCode", "(Ljava/lang/String;)Z", reinterpret_cast(decodeQrCode)}
+ {"decodeQrCode", "(Ljava/lang/String;)Z", reinterpret_cast(decodeQrCode)},
+ {"onImeInsetsChanged", "(I)V", reinterpret_cast(onImeInsetsChanged)},
+ {"onSystemBarsInsetsChanged", "(II)V", reinterpret_cast(onSystemBarsInsetsChanged)}
};
QJniEnvironment env;
@@ -536,3 +538,23 @@ bool AndroidController::decodeQrCode(JNIEnv *env, jobject thiz, jstring data)
return ImportController::decodeQrCode(AndroidUtils::convertJString(env, data));
}
+// static
+void AndroidController::onImeInsetsChanged(JNIEnv *env, jobject thiz, jint heightDp)
+{
+ Q_UNUSED(env);
+ Q_UNUSED(thiz);
+
+ qDebug() << "Android IME insets changed: height =" << heightDp << "dp";
+ emit AndroidController::instance()->imeInsetsChanged(heightDp);
+}
+
+// static
+void AndroidController::onSystemBarsInsetsChanged(JNIEnv *env, jobject thiz, jint navBarHeightDp, jint statusBarHeightDp)
+{
+ Q_UNUSED(env);
+ Q_UNUSED(thiz);
+
+ qDebug() << "Android system bars insets changed: nav bar =" << navBarHeightDp << "dp, status bar =" << statusBarHeightDp << "dp";
+ emit AndroidController::instance()->systemBarsInsetsChanged(navBarHeightDp, statusBarHeightDp);
+}
+
diff --git a/client/platforms/android/android_controller.h b/client/platforms/android/android_controller.h
index b3b600602..a5a4b3d23 100644
--- a/client/platforms/android/android_controller.h
+++ b/client/platforms/android/android_controller.h
@@ -73,6 +73,8 @@ signals:
void importConfigFromOutside(QString config);
void initConnectionState(Vpn::ConnectionState state);
void authenticationResult(bool result);
+ void imeInsetsChanged(int heightDp);
+ void systemBarsInsetsChanged(int navBarHeightDp, int statusBarHeightDp);
private:
bool isWaitingStatus = true;
@@ -101,6 +103,8 @@ private:
static void onFileOpened(JNIEnv *env, jobject thiz, jstring uri);
static void onAuthResult(JNIEnv *env, jobject thiz, jboolean result);
static bool decodeQrCode(JNIEnv *env, jobject thiz, jstring data);
+ static void onImeInsetsChanged(JNIEnv *env, jobject thiz, jint heightDp);
+ static void onSystemBarsInsetsChanged(JNIEnv *env, jobject thiz, jint navBarHeightDp, jint statusBarHeightDp);
template
static auto callActivityMethod(const char *methodName, const char *signature, Args &&...args);
diff --git a/client/ui/controllers/settingsController.cpp b/client/ui/controllers/settingsController.cpp
index 24b547b41..7eef384c5 100644
--- a/client/ui/controllers/settingsController.cpp
+++ b/client/ui/controllers/settingsController.cpp
@@ -1,6 +1,7 @@
#include "settingsController.h"
#include
+#include
#include "logger.h"
#include "systemController.h"
@@ -33,6 +34,17 @@ SettingsController::SettingsController(const QSharedPointer &serve
checkIfNeedDisableLogs();
#ifdef Q_OS_ANDROID
connect(AndroidController::instance(), &AndroidController::notificationStateChanged, this, &SettingsController::onNotificationStateChanged);
+ connect(AndroidController::instance(), &AndroidController::imeInsetsChanged, this, [this](int heightDp) {
+ m_imeHeight = heightDp;
+ emit imeHeightChanged(heightDp);
+ emit safeAreaBottomMarginChanged();
+ });
+ connect(AndroidController::instance(), &AndroidController::systemBarsInsetsChanged, this, [this](int navBarHeightDp, int statusBarHeightDp) {
+ m_cachedNavigationBarHeight = navBarHeightDp;
+ m_cachedStatusBarHeight = statusBarHeightDp;
+ emit safeAreaBottomMarginChanged();
+ emit safeAreaTopMarginChanged();
+ });
#endif
m_isDevModeEnabled = m_settings->isDevGatewayEnv();
@@ -480,6 +492,10 @@ int SettingsController::getSafeAreaBottomMargin()
{
#ifdef Q_OS_ANDROID
if (isEdgeToEdgeEnabled()) {
+ if (m_imeHeight > 0) {
+ return 0;
+ }
+
int height = getNavigationBarHeight();
int result = height > 0 ? height : 56; // fallback to 56 if system returns 0
return result;
@@ -488,6 +504,11 @@ int SettingsController::getSafeAreaBottomMargin()
return 0;
}
+int SettingsController::getImeHeight()
+{
+ return m_imeHeight;
+}
+
bool SettingsController::isHomeAdLabelVisible()
{
return m_settings->isHomeAdLabelVisible();
diff --git a/client/ui/controllers/settingsController.h b/client/ui/controllers/settingsController.h
index ad9b97afe..cc90de567 100644
--- a/client/ui/controllers/settingsController.h
+++ b/client/ui/controllers/settingsController.h
@@ -33,8 +33,9 @@ public:
Q_PROPERTY(bool isHomeAdLabelVisible READ isHomeAdLabelVisible NOTIFY isHomeAdLabelVisibleChanged)
Q_PROPERTY(bool startMinimized READ isStartMinimizedEnabled NOTIFY startMinimizedChanged)
- Q_PROPERTY(int safeAreaTopMargin READ getSafeAreaTopMargin CONSTANT)
- Q_PROPERTY(int safeAreaBottomMargin READ getSafeAreaBottomMargin CONSTANT)
+ Q_PROPERTY(int safeAreaTopMargin READ getSafeAreaTopMargin NOTIFY safeAreaTopMarginChanged)
+ Q_PROPERTY(int safeAreaBottomMargin READ getSafeAreaBottomMargin NOTIFY safeAreaBottomMarginChanged)
+ Q_PROPERTY(int imeHeight READ getImeHeight NOTIFY imeHeightChanged)
public slots:
void toggleAmneziaDns(bool enable);
@@ -103,6 +104,7 @@ public slots:
int getNavigationBarHeight();
int getSafeAreaTopMargin();
int getSafeAreaBottomMargin();
+ int getImeHeight();
bool isHomeAdLabelVisible();
void disableHomeAdLabel();
@@ -131,6 +133,10 @@ signals:
void devModeEnabled();
void gatewayEndpointChanged(const QString &endpoint);
void devGatewayEnvChanged(bool enabled);
+
+ void imeHeightChanged(int height);
+ void safeAreaTopMarginChanged();
+ void safeAreaBottomMarginChanged();
void isHomeAdLabelVisibleChanged(bool visible);
void startMinimizedChanged();
@@ -144,6 +150,7 @@ private:
mutable int m_cachedStatusBarHeight = -1;
mutable int m_cachedNavigationBarHeight = -1;
+ int m_imeHeight = 0;
std::shared_ptr m_settings;
QString m_appVersion;
diff --git a/client/ui/qml/Controls2/DrawerType2.qml b/client/ui/qml/Controls2/DrawerType2.qml
index e67e36a15..65822afe8 100644
--- a/client/ui/qml/Controls2/DrawerType2.qml
+++ b/client/ui/qml/Controls2/DrawerType2.qml
@@ -49,6 +49,22 @@ Item {
return drawerContent.state === stateName
}
+ Connections {
+ target: Qt.application
+
+ function onStateChanged() {
+ if (Qt.application.state !== Qt.ApplicationActive) {
+ if (dragArea.drag.active) {
+ dragArea.drag.target = null
+ dragArea.drag.target = drawerContent
+ }
+ if (isOpened && !isCollapsedStateActive()) {
+ root.closeTriggered()
+ }
+ }
+ }
+ }
+
Connections {
target: PageController
diff --git a/client/ui/qml/Controls2/DropDownType.qml b/client/ui/qml/Controls2/DropDownType.qml
index 633b11cf6..540028159 100644
--- a/client/ui/qml/Controls2/DropDownType.qml
+++ b/client/ui/qml/Controls2/DropDownType.qml
@@ -74,6 +74,18 @@ Item {
FocusController.nextKeyRightItem()
}
+ Connections {
+ target: Qt.application
+
+ function onStateChanged() {
+ if (Qt.application.state !== Qt.ApplicationActive) {
+ if (!menu.isClosed) {
+ menu.closeTriggered()
+ }
+ }
+ }
+ }
+
implicitWidth: rootButtonContent.implicitWidth
implicitHeight: rootButtonContent.implicitHeight
diff --git a/client/ui/qml/Pages2/PageDeinstalling.qml b/client/ui/qml/Pages2/PageDeinstalling.qml
index 03d26f8d9..24f497bd3 100644
--- a/client/ui/qml/Pages2/PageDeinstalling.qml
+++ b/client/ui/qml/Pages2/PageDeinstalling.qml
@@ -45,7 +45,7 @@ PageType {
BaseHeaderType {
Layout.fillWidth: true
- Layout.topMargin: 20
+ Layout.topMargin: 20 + SettingsController.safeAreaTopMargin
Layout.leftMargin: 16
Layout.rightMargin: 16
diff --git a/client/ui/qml/Pages2/PageDevMenu.qml b/client/ui/qml/Pages2/PageDevMenu.qml
index 9f042fad4..81f44edbc 100644
--- a/client/ui/qml/Pages2/PageDevMenu.qml
+++ b/client/ui/qml/Pages2/PageDevMenu.qml
@@ -22,7 +22,7 @@ PageType {
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
- anchors.topMargin: 20
+ anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
}
ListViewType {
diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml
index 06bfb2430..1615d2761 100644
--- a/client/ui/qml/Pages2/PageHome.qml
+++ b/client/ui/qml/Pages2/PageHome.qml
@@ -20,6 +20,21 @@ import "../Components"
PageType {
id: root
+ Connections {
+ target: Qt.application
+
+ function onStateChanged() {
+ if (Qt.application.state !== Qt.ApplicationActive) {
+ if (drawer.isOpened) {
+ drawer.closeTriggered()
+ }
+ if (homeSplitTunnelingDrawer.isOpened) {
+ homeSplitTunnelingDrawer.closeTriggered()
+ }
+ }
+ }
+ }
+
Connections {
objectName: "pageControllerConnections"
diff --git a/client/ui/qml/Pages2/PageProtocolAwgClientSettings.qml b/client/ui/qml/Pages2/PageProtocolAwgClientSettings.qml
index 2db21d495..47b4fb58c 100644
--- a/client/ui/qml/Pages2/PageProtocolAwgClientSettings.qml
+++ b/client/ui/qml/Pages2/PageProtocolAwgClientSettings.qml
@@ -22,7 +22,7 @@ PageType {
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
- anchors.topMargin: 20
+ anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
onActiveFocusChanged: {
if(backButton.enabled && backButton.activeFocus) {
diff --git a/client/ui/qml/Pages2/PageSettingsApiAvailableCountries.qml b/client/ui/qml/Pages2/PageSettingsApiAvailableCountries.qml
index e4c57b5be..d2787f590 100644
--- a/client/ui/qml/Pages2/PageSettingsApiAvailableCountries.qml
+++ b/client/ui/qml/Pages2/PageSettingsApiAvailableCountries.qml
@@ -66,7 +66,7 @@ PageType {
id: backButton
objectName: "backButton"
- Layout.topMargin: 20
+ Layout.topMargin: 20 + SettingsController.safeAreaTopMargin
}
HeaderTypeWithButton {
diff --git a/client/ui/qml/Pages2/PageSettingsApiSubscriptionKey.qml b/client/ui/qml/Pages2/PageSettingsApiSubscriptionKey.qml
index 38504edde..0a5ea6e34 100644
--- a/client/ui/qml/Pages2/PageSettingsApiSubscriptionKey.qml
+++ b/client/ui/qml/Pages2/PageSettingsApiSubscriptionKey.qml
@@ -61,7 +61,7 @@ PageType {
width: root.width
BackButtonType {
- Layout.topMargin: 20
+ Layout.topMargin: 20 + SettingsController.safeAreaTopMargin
}
Label {
diff --git a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml
index 2e069cb7b..d7880dc56 100644
--- a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml
+++ b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml
@@ -103,7 +103,7 @@ PageType {
BaseHeaderType {
Layout.fillWidth: true
- Layout.topMargin: 20
+ Layout.topMargin: 20 + SettingsController.safeAreaTopMargin
Layout.leftMargin: 16
Layout.rightMargin: 16
@@ -147,6 +147,7 @@ PageType {
Layout.fillWidth: true
Layout.topMargin: 24
+ Layout.bottomMargin: 24 + SettingsController.safeAreaBottomMargin
Layout.leftMargin: 16
Layout.rightMargin: 16
diff --git a/client/ui/qml/Pages2/PageSetupWizardQrReader.qml b/client/ui/qml/Pages2/PageSetupWizardQrReader.qml
index 83ead6f2d..320e0e206 100644
--- a/client/ui/qml/Pages2/PageSetupWizardQrReader.qml
+++ b/client/ui/qml/Pages2/PageSetupWizardQrReader.qml
@@ -20,7 +20,7 @@ PageType {
anchors.left: parent.left
anchors.top: parent.top
- anchors.topMargin: 20
+ anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
}
ParagraphTextType {
diff --git a/client/ui/qml/Pages2/PageSetupWizardStart.qml b/client/ui/qml/Pages2/PageSetupWizardStart.qml
index 2d6790baf..82ff3b7c1 100644
--- a/client/ui/qml/Pages2/PageSetupWizardStart.qml
+++ b/client/ui/qml/Pages2/PageSetupWizardStart.qml
@@ -25,7 +25,7 @@ PageType {
source: "qrc:/images/amneziaBigLogo.png"
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
- Layout.topMargin: 32
+ Layout.topMargin: 32 + SettingsController.safeAreaTopMargin
Layout.preferredWidth: 360
Layout.preferredHeight: 287
}
@@ -33,7 +33,7 @@ PageType {
BasicButtonType {
id: startButton
Layout.fillWidth: true
- Layout.bottomMargin: 48
+ Layout.bottomMargin: 48 + SettingsController.safeAreaBottomMargin
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.alignment: Qt.AlignBottom
diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml
index fbb04adea..ab4224fda 100644
--- a/client/ui/qml/Pages2/PageStart.qml
+++ b/client/ui/qml/Pages2/PageStart.qml
@@ -304,6 +304,9 @@ PageType {
anchors.right: parent.right
anchors.left: parent.left
anchors.bottom: parent.bottom
+
+ // Also adjust TabBar position when keyboard appears (Android 14+ workaround)
+ anchors.bottomMargin: SettingsController.imeHeight
topPadding: 8
bottomPadding: 8 + SettingsController.safeAreaBottomMargin