Compare commits

...

9 Commits

Author SHA1 Message Date
Roberto Viola
9fda5550d2 2.20.1 2025-07-13 17:23:09 +02:00
Roberto Viola
0fa88c3803 fixed orientation 2025-07-13 17:12:11 +02:00
Roberto Viola
ac7b65a8c5 could be ok? 2025-07-12 15:47:43 +02:00
Roberto Viola
d3253befde trying to reduce the gap 2025-07-12 13:26:16 +02:00
Roberto Viola
63c249ce38 Merge branch 'android_header' of https://github.com/cagnulein/qdomyos-zwift into android_header 2025-07-12 12:47:41 +02:00
Roberto Viola
2b263577e0 Update Android emulator permissions for comprehensive app testing
- Added comprehensive permission grants for all Android API levels (24-36)
- Includes Bluetooth permissions for modern Android versions (12+)
- Added storage, camera, audio, and network permissions
- Configured app ops for special permissions (MANAGE_EXTERNAL_STORAGE, SYSTEM_ALERT_WINDOW)
- All permissions use || true to handle API compatibility gracefully

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-12 12:36:51 +02:00
Roberto Viola
a312545385 creating nomedia file for the gallery 2025-07-12 07:49:21 +02:00
Roberto Viola
b15056272c seems ok 2025-07-12 07:45:31 +02:00
Roberto Viola
144355a7b5 Fix Android header positioning under status bar
- Remove fullscreen flags from CustomQtActivity to allow normal window mode
- Add dynamic top padding to main.qml header toolbar for Android
- Use Screen.height - Screen.desktopAvailableHeight for proper status bar compensation
- Maintains fullscreen QML visibility while preventing header overlap

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-11 19:57:05 +02:00
9 changed files with 327 additions and 27 deletions

View File

