Port Qt Quick GUI for Android to Qt 6
This commit is contained in:
parent
430e17416a
commit
4bf6a91d72
|
@ -42,3 +42,6 @@ Makefile*
|
|||
|
||||
# clang-format
|
||||
/.clang-format
|
||||
|
||||
# Android-specific
|
||||
/android/AndroidManifest.xml
|
||||
|
|
|
@ -130,11 +130,6 @@ use_qt_utilities()
|
|||
find_package(passwordfile${CONFIGURATION_PACKAGE_SUFFIX} 5.0.0 REQUIRED)
|
||||
use_password_file()
|
||||
|
||||
# require at least Qt 5.8 for the Qt Quick GUI
|
||||
if (QUICK_GUI)
|
||||
set(META_QT5_VERSION 5.8)
|
||||
endif ()
|
||||
|
||||
# allow to enable undo support from the widgets GUI in the quick GUI as well (so the quick GUI will depend on Qt Widgets as
|
||||
# well)
|
||||
if (QUICK_GUI AND NOT WIDGETS_GUI)
|
||||
|
@ -147,20 +142,33 @@ if (QUICK_GUI AND NOT WIDGETS_GUI)
|
|||
endif ()
|
||||
endif ()
|
||||
|
||||
# add further Qt/KF modules required by the Qt Quick GUI under Android
|
||||
if (ANDROID)
|
||||
list(APPEND ADDITIONAL_QT_MODULES AndroidExtras)
|
||||
# deduce major Qt version from package prefix
|
||||
if (NOT QT_PACKAGE_PREFIX)
|
||||
set(MAJOR_QT_VERSION "5")
|
||||
elseif (QT_PACKAGE_PREFIX MATCHES ".*Qt([0-9]+).*")
|
||||
set(MAJOR_QT_VERSION "${CMAKE_MATCH_1}")
|
||||
endif ()
|
||||
|
||||
# require Qt 6 for the Qt Quick GUI
|
||||
if (QUICK_GUI AND MAJOR_QT_VERSION VERSION_LESS 6 OR MAJOR_QT_VERSION VERSION_GREATER_EQUAL 7)
|
||||
message(FATAL_ERROR "The Qt Quick GUI is only compatible with Qt 6 (but Qt ${MAJOR_QT_VERSION} was found).")
|
||||
endif ()
|
||||
|
||||
# workaround "ld: error: undefined symbol: qt_resourceFeatureZstd" when Qt 6 is not configured with zstd support
|
||||
if (MAJOR_QT_VERSION GREATER_EQUAL 6 AND NOT QT_FEATURE_zstd)
|
||||
set(CMAKE_AUTORCC_OPTIONS "--no-zstd")
|
||||
endif ()
|
||||
|
||||
# add further Qt/KF modules required by Qt Quick GUI
|
||||
if (QUICK_GUI)
|
||||
list(APPEND ADDITIONAL_QT_MODULES QuickControls2)
|
||||
list(APPEND ADDITIONAL_KF_MODULES Kirigami2)
|
||||
endif ()
|
||||
|
||||
# add Qt-version-specific QML files
|
||||
unset(QML_FILE)
|
||||
if (NOT QT_PACKAGE_PREFIX)
|
||||
set(QML_FILE "resources/qml5.qrc")
|
||||
elseif (QT_PACKAGE_PREFIX MATCHES ".*Qt([0-9]+).*")
|
||||
set(QML_FILE "resources/qml${CMAKE_MATCH_1}.qrc")
|
||||
if (MAJOR_QT_VERSION)
|
||||
set(QML_FILE "resources/qml${MAJOR_QT_VERSION}.qrc")
|
||||
endif ()
|
||||
if (NOT QML_FILE)
|
||||
message(FATAL_ERROR "Unable to add Qt-version-specific resource file for QT_PACKAGE_PREFIX \"${QT_PACKAGE_PREFIX}\".")
|
||||
|
@ -180,9 +188,28 @@ if (WIDGETS_GUI OR QUICK_GUI)
|
|||
endif ()
|
||||
include(WindowsResources)
|
||||
include(AppTarget)
|
||||
include(AndroidApk)
|
||||
include(ShellCompletion)
|
||||
include(ConfigHeader)
|
||||
|
||||
# configure creating an Android package using androiddeployqt
|
||||
if (ANDROID)
|
||||
set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android")
|
||||
set_target_properties(${META_TARGET_NAME} PROPERTIES QT_ANDROID_PACKAGE_SOURCE_DIR "${ANDROID_PACKAGE_SOURCE_DIR}")
|
||||
|
||||
set(ANDROID_MANIFEST_PATH "${ANDROID_PACKAGE_SOURCE_DIR}/AndroidManifest.xml")
|
||||
configure_file("resources/AndroidManifest.xml.in" "${ANDROID_MANIFEST_PATH}")
|
||||
|
||||
# bundle OpenMP (used by Kirigami) explicitly as it is otherwise not bundled
|
||||
find_package(OpenMP)
|
||||
if (OpenMP_CXX_FOUND)
|
||||
message(STATUS "Bundling OpenMP library for Kirigami: ${OpenMP_omp_LIBRARY}")
|
||||
set_target_properties(${META_TARGET_NAME} PROPERTIES QT_ANDROID_EXTRA_LIBS "${OpenMP_omp_LIBRARY}")
|
||||
endif ()
|
||||
|
||||
set(QT_ANDROID_SIGN_APK ON)
|
||||
qt_android_generate_deployment_settings(${META_TARGET_NAME})
|
||||
qt_android_add_apk_target(${META_TARGET_NAME})
|
||||
endif ()
|
||||
|
||||
# create desktop file using previously defined meta data
|
||||
add_desktop_file()
|
||||
|
|
131
README.md
131
README.md
|
@ -124,131 +124,44 @@ always requires the same major Qt version as your KDE modules use.
|
|||
the desired location afterwards.
|
||||
|
||||
#### Concrete example of 3. for building an Android APK under Arch Linux
|
||||
Create stuff for signing the package (remove `-DANDROID_APK_FORCE_DEBUG=ON` line in the CMake invocation to actually use this):
|
||||
Create stuff for signing the package:
|
||||
```
|
||||
# locate keystore
|
||||
# set variables for creating keystore and androiddeployqt to find it
|
||||
keystore_dir=/path/to/keystore-dir
|
||||
keystore_alias=$USER
|
||||
keystore_url=$keystore_dir/$keystore_alias
|
||||
|
||||
# make up some password to protect the store; enter this on keytool invocation
|
||||
keystore_password=<password>
|
||||
export QT_ANDROID_KEYSTORE_PATH=$keystore_dir QT_ANDROID_KEYSTORE_ALIAS=$USER QT_ANDROID_KEYSTORE_STORE_PASS=$USER-devel QT_ANDROID_KEYSTORE_KEY_PASS=$USER-devel
|
||||
|
||||
# create keystore (do only once)
|
||||
mkdir -p "$keystore_dir"
|
||||
pushd "$keystore_dir"
|
||||
keytool -genkey -v -keystore "$keystore_alias" -alias "$keystore_alias" -keyalg RSA -keysize 2048 -validity 10000
|
||||
keytool -genkey -v -keystore "$QT_ANDROID_KEYSTORE_ALIAS" -alias "$QT_ANDROID_KEYSTORE_ALIAS" -keyalg RSA -keysize 2048 -validity 10000
|
||||
popd
|
||||
```
|
||||
|
||||
Build c++utilities, passwordfile, qtutilities and passwordmanager in one step to create an Android APK for arm64-v8a:
|
||||
Build c++utilities, passwordfile, qtutilities and passwordmanager in one step to create an Android APK for aarch64:
|
||||
|
||||
```
|
||||
# specify Android platform
|
||||
_pkg_arch=aarch64
|
||||
_android_arch=arm64-v8a
|
||||
_android_arch2=arm64
|
||||
_android_api_level=22
|
||||
# use Java 17, the latest Java doesn't work at this point and avoid unwanted Java options
|
||||
export PATH=/usr/lib/jvm/java-17-openjdk/bin:$PATH
|
||||
export _JAVA_OPTIONS=
|
||||
|
||||
# set project name
|
||||
_reponame=passwordmanager
|
||||
_pkgname=passwordmanager
|
||||
# configure and build using helpers from android-cmake package
|
||||
android_arch=aarch64
|
||||
build_dir=$BUILD_DIR/../manual/passwordmanager-android-$android_arch-release
|
||||
source /usr/bin/android-env $android_arch
|
||||
android-$android_arch-cmake -G Ninja -S . -B "$build_dir" \
|
||||
-DCMAKE_FIND_ROOT_PATH="${ANDROID_PREFIX}" -DANDROID_SDK_ROOT="${ANDROID_HOME}" \
|
||||
-DPKG_CONFIG_EXECUTABLE:FILEPATH=/usr/bin/android-$android_arch-pkg-config \
|
||||
-DQT_PACKAGE_PREFIX:STRING=Qt6 -DKF_PACKAGE_PREFIX:STRING=KF6
|
||||
cmake --build "$build_dir"
|
||||
|
||||
# locate SDK, NDK and further libraries
|
||||
android_sdk_root=${ANDROID_SDK_ROOT:-/opt/android-sdk}
|
||||
android_ndk_root=${ANDROID_NDK_ROOT:-/opt/android-ndk}
|
||||
build_tools_version=$(pacman -Q android-sdk-build-tools | sed 's/.* r\(.*\)-.*/\1/')
|
||||
other_libs_root=/opt/android-libs/$_pkg_arch
|
||||
other_libs_include=$other_libs_root/include
|
||||
root="$android_ndk_root/sysroot;$other_libs_root"
|
||||
|
||||
# use Java 8 which seems to be the latest version which works
|
||||
export PATH=/usr/lib/jvm/java-8-openjdk/jre/bin/:$PATH
|
||||
|
||||
# configure with the toolchain file provided by the Android NDK (still WIP)
|
||||
# note: This configuration is likely required in the future to resolve https://gitlab.kitware.com/cmake/cmake/issues/18739. But for now
|
||||
# better keep using CMake's internal Android support because this config has its own pitfalls (see CMAKE_CXX_FLAGS).
|
||||
cmake \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DANDROID_ABI=$_android_arch \
|
||||
-DANDROID_PLATFORM=$_android_api_level \
|
||||
-DCMAKE_TOOLCHAIN_FILE=$android_ndk_root/build/cmake/android.toolchain.cmake \
|
||||
-DCMAKE_SYSTEM_NAME=Android \
|
||||
-DCMAKE_SYSTEM_VERSION=$_android_api_level \
|
||||
-DCMAKE_ANDROID_ARCH_ABI=$_android_arch \
|
||||
-DCMAKE_ANDROID_NDK="$android_ndk_root" \
|
||||
-DCMAKE_ANDROID_SDK="$android_sdk_root" \
|
||||
-DCMAKE_ANDROID_STL_TYPE=c++_shared \
|
||||
-DCMAKE_INSTALL_PREFIX=$other_libs_root \
|
||||
-DCMAKE_PREFIX_PATH="$root" \
|
||||
-DCMAKE_FIND_ROOT_PATH="$root;$root/libs" \
|
||||
-DCMAKE_CXX_FLAGS="-include $android_ndk_root/sysroot/usr/include/math.h -include $android_ndk_root/sources/cxx-stl/llvm-libc++/include/math.h -I$other_libs_include" \
|
||||
-DBUILD_SHARED_LIBS=ON \
|
||||
-DZLIB_LIBRARY="$android_ndk_root/platforms/android-$_android_api_level/arch-$_android_arch2/usr/lib/libz.so" \
|
||||
-DCLANG_FORMAT_ENABLED=ON \
|
||||
-DUSE_NATIVE_FILE_BUFFER=ON \
|
||||
-DUSE_STANDARD_FILESYSTEM=OFF \
|
||||
-DNO_DOXYGEN=ON \
|
||||
-DWIDGETS_GUI=OFF \
|
||||
-DQUICK_GUI=ON \
|
||||
-DBUILTIN_ICON_THEMES=breeze \
|
||||
-DBUILTIN_TRANSLATIONS=ON \
|
||||
-DANDROID_APK_TOOLCHAIN_VERSION=4.9 \
|
||||
-DANDROID_APK_CXX_STANDARD_LIBRARY="$android_ndk_root/platforms/android-$_android_api_level/arch-$_android_arch2/usr/lib/libstdc++.so" \
|
||||
-DANDROID_APK_FORCE_DEBUG=ON \
|
||||
-DANDROID_APK_KEYSTORE_URL="$keystore_url" \
|
||||
-DANDROID_APK_KEYSTORE_ALIAS="$keystore_alias" \
|
||||
-DANDROID_APK_KEYSTORE_PASSWORD="$keystore_password" \
|
||||
-DANDROID_APK_APPLICATION_ID_SUFFIX=".unstable" \
|
||||
-DANDROID_APK_APPLICATION_LABEL="Password Manager (unstable)" \
|
||||
$SOURCES/subdirs/$_reponame
|
||||
|
||||
# configure with CMake's internal Android support
|
||||
# note: Requires workaround with Android NDK r19: https://gitlab.kitware.com/cmake/cmake/issues/18739#note_498676
|
||||
cmake \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_SYSTEM_NAME=Android \
|
||||
-DCMAKE_SYSTEM_VERSION=$_android_api_level \
|
||||
-DCMAKE_ANDROID_ARCH_ABI=$_android_arch \
|
||||
-DCMAKE_ANDROID_NDK="$android_ndk_root" \
|
||||
-DCMAKE_ANDROID_SDK="$android_sdk_root" \
|
||||
-DCMAKE_ANDROID_STL_TYPE=c++_shared \
|
||||
-DCMAKE_INSTALL_PREFIX=$other_libs_root \
|
||||
-DCMAKE_PREFIX_PATH="$root" \
|
||||
-DCMAKE_FIND_ROOT_PATH="$root;$root/libs" \
|
||||
-DCMAKE_CXX_FLAGS="-D__ANDROID_API__=$_android_api_level" \
|
||||
-DCLANG_FORMAT_ENABLED=ON \
|
||||
-DBUILD_SHARED_LIBS=ON \
|
||||
-DUSE_NATIVE_FILE_BUFFER=ON \
|
||||
-DUSE_STANDARD_FILESYSTEM=OFF \
|
||||
-DNO_DOXYGEN=ON \
|
||||
-DWIDGETS_GUI=OFF \
|
||||
-DQUICK_GUI=ON \
|
||||
-DBUILTIN_ICON_THEMES=breeze \
|
||||
-DBUILTIN_TRANSLATIONS=ON \
|
||||
-DANDROID_APK_FORCE_DEBUG=ON \
|
||||
-DANDROID_APK_KEYSTORE_URL="$keystore_url" \
|
||||
-DANDROID_APK_KEYSTORE_ALIAS="$keystore_alias" \
|
||||
-DANDROID_APK_KEYSTORE_PASSWORD="$keystore_password" \
|
||||
-DANDROID_APK_APPLICATION_ID_SUFFIX=".unstable" \
|
||||
-DANDROID_APK_APPLICATION_LABEL="Password Manager (unstable)" \
|
||||
$SOURCES/subdirs/$_reponame
|
||||
|
||||
# build all binaries and make APK file using all CPU cores
|
||||
make passwordmanager_apk -j$(nproc)
|
||||
|
||||
# install app on USB-connected phone
|
||||
make passwordmanager_deploy_apk
|
||||
# install the app
|
||||
adb install "$build_dir/passwordmanager/android-build//build/outputs/apk/release/android-build-release-signed.apk"
|
||||
```
|
||||
|
||||
##### Notes
|
||||
* The Android packages for the dependencies Qt, iconv, OpenSSL and Kirigami 2 are provided in
|
||||
* The Android packages for the dependencies Boost, Qt, iconv, OpenSSL and Kirigami are provided in
|
||||
my [PKGBUILDs](http://github.com/Martchus/PKGBUILDs) repo.
|
||||
* The latest Java I was able to use was version 8 (`jdk8-openjdk` package).
|
||||
|
||||
### Manual deployment of Android APK file
|
||||
1. Find device ID: `adb devices`
|
||||
2. Install App on phone: `adb -s <DEVICE_ID> install -r $BUILD_DIR/passwordmanager_build_apk/build/outputs/apk/passwordmanager_build_apk-debug.apk`
|
||||
3. View log: `adb -s <DEVICE_ID> logcat`
|
||||
* The latest Java I was able to use was version 17.
|
||||
|
||||
### Building without Qt GUI
|
||||
It is possible to build without the GUI if only the CLI is needed. In this case no Qt dependencies (including qtutilities) are required.
|
||||
|
|
|
@ -1,54 +0,0 @@
|
|||
<?xml version="1.0"?>
|
||||
<manifest package="@META_ANDROID_PACKAGE_NAME@" xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="@META_APP_VERSION@" android:versionCode="@META_VERSION_MAJOR@" android:installLocation="auto">
|
||||
<application
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:name="org.qtproject.qt5.android.bindings.QtApplication"
|
||||
android:label="@string/app_name"
|
||||
android:resizeableActivity="true">
|
||||
<activity
|
||||
android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation"
|
||||
android:name="@META_ANDROID_PACKAGE_NAME@.Activity"
|
||||
android:label="@string/app_name"
|
||||
android:screenOrientation="unspecified"
|
||||
android:theme="@style/AppTheme">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
<meta-data android:name="android.app.lib_name" android:value="-- %%INSERT_APP_LIB_NAME%% --"/>
|
||||
<meta-data android:name="android.app.qt_sources_resource_id" android:resource="@array/qt_sources"/>
|
||||
<meta-data android:name="android.app.repository" android:value="default"/>
|
||||
<meta-data android:name="android.app.qt_libs_resource_id" android:resource="@array/qt_libs"/>
|
||||
<meta-data android:name="android.app.bundled_libs_resource_id" android:resource="@array/bundled_libs"/>
|
||||
<!-- Deploy Qt libs as part of package -->
|
||||
<meta-data android:name="android.app.bundle_local_qt_libs" android:value="-- %%BUNDLE_LOCAL_QT_LIBS%% --"/>
|
||||
<meta-data android:name="android.app.bundled_in_lib_resource_id" android:resource="@array/bundled_in_lib"/>
|
||||
<meta-data android:name="android.app.bundled_in_assets_resource_id" android:resource="@array/bundled_in_assets"/>
|
||||
<!-- Run with local libs -->
|
||||
<meta-data android:name="android.app.use_local_qt_libs" android:value="-- %%USE_LOCAL_QT_LIBS%% --"/>
|
||||
<meta-data android:name="android.app.libs_prefix" android:value="/data/local/tmp/qt/"/>
|
||||
<!--<meta-data android:name="android.app.load_local_libs" android:value="-- %%INSERT_LOCAL_LIBS%% --"/>-->
|
||||
<meta-data android:name="android.app.load_local_libs" android:value="plugins/platforms/android/libqtforandroid.so:plugins/bearer/libqandroidbearer.so:lib/libQt5QuickParticles.so"/>
|
||||
<meta-data android:name="android.app.load_local_jars" android:value="-- %%INSERT_LOCAL_JARS%% --"/>
|
||||
<meta-data android:name="android.app.static_init_classes" android:value="-- %%INSERT_INIT_CLASSES%% --"/>
|
||||
<!-- Messages maps -->
|
||||
<meta-data android:value="@string/ministro_not_found_msg" android:name="android.app.ministro_not_found_msg"/>
|
||||
<meta-data android:value="@string/ministro_needed_msg" android:name="android.app.ministro_needed_msg"/>
|
||||
<meta-data android:value="@string/fatal_error_msg" android:name="android.app.fatal_error_msg"/>
|
||||
|
||||
<!-- Splash screen -->
|
||||
<meta-data android:name="android.app.splash_screen_drawable" android:resource="@drawable/splash"/>
|
||||
<!-- Splash screen -->
|
||||
</activity>
|
||||
</application>
|
||||
<uses-sdk android:minSdkVersion="23" android:targetSdkVersion="23"/>
|
||||
<supports-screens android:largeScreens="true" android:normalScreens="true" android:anyDensity="true" android:smallScreens="true"/>
|
||||
|
||||
<!-- 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 -->
|
||||
|
||||
<!-- The following comment will be replaced upon deployment with default features based on the dependencies of the application.
|
||||
Remove the comment if you do not require these default features. -->
|
||||
<!-- %%INSERT_FEATURES -->
|
||||
</manifest>
|
|
@ -0,0 +1,83 @@
|
|||
buildscript {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.4.1'
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
|
||||
implementation "androidx.documentfile:documentfile:1.0.1"
|
||||
implementation 'androidx.core:core:1.10.1'
|
||||
}
|
||||
|
||||
android {
|
||||
/*******************************************************
|
||||
* The following variables:
|
||||
* - androidBuildToolsVersion,
|
||||
* - androidCompileSdkVersion
|
||||
* - qtAndroidDir - holds the path to qt android files
|
||||
* needed to build any Qt application
|
||||
* on Android.
|
||||
*
|
||||
* are defined in gradle.properties file. This file is
|
||||
* updated by QtCreator and androiddeployqt tools.
|
||||
* Changing them manually might break the compilation!
|
||||
*******************************************************/
|
||||
|
||||
compileSdkVersion androidCompileSdkVersion
|
||||
buildToolsVersion androidBuildToolsVersion
|
||||
ndkVersion androidNdkVersion
|
||||
|
||||
// Extract native libraries from the APK
|
||||
packagingOptions.jniLibs.useLegacyPackaging true
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
manifest.srcFile 'AndroidManifest.xml'
|
||||
java.srcDirs = [qtAndroidDir + '/src', 'src', 'java']
|
||||
aidl.srcDirs = [qtAndroidDir + '/src', 'src', 'aidl']
|
||||
res.srcDirs = [qtAndroidDir + '/res', 'res']
|
||||
resources.srcDirs = ['resources']
|
||||
renderscript.srcDirs = ['src']
|
||||
assets.srcDirs = ['assets']
|
||||
jniLibs.srcDirs = ['libs']
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile) {
|
||||
options.incremental = true
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
abortOnError false
|
||||
}
|
||||
|
||||
// Do not compress Qt binary resources file
|
||||
aaptOptions {
|
||||
noCompress 'rcc'
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
resConfig "en"
|
||||
minSdkVersion qtMinSdkVersion
|
||||
targetSdkVersion qtTargetSdkVersion
|
||||
ndk.abiFilters = qtTargetAbiList.split(",")
|
||||
}
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
buildscript {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.2.0'
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
compile 'com.android.support:support-v4:27.1.0'
|
||||
}
|
||||
|
||||
android {
|
||||
/*******************************************************
|
||||
* The following variables:
|
||||
* - androidBuildToolsVersion,
|
||||
* - androidCompileSdkVersion
|
||||
* - qt5AndroidDir - holds the path to qt android files
|
||||
* needed to build any Qt application
|
||||
* on Android.
|
||||
*
|
||||
* are defined in gradle.properties file. This file is
|
||||
* updated by QtCreator and androiddeployqt tools.
|
||||
* Changing them manually might break the compilation!
|
||||
*******************************************************/
|
||||
|
||||
compileSdkVersion androidCompileSdkVersion.toInteger()
|
||||
|
||||
buildToolsVersion androidBuildToolsVersion
|
||||
|
||||
defaultConfig {
|
||||
applicationId "@META_ANDROID_PACKAGE_NAME@"
|
||||
applicationIdSuffix "@ANDROID_APK_APPLICATION_ID_SUFFIX@"
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
manifest.srcFile 'AndroidManifest.xml'
|
||||
java.srcDirs = [qt5AndroidDir + '/src', 'src', 'java']
|
||||
aidl.srcDirs = [qt5AndroidDir + '/src', 'src', 'aidl']
|
||||
res.srcDirs = [qt5AndroidDir + '/res', 'res']
|
||||
resources.srcDirs = ['src']
|
||||
renderscript.srcDirs = ['src']
|
||||
assets.srcDirs = ['assets']
|
||||
jniLibs.srcDirs = ['libs']
|
||||
}
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
abortOnError false
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
android.useAndroidX=true
|
|
@ -1,4 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">@ANDROID_APK_APPLICATION_LABEL@</string>
|
||||
</resources>
|
|
@ -7,9 +7,9 @@ import android.os.Bundle;
|
|||
import android.os.ParcelFileDescriptor;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager.LayoutParams;
|
||||
import android.support.v4.provider.DocumentFile;
|
||||
import androidx.documentfile.provider.DocumentFile;
|
||||
import java.io.FileNotFoundException;
|
||||
import org.qtproject.qt5.android.bindings.QtActivity;
|
||||
import org.qtproject.qt.android.bindings.QtActivity;
|
||||
|
||||
public class Activity extends QtActivity {
|
||||
private final int REQUEST_CODE_OPEN_EXISTING_FILE = 1;
|
||||
|
|
|
@ -15,17 +15,17 @@ Kirigami.ScrollablePage {
|
|||
var currentEntryName = entryModel.data(rootIndex)
|
||||
return currentEntryName ? currentEntryName : ""
|
||||
}
|
||||
actions {
|
||||
main: Kirigami.Action {
|
||||
iconName: "list-add"
|
||||
actions:[
|
||||
Kirigami.Action {
|
||||
icon.name: "list-add"
|
||||
text: qsTr("Add account")
|
||||
visible: !nativeInterface.hasEntryFilter
|
||||
enabled: !nativeInterface.hasEntryFilter
|
||||
onTriggered: insertEntry("Account")
|
||||
shortcut: "Ctrl+A"
|
||||
}
|
||||
left: Kirigami.Action {
|
||||
iconName: "edit-paste"
|
||||
},
|
||||
Kirigami.Action {
|
||||
icon.name: "edit-paste"
|
||||
text: qsTr("Paste account")
|
||||
visible: !nativeInterface.hasEntryFilter
|
||||
enabled: nativeInterface.canPaste && !nativeInterface.hasEntryFilter
|
||||
|
@ -40,16 +40,16 @@ Kirigami.ScrollablePage {
|
|||
pastedEntries.join(", ")))
|
||||
}
|
||||
shortcut: StandardKey.Paste
|
||||
}
|
||||
right: Kirigami.Action {
|
||||
iconName: "folder-add"
|
||||
},
|
||||
Kirigami.Action {
|
||||
icon.name: "folder-add"
|
||||
text: qsTr("Add category")
|
||||
visible: !nativeInterface.hasEntryFilter
|
||||
enabled: !nativeInterface.hasEntryFilter
|
||||
onTriggered: insertEntry("Node")
|
||||
shortcut: "Ctrl+Shift+A"
|
||||
}
|
||||
}
|
||||
]
|
||||
background: Rectangle {
|
||||
color: Kirigami.Theme.backgroundColor
|
||||
}
|
||||
|
@ -218,7 +218,7 @@ Kirigami.ScrollablePage {
|
|||
}
|
||||
actions: [
|
||||
Kirigami.Action {
|
||||
iconName: "edit-cut"
|
||||
icon.name: "edit-cut"
|
||||
text: qsTr("Cut")
|
||||
enabled: !nativeInterface.hasEntryFilter
|
||||
onTriggered: {
|
||||
|
@ -229,7 +229,7 @@ Kirigami.ScrollablePage {
|
|||
shortcut: StandardKey.Cut
|
||||
},
|
||||
Kirigami.Action {
|
||||
iconName: "edit-delete"
|
||||
icon.name: "edit-delete"
|
||||
text: qsTr("Delete")
|
||||
enabled: !nativeInterface.hasEntryFilter
|
||||
onTriggered: confirmDeletionDialog.confirmDeletion(
|
||||
|
@ -237,7 +237,7 @@ Kirigami.ScrollablePage {
|
|||
shortcut: StandardKey.Delete
|
||||
},
|
||||
Kirigami.Action {
|
||||
iconName: "edit-rename"
|
||||
icon.name: "edit-rename"
|
||||
text: qsTr("Rename")
|
||||
enabled: !nativeInterface.hasEntryFilter
|
||||
onTriggered: renameDialog.renameEntry(model.name, index)
|
||||
|
@ -259,11 +259,7 @@ Kirigami.ScrollablePage {
|
|||
}
|
||||
model: DelegateModel {
|
||||
id: delegateModel
|
||||
|
||||
delegate: Kirigami.DelegateRecycler {
|
||||
width: parent ? parent.width : implicitWidth
|
||||
sourceComponent: listDelegateComponent
|
||||
}
|
||||
delegate: listDelegateComponent
|
||||
|
||||
function isNode(rowNumber) {
|
||||
return entryModel.isNode(entryModel.index(rowNumber, 0,
|
||||
|
|
|
@ -218,7 +218,7 @@ Kirigami.ScrollablePage {
|
|||
}
|
||||
actions: [
|
||||
Kirigami.Action {
|
||||
iconName: !model.isPassword ? "password-show-off" : "password-show-on"
|
||||
icon.name: !model.isPassword ? "password-show-off" : "password-show-on"
|
||||
text: model.isPassword ? qsTr(
|
||||
"Mark as normal field") : qsTr(
|
||||
"Mark as password field")
|
||||
|
@ -228,7 +228,7 @@ Kirigami.ScrollablePage {
|
|||
visible: !fieldRow.isLast
|
||||
},
|
||||
Kirigami.Action {
|
||||
iconName: "edit-copy"
|
||||
icon.name: "edit-copy"
|
||||
text: model.isPassword ? qsTr("Copy password") : qsTr(
|
||||
"Copy value")
|
||||
onTriggered: showPassiveNotification(
|
||||
|
@ -239,14 +239,14 @@ Kirigami.ScrollablePage {
|
|||
visible: !fieldRow.isLast
|
||||
},
|
||||
Kirigami.Action {
|
||||
iconName: "edit-delete"
|
||||
icon.name: "edit-delete"
|
||||
text: qsTr("Delete field")
|
||||
onTriggered: fieldsListView.model.removeRows(index, 1)
|
||||
shortcut: StandardKey.Delete
|
||||
visible: !fieldRow.isLast
|
||||
},
|
||||
Kirigami.Action {
|
||||
iconName: "list-add"
|
||||
icon.name: "list-add"
|
||||
text: qsTr("Insert empty field after this")
|
||||
enabled: !nativeInterface.hasEntryFilter
|
||||
onTriggered: fieldsListView.model.insertRows(index + 1, 1)
|
||||
|
@ -267,9 +267,6 @@ Kirigami.ScrollablePage {
|
|||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
delegate: Kirigami.DelegateRecycler {
|
||||
width: parent ? parent.width : implicitWidth
|
||||
sourceComponent: fieldsListDelegateComponent
|
||||
}
|
||||
delegate: fieldsListDelegateComponent
|
||||
}
|
||||
}
|
||||
|
|
36
qml/main.qml
36
qml/main.qml
|
@ -16,6 +16,12 @@ Kirigami.ApplicationWindow {
|
|||
|
||||
title: app.applicationName
|
||||
titleIcon: "qrc://icons/hicolor/scalable/apps/passwordmanager.svg"
|
||||
// FIXME: not sure why this doesn't work anymore
|
||||
//onBannerClicked: () => {
|
||||
// leftMenu.resetMenu()
|
||||
// aboutDialog.open()
|
||||
//}
|
||||
|
||||
visible: true
|
||||
resetMenuOnTriggered: false
|
||||
topContent: ColumnLayout {
|
||||
|
@ -101,20 +107,20 @@ Kirigami.ApplicationWindow {
|
|||
actions: [
|
||||
Kirigami.Action {
|
||||
text: qsTr("Create new file")
|
||||
iconName: "document-new"
|
||||
icon.name: "document-new"
|
||||
onTriggered: fileDialog.createNew()
|
||||
shortcut: StandardKey.New
|
||||
},
|
||||
Kirigami.Action {
|
||||
text: qsTr("Open existing file")
|
||||
iconName: "document-open"
|
||||
icon.name: "document-open"
|
||||
onTriggered: fileDialog.openExisting()
|
||||
shortcut: StandardKey.Open
|
||||
},
|
||||
Kirigami.Action {
|
||||
id: recentlyOpenedAction
|
||||
text: qsTr("Recently opened ...")
|
||||
iconName: "document-open-recent"
|
||||
icon.name: "document-open-recent"
|
||||
children: createRecentlyOpenedActions(
|
||||
nativeInterface.recentFiles)
|
||||
visible: nativeInterface.recentFiles.length > 0
|
||||
|
@ -123,14 +129,14 @@ Kirigami.ApplicationWindow {
|
|||
Kirigami.Action {
|
||||
text: qsTr("Save modifications")
|
||||
enabled: nativeInterface.fileOpen
|
||||
iconName: "document-save"
|
||||
icon.name: "document-save"
|
||||
onTriggered: nativeInterface.save()
|
||||
shortcut: StandardKey.Save
|
||||
},
|
||||
Kirigami.Action {
|
||||
text: qsTr("Save as")
|
||||
enabled: nativeInterface.fileOpen
|
||||
iconName: "document-save-as"
|
||||
icon.name: "document-save-as"
|
||||
onTriggered: fileDialog.saveAs()
|
||||
shortcut: StandardKey.SaveAs
|
||||
},
|
||||
|
@ -138,7 +144,7 @@ Kirigami.ApplicationWindow {
|
|||
text: nativeInterface.passwordSet ? qsTr("Change password") : qsTr(
|
||||
"Add password")
|
||||
enabled: nativeInterface.fileOpen
|
||||
iconName: "document-encrypt"
|
||||
icon.name: "document-encrypt"
|
||||
onTriggered: enterPasswordDialog.askForNewPassword(
|
||||
qsTr("Change password for %1").arg(
|
||||
nativeInterface.filePath))
|
||||
|
@ -147,7 +153,7 @@ Kirigami.ApplicationWindow {
|
|||
Kirigami.Action {
|
||||
text: qsTr("Details")
|
||||
enabled: nativeInterface.fileOpen
|
||||
iconName: "document-properties"
|
||||
icon.name: "document-properties"
|
||||
onTriggered: {
|
||||
leftMenu.resetMenu()
|
||||
fileSummaryDialog.show()
|
||||
|
@ -159,7 +165,7 @@ Kirigami.ApplicationWindow {
|
|||
"Adjust search")
|
||||
enabled: nativeInterface.fileOpen
|
||||
visible: nativeInterface.filterAsDialog
|
||||
iconName: "search"
|
||||
icon.name: "search"
|
||||
onTriggered: {
|
||||
leftMenu.resetMenu()
|
||||
filterDialog.open()
|
||||
|
@ -171,7 +177,7 @@ Kirigami.ApplicationWindow {
|
|||
enabled: nativeInterface.fileOpen
|
||||
visible: nativeInterface.filterAsDialog
|
||||
&& nativeInterface.entryFilter.length > 0
|
||||
iconName: "edit-clear"
|
||||
icon.name: "edit-clear"
|
||||
onTriggered: {
|
||||
leftMenu.resetMenu()
|
||||
nativeInterface.entryFilter = ""
|
||||
|
@ -183,7 +189,7 @@ Kirigami.ApplicationWindow {
|
|||
visible: nativeInterface.undoText.length !== 0
|
||||
&& nativeInterface.entryFilter.length === 0
|
||||
enabled: visible
|
||||
iconName: "edit-undo"
|
||||
icon.name: "edit-undo"
|
||||
shortcut: StandardKey.Undo
|
||||
onTriggered: nativeInterface.undo()
|
||||
},
|
||||
|
@ -192,22 +198,18 @@ Kirigami.ApplicationWindow {
|
|||
visible: nativeInterface.redoText.length !== 0
|
||||
&& nativeInterface.entryFilter.length === 0
|
||||
enabled: visible
|
||||
iconName: "edit-redo"
|
||||
icon.name: "edit-redo"
|
||||
shortcut: StandardKey.Redo
|
||||
onTriggered: nativeInterface.redo()
|
||||
},
|
||||
Kirigami.Action {
|
||||
text: qsTr("Close file")
|
||||
enabled: nativeInterface.fileOpen
|
||||
iconName: "document-close"
|
||||
icon.name: "document-close"
|
||||
shortcut: StandardKey.Close
|
||||
onTriggered: nativeInterface.close()
|
||||
}
|
||||
]
|
||||
onBannerClicked: {
|
||||
leftMenu.resetMenu()
|
||||
aboutDialog.open()
|
||||
}
|
||||
|
||||
Controls.Switch {
|
||||
text: qsTr("Use native file dialog")
|
||||
|
@ -395,7 +397,7 @@ Kirigami.ApplicationWindow {
|
|||
id: clearRecentFilesActionComponent
|
||||
Kirigami.Action {
|
||||
text: qsTr("Clear recently opened files")
|
||||
iconName: "edit-clear"
|
||||
icon.name: "edit-clear"
|
||||
onTriggered: {
|
||||
nativeInterface.clearRecentFiles()
|
||||
leftMenu.resetMenu()
|
||||
|
|
|
@ -5,12 +5,11 @@
|
|||
|
||||
#include <c++utilities/conversion/stringbuilder.h>
|
||||
|
||||
#include <QAndroidJniObject>
|
||||
#include <QJniObject>
|
||||
#include <QColor>
|
||||
#include <QCoreApplication>
|
||||
#include <QMessageLogContext>
|
||||
#include <QMetaObject>
|
||||
#include <QtAndroid>
|
||||
|
||||
#include <android/log.h>
|
||||
|
||||
|
@ -35,9 +34,9 @@ static Controller *controllerForAndroid = nullptr;
|
|||
|
||||
void applyThemingForAndroid()
|
||||
{
|
||||
QtAndroid::runOnAndroidThread([=]() {
|
||||
QNativeInterface::QAndroidApplication::runOnAndroidMainThread([=]() {
|
||||
const auto color = QColor(QLatin1String("#2c714a")).rgba();
|
||||
QAndroidJniObject window = QtAndroid::androidActivity().callObjectMethod("getWindow", "()Landroid/view/Window;");
|
||||
QJniObject window = QJniObject(QNativeInterface::QAndroidApplication::context()).callObjectMethod("getWindow", "()Landroid/view/Window;");
|
||||
window.callMethod<void>("addFlags", "(I)V", Android::WindowManager::LayoutParams::DrawsSystemBarBackgrounds);
|
||||
window.callMethod<void>("clearFlags", "(I)V", Android::WindowManager::LayoutParams::TranslucentStatus);
|
||||
window.callMethod<void>("setStatusBarColor", "(I)V", color);
|
||||
|
@ -52,13 +51,13 @@ void registerControllerForAndroid(Controller *controller)
|
|||
|
||||
bool showAndroidFileDialog(bool existing, bool createNew)
|
||||
{
|
||||
return QtAndroid::androidActivity().callMethod<jboolean>("showAndroidFileDialog", "(ZZ)Z", existing, createNew);
|
||||
return QJniObject(QNativeInterface::QAndroidApplication::context()).callMethod<jboolean>("showAndroidFileDialog", "(ZZ)Z", existing, createNew);
|
||||
}
|
||||
|
||||
int openFileDescriptorFromAndroidContentUrl(const QString &url, const QString &mode)
|
||||
{
|
||||
return QtAndroid::androidActivity().callMethod<jint>("openFileDescriptorFromAndroidContentUri", "(Ljava/lang/String;Ljava/lang/String;)I",
|
||||
QAndroidJniObject::fromString(url).object<jstring>(), QAndroidJniObject::fromString(mode).object<jstring>());
|
||||
return QJniObject(QNativeInterface::QAndroidApplication::context()).callMethod<jint>("openFileDescriptorFromAndroidContentUri", "(Ljava/lang/String;Ljava/lang/String;)I",
|
||||
QJniObject::fromString(url).object<jstring>(), QJniObject::fromString(mode).object<jstring>());
|
||||
}
|
||||
|
||||
void writeToAndroidLog(QtMsgType type, const QMessageLogContext &context, const QString &msg)
|
||||
|
@ -102,19 +101,19 @@ void setupAndroidSpecifics()
|
|||
static void onAndroidError(JNIEnv *, jobject, jstring message)
|
||||
{
|
||||
QMetaObject::invokeMethod(
|
||||
QtGui::controllerForAndroid, "newNotification", Qt::QueuedConnection, Q_ARG(QString, QAndroidJniObject::fromLocalRef(message).toString()));
|
||||
QtGui::controllerForAndroid, "newNotification", Qt::QueuedConnection, Q_ARG(QString, QJniObject::fromLocalRef(message).toString()));
|
||||
}
|
||||
|
||||
static void onAndroidFileDialogAccepted(JNIEnv *, jobject, jstring fileName, jboolean existing, jboolean createNew)
|
||||
{
|
||||
QMetaObject::invokeMethod(QtGui::controllerForAndroid, "handleFileSelectionAccepted", Qt::QueuedConnection,
|
||||
Q_ARG(QString, QAndroidJniObject::fromLocalRef(fileName).toString()), Q_ARG(bool, existing), Q_ARG(bool, createNew));
|
||||
Q_ARG(QString, QJniObject::fromLocalRef(fileName).toString()), Q_ARG(bool, existing), Q_ARG(bool, createNew));
|
||||
}
|
||||
|
||||
static void onAndroidFileDialogAcceptedDescriptor(JNIEnv *, jobject, jstring nativeUrl, jstring fileName, jint fileHandle, jboolean existing, jboolean createNew)
|
||||
{
|
||||
QMetaObject::invokeMethod(QtGui::controllerForAndroid, "handleFileSelectionAcceptedDescriptor", Qt::QueuedConnection,
|
||||
Q_ARG(QString, QAndroidJniObject::fromLocalRef(nativeUrl).toString()), Q_ARG(QString, QAndroidJniObject::fromLocalRef(fileName).toString()),
|
||||
Q_ARG(QString, QJniObject::fromLocalRef(nativeUrl).toString()), Q_ARG(QString, QJniObject::fromLocalRef(fileName).toString()),
|
||||
Q_ARG(int, fileHandle), Q_ARG(bool, existing), Q_ARG(bool, createNew));
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
<?xml version="1.0"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="@META_ANDROID_PACKAGE_NAME@"
|
||||
android:label="@META_APP_NAME@"
|
||||
android:installLocation="auto"
|
||||
android:versionName="@META_APP_VERSION@"
|
||||
android:versionCode="@META_VERSION_MAJOR@">
|
||||
<!-- %%INSERT_PERMISSIONS -->
|
||||
<!-- %%INSERT_FEATURES -->
|
||||
<supports-screens
|
||||
android:anyDensity="true"
|
||||
android:largeScreens="true"
|
||||
android:normalScreens="true"
|
||||
android:smallScreens="true" />
|
||||
<application
|
||||
android:name="org.qtproject.qt.android.bindings.QtApplication"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:hardwareAccelerated="true"
|
||||
android:label="@META_APP_NAME@"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:allowNativeHeapPointerTagging="false"
|
||||
android:allowBackup="true"
|
||||
android:fullBackupOnly="false">
|
||||
<activity
|
||||
android:name="org.martchus.passwordmanager.Activity"
|
||||
android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation|mcc|mnc|density"
|
||||
android:label="@META_APP_NAME@"
|
||||
android:launchMode="singleTop"
|
||||
android:screenOrientation="unspecified"
|
||||
android:exported="true"
|
||||
android:theme="@style/AppTheme">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
<meta-data
|
||||
android:name="android.app.lib_name"
|
||||
android:value="-- %%INSERT_APP_LIB_NAME%% --" />
|
||||
<meta-data
|
||||
android:name="android.app.arguments"
|
||||
android:value="-- %%INSERT_APP_ARGUMENTS%% --" />
|
||||
<meta-data
|
||||
android:name="android.app.extract_android_style"
|
||||
android:value="minimal" />
|
||||
<meta-data
|
||||
android:name="android.app.splash_screen_drawable"
|
||||
android:resource="@drawable/splash" />
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
Loading…
Reference in New Issue