@@ -741,13 +741,41 @@ jobs:
# Install the APK
adb install apk-debug/android-debug.apk
# Grant necessary permissions for API 25
echo "Granting permissions..."
# Grant necessary permissions - comprehensive list for all Android APIs
echo "Granting all required permissions..."
adb shell pm grant org.cagnulen.qdomyoszwift android.permission.ACCESS_FINE_LOCATION || true
adb shell pm grant org.cagnulen.qdomyoszwift android.permission.ACCESS_COARSE_LOCATION || true
adb shell pm grant org.cagnulen.qdomyoszwift android.permission.BLUETOOTH || true
adb shell pm grant org.cagnulen.qdomyoszwift android.permission.BLUETOOTH_ADMIN || true
adb shell pm grant org.cagnulen.qdomyoszwift android.permission.BLUETOOTH_ADVERTISE || true
adb shell pm grant org.cagnulen.qdomyoszwift android.permission.BLUETOOTH_CONNECT || true
adb shell pm grant org.cagnulen.qdomyoszwift android.permission.BLUETOOTH_SCAN || true
adb shell pm grant org.cagnulen.qdomyoszwift android.permission.READ_EXTERNAL_STORAGE || true
adb shell pm grant org.cagnulen.qdomyoszwift android.permission.WRITE_EXTERNAL_STORAGE || true
adb shell pm grant org.cagnulen.qdomyoszwift android.permission.MANAGE_EXTERNAL_STORAGE || true
adb shell pm grant org.cagnulen.qdomyoszwift android.permission.CAMERA || true
adb shell pm grant org.cagnulen.qdomyoszwift android.permission.RECORD_AUDIO || true
adb shell pm grant org.cagnulen.qdomyoszwift android.permission.INTERNET || true
adb shell pm grant org.cagnulen.qdomyoszwift android.permission.ACCESS_NETWORK_STATE || true
adb shell pm grant org.cagnulen.qdomyoszwift android.permission.ACCESS_WIFI_STATE || true
adb shell pm grant org.cagnulen.qdomyoszwift android.permission.CHANGE_WIFI_STATE || true
adb shell pm grant org.cagnulen.qdomyoszwift android.permission.WAKE_LOCK || true
adb shell pm grant org.cagnulen.qdomyoszwift android.permission.VIBRATE || true
adb shell pm grant org.cagnulen.qdomyoszwift android.permission.READ_PHONE_STATE || true
adb shell pm grant org.cagnulen.qdomyoszwift android.permission.FOREGROUND_SERVICE || true
adb shell pm grant org.cagnulen.qdomyoszwift android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS || true
# Additional permissions for newer Android versions (12+)
adb shell pm grant org.cagnulen.qdomyoszwift android.permission.POST_NOTIFICATIONS || true
adb shell pm grant org.cagnulen.qdomyoszwift android.permission.SCHEDULE_EXACT_ALARM || true
adb shell pm grant org.cagnulen.qdomyoszwift android.permission.USE_EXACT_ALARM || true
# Enable all app ops permissions
adb shell appops set org.cagnulen.qdomyoszwift MANAGE_EXTERNAL_STORAGE allow || true
adb shell appops set org.cagnulen.qdomyoszwift SYSTEM_ALERT_WINDOW allow || true
adb shell appops set org.cagnulen.qdomyoszwift WRITE_SETTINGS allow || true
echo "All permissions granted successfully"
# Start the main activity
adb shell am start -n org.cagnulen.qdomyoszwift/org.cagnulen.qdomyoszwift.CustomQtActivity
@@ -779,6 +807,25 @@ jobs:
adb shell screencap -p /sdcard/screenshot.png
adb pull /sdcard/screenshot.png
# Test orientamento automatico con screenshot
echo "Starting orientation test with automatic screenshots..."
# Screenshot iniziale (orientamento corrente)
adb shell screencap -p /sdcard/screenshot_orientation_0.png
adb pull /sdcard/screenshot_orientation_0.png
# Loop per 3 rotazioni aggiuntive (90°, 180°, 270°)
for i in 1 2 3; do
echo "Rotating to orientation $i (90° * $i)"
adb shell settings put system user_rotation $i
sleep 5
echo "Taking screenshot for orientation $i"
adb shell screencap -p /sdcard/screenshot_orientation_$i.png
adb pull /sdcard/screenshot_orientation_$i.png
done
echo "Orientation test completed - 4 screenshots captured"
# Check if the package is installed
adb shell pm list packages | grep org.cagnulen.qdomyoszwift
@@ -794,6 +841,7 @@ jobs:
name: android-emulator-test-evidence-api${{ matrix.api-level }}
path: |
screenshot.png
screenshot_orientation_*.png
process_list.txt
full_logcat.txt
qdomyos_logcat.txt

View File

@@ -1,5 +1,5 @@
<?xml version="1.0"?>
<manifest package="org.cagnulen.qdomyoszwift" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionName="2.20.0" android:versionCode="1121" android:installLocation="auto">
<manifest package="org.cagnulen.qdomyoszwift" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionName="2.20.1" android:versionCode="1122" android:installLocation="auto">
<!-- The following comment will be replaced upon deployment with default permissions based on the dependencies of the application.
Remove the comment if you do not require these default permissions. -->
<!-- %%INSERT_PERMISSIONS -->

View File

@@ -1,34 +1,96 @@
package org.cagnulen.qdomyoszwift;
import android.os.Build;
import android.content.res.Resources;
import android.os.Bundle;
import android.view.View;
import android.view.WindowInsets;
import android.view.WindowInsetsController;
import android.util.Log;
import org.qtproject.qt5.android.bindings.QtActivity;
public class CustomQtActivity extends QtActivity {
private static final String TAG = "CustomQtActivity";
private static CustomQtActivity instance;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
instance = this;
// Handle Android 16 API 36 WindowInsetsController for fullscreen support
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
// Android 11 (API 30) and above - use WindowInsetsController
getWindow().setDecorFitsSystemWindows(false);
WindowInsetsController controller = getWindow().getDecorView().getWindowInsetsController();
if (controller != null) {
controller.hide(WindowInsets.Type.statusBars() | WindowInsets.Type.navigationBars());
controller.setSystemBarsBehavior(WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
Log.d(TAG, "onCreate: CustomQtActivity initialized");
// Get and log the real status bar height
int statusBarHeight = getStatusBarHeight();
Log.d(TAG, "Real status bar height: " + statusBarHeight + "px");
}
// Native method that can be called from C++/Qt
public static int getStatusBarHeight() {
try {
if (instance == null) {
Log.e("CustomQtActivity", "Activity instance not available");
return 72; // fallback value
}
} else {
// Fallback for older Android versions (API < 30)
getWindow().getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
View.SYSTEM_UI_FLAG_FULLSCREEN |
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
);
Resources resources = instance.getResources();
android.util.DisplayMetrics metrics = resources.getDisplayMetrics();
// Log display metrics for analysis
Log.d("CustomQtActivity", "Display metrics:");
Log.d("CustomQtActivity", " density: " + metrics.density);
Log.d("CustomQtActivity", " densityDpi: " + metrics.densityDpi);
Log.d("CustomQtActivity", " scaledDensity: " + metrics.scaledDensity);
Log.d("CustomQtActivity", " xdpi: " + metrics.xdpi);
Log.d("CustomQtActivity", " ydpi: " + metrics.ydpi);
int resourceId = resources.getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
int heightPx = resources.getDimensionPixelSize(resourceId);
float heightInDp = heightPx / metrics.density;
Log.d("CustomQtActivity", "Status bar height analysis:");
Log.d("CustomQtActivity", " getDimensionPixelSize: " + heightPx + "px");
Log.d("CustomQtActivity", " Calculated DP: " + heightInDp + "dp");
Log.d("CustomQtActivity", " Returning DP value: " + Math.round(heightInDp));
// Return DP value instead of pixel value to let Qt handle scaling
return Math.round(heightInDp);
}
} catch (Exception e) {
Log.e("CustomQtActivity", "Error getting status bar height", e);
}
return 72; // fallback value ~24dp
}
// Native method that can be called from C++/Qt for navigation bar
public static int getNavigationBarHeight() {
try {
if (instance == null) {
Log.e("CustomQtActivity", "Activity instance not available for navigation bar");
return 48; // fallback value
}
Resources resources = instance.getResources();
android.util.DisplayMetrics metrics = resources.getDisplayMetrics();
int resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android");
if (resourceId > 0) {
int heightPx = resources.getDimensionPixelSize(resourceId);
float heightInDp = heightPx / metrics.density;
Log.d("CustomQtActivity", "Navigation bar height analysis:");
Log.d("CustomQtActivity", " getDimensionPixelSize: " + heightPx + "px");
Log.d("CustomQtActivity", " Calculated DP: " + heightInDp + "dp");
Log.d("CustomQtActivity", " Returning DP value: " + Math.round(heightInDp));
// Return DP value instead of pixel value to let Qt handle scaling
return Math.round(heightInDp);
}
} catch (Exception e) {
Log.e("CustomQtActivity", "Error getting navigation bar height", e);
}
return 48; // fallback value ~16dp
}
// Native method that can be called from C++/Qt to get API level
public static int getApiLevel() {
return android.os.Build.VERSION.SDK_INT;
}
}

129
src/androidstatusbar.cpp Normal file
View File

@@ -0,0 +1,129 @@
#include "androidstatusbar.h"
#include <QQmlEngine>
#include <QGuiApplication>
#include <QScreen>
#ifdef Q_OS_ANDROID
#include <QtAndroid>
#include <QAndroidJniObject>
#include <QAndroidJniEnvironment>
#endif
AndroidStatusBar::AndroidStatusBar(QObject *parent)
: QObject(parent)
, m_cachedHeight(-1)
, m_cachedNavigationBarHeight(-1)
{
// Listen for orientation changes
if (QGuiApplication::primaryScreen()) {
connect(QGuiApplication::primaryScreen(), &QScreen::orientationChanged,
this, &AndroidStatusBar::onOrientationChanged);
}
}
void AndroidStatusBar::registerQmlType()
{
qmlRegisterSingletonType<AndroidStatusBar>("AndroidStatusBar", 1, 0, "AndroidStatusBar",
[](QQmlEngine *engine, QJSEngine *scriptEngine) -> QObject* {
Q_UNUSED(engine)
Q_UNUSED(scriptEngine)
return new AndroidStatusBar();
});
}
int AndroidStatusBar::height() const
{
if (m_cachedHeight == -1) {
m_cachedHeight = getStatusBarHeightFromAndroid();
}
return m_cachedHeight;
}
int AndroidStatusBar::navigationBarHeight() const
{
if (m_cachedNavigationBarHeight == -1) {
m_cachedNavigationBarHeight = getNavigationBarHeightFromAndroid();
}
return m_cachedNavigationBarHeight;
}
int AndroidStatusBar::apiLevel() const
{
return getApiLevelFromAndroid();
}
void AndroidStatusBar::onOrientationChanged()
{
invalidateCache();
}
void AndroidStatusBar::invalidateCache()
{
m_cachedHeight = -1;
m_cachedNavigationBarHeight = -1;
emit heightChanged();
emit navigationBarHeightChanged();
}
int AndroidStatusBar::getStatusBarHeightFromAndroid() const
{
#ifdef Q_OS_ANDROID
try {
// Call the static method that returns int directly
int height = QAndroidJniObject::callStaticMethod<jint>(
"org/cagnulen/qdomyoszwift/CustomQtActivity",
"getStatusBarHeight",
"()I"
);
return height;
} catch (...) {
// Fallback: return a reasonable default
return 72; // ~24dp for typical Android devices
}
#else
return 0; // Non-Android platforms don't have status bar
#endif
}
int AndroidStatusBar::getNavigationBarHeightFromAndroid() const
{
#ifdef Q_OS_ANDROID
try {
// Call the static method that returns int directly
int height = QAndroidJniObject::callStaticMethod<jint>(
"org/cagnulen/qdomyoszwift/CustomQtActivity",
"getNavigationBarHeight",
"()I"
);
return height;
} catch (...) {
// Fallback: return a reasonable default
return 48; // ~16dp for typical Android devices
}
#else
return 0; // Non-Android platforms don't have navigation bar
#endif
}
int AndroidStatusBar::getApiLevelFromAndroid() const
{
#ifdef Q_OS_ANDROID
try {
// Call the static method that returns int directly
int apiLevel = QAndroidJniObject::callStaticMethod<jint>(
"org/cagnulen/qdomyoszwift/CustomQtActivity",
"getApiLevel",
"()I"
);
return apiLevel;
} catch (...) {
// Fallback: return a reasonable default
return 30; // Default to API 30 if we can't get it
}
#else
return 0; // Non-Android platforms
#endif
}

40
src/androidstatusbar.h Normal file
View File

@@ -0,0 +1,40 @@
#ifndef ANDROIDSTATUSBAR_H
#define ANDROIDSTATUSBAR_H
#include <QObject>
#include <QQmlEngine>
#include <QScreen>
class AndroidStatusBar : public QObject
{
Q_OBJECT
Q_PROPERTY(int height READ height NOTIFY heightChanged)
Q_PROPERTY(int navigationBarHeight READ navigationBarHeight NOTIFY navigationBarHeightChanged)
Q_PROPERTY(int apiLevel READ apiLevel CONSTANT)
public:
explicit AndroidStatusBar(QObject *parent = nullptr);
static void registerQmlType();
int height() const;
int navigationBarHeight() const;
int apiLevel() const;
signals:
void heightChanged();
void navigationBarHeightChanged();
private slots:
void onOrientationChanged();
private:
int getStatusBarHeightFromAndroid() const;
int getNavigationBarHeightFromAndroid() const;
int getApiLevelFromAndroid() const;
void invalidateCache();
mutable int m_cachedHeight;
mutable int m_cachedNavigationBarHeight;
};
#endif // ANDROIDSTATUSBAR_H

View File

@@ -1082,6 +1082,12 @@ QString homeform::getWritableAppDir() {
if (android_documents_folder || QOperatingSystemVersion::current() >= QOperatingSystemVersion(QOperatingSystemVersion::Android, 14)) {
path = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation) + "/QZ/";
QDir().mkdir(path);
// Create .nomedia file to prevent gallery indexing
QFile nomediaFile(path + ".nomedia");
if (!nomediaFile.exists()) {
nomediaFile.open(QIODevice::WriteOnly);
nomediaFile.close();
}
} else {
path = getAndroidDataAppDir() + "/";
}

View File

@@ -27,6 +27,7 @@
#endif
#include "mqttpublisher.h"
#include "androidstatusbar.h"
#ifdef Q_OS_ANDROID
#include "keepawakehelper.h"
@@ -796,6 +797,7 @@ int main(int argc, char *argv[]) {
if (forceQml)
#endif
{
AndroidStatusBar::registerQmlType();
QQmlApplicationEngine engine;
const QUrl url(QStringLiteral("qrc:/main.qml"));
QObject::connect(

View File

@@ -8,6 +8,7 @@ import QtMultimedia 5.15
import org.cagnulein.qdomyoszwift 1.0
import QtQuick.Window 2.12
import Qt.labs.platform 1.1
import AndroidStatusBar 1.0
ApplicationWindow {
id: window
@@ -472,6 +473,7 @@ ApplicationWindow {
contentHeight: toolButton.implicitHeight
Material.primary: settings.theme_status_bar_background_color
id: headerToolbar
topPadding: (Qt.platform.os === "android" && AndroidStatusBar.apiLevel >= 31) ? AndroidStatusBar.height : 0
ToolButton {
id: toolButton
@@ -680,6 +682,8 @@ ApplicationWindow {
id: drawer
width: window.width * 0.66
height: window.height
topPadding: (Qt.platform.os === "android" && AndroidStatusBar.apiLevel >= 31) ? AndroidStatusBar.height : 0
bottomPadding: (Qt.platform.os === "android" && AndroidStatusBar.apiLevel >= 31) ? AndroidStatusBar.navigationBarHeight : 0
ScrollView {
contentWidth: -1
@@ -844,7 +848,7 @@ ApplicationWindow {
}
ItemDelegate {
text: "version 2.20.0"
text: "version 2.20.1"
width: parent.width
}
@@ -909,6 +913,7 @@ ApplicationWindow {
id: stackView
initialItem: "Home.qml"
anchors.fill: parent
anchors.bottomMargin: (Qt.platform.os === "android" && AndroidStatusBar.apiLevel >= 31) ? AndroidStatusBar.navigationBarHeight : 0
focus: true
Keys.onVolumeUpPressed: (event)=> { console.log("onVolumeUpPressed"); volumeUp(); event.accepted = settings.volume_change_gears; }
Keys.onVolumeDownPressed: (event)=> { console.log("onVolumeDownPressed"); volumeDown(); event.accepted = settings.volume_change_gears; }
@@ -926,3 +931,9 @@ ApplicationWindow {
}
}
}
/*##^##
Designer {
D{i:0;autoSize:true;height:480;width:640}
}
##^##*/

View File

@@ -969,10 +969,12 @@ ios {
}
HEADERS += \
mqttpublisher.h
mqttpublisher.h \
androidstatusbar.h
SOURCES += \
mqttpublisher.cpp
mqttpublisher.cpp \
androidstatusbar.cpp
include($$PWD/purchasing/purchasing.pri)
INCLUDEPATH += purchasing/qmltypes
@@ -980,4 +982,4 @@ INCLUDEPATH += purchasing/inapp
WINRT_MANIFEST = AppxManifest.xml
VERSION = 2.20.0
VERSION = 2.20.1