Port Qt Quick GUI for Android to Qt 6

This commit is contained in:
Martchus 2023-03-04 17:27:38 +01:00
parent 430e17416a
commit 4bf6a91d72
14 changed files with 248 additions and 298 deletions

3
.gitignore vendored
View File

@ -42,3 +42,6 @@ Makefile*
# clang-format # clang-format
/.clang-format /.clang-format
# Android-specific
/android/AndroidManifest.xml

View File

@ -130,11 +130,6 @@ use_qt_utilities()
find_package(passwordfile${CONFIGURATION_PACKAGE_SUFFIX} 5.0.0 REQUIRED) find_package(passwordfile${CONFIGURATION_PACKAGE_SUFFIX} 5.0.0 REQUIRED)
use_password_file() 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 # 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) # well)
if (QUICK_GUI AND NOT WIDGETS_GUI) if (QUICK_GUI AND NOT WIDGETS_GUI)
@ -147,20 +142,33 @@ if (QUICK_GUI AND NOT WIDGETS_GUI)
endif () endif ()
endif () endif ()
# add further Qt/KF modules required by the Qt Quick GUI under Android # deduce major Qt version from package prefix
if (ANDROID) if (NOT QT_PACKAGE_PREFIX)
list(APPEND ADDITIONAL_QT_MODULES AndroidExtras) set(MAJOR_QT_VERSION "5")
elseif (QT_PACKAGE_PREFIX MATCHES ".*Qt([0-9]+).*")
set(MAJOR_QT_VERSION "${CMAKE_MATCH_1}")
endif () 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) if (QUICK_GUI)
list(APPEND ADDITIONAL_QT_MODULES QuickControls2)
list(APPEND ADDITIONAL_KF_MODULES Kirigami2) list(APPEND ADDITIONAL_KF_MODULES Kirigami2)
endif () endif ()
# add Qt-version-specific QML files # add Qt-version-specific QML files
unset(QML_FILE) unset(QML_FILE)
if (NOT QT_PACKAGE_PREFIX) if (MAJOR_QT_VERSION)
set(QML_FILE "resources/qml5.qrc") set(QML_FILE "resources/qml${MAJOR_QT_VERSION}.qrc")
elseif (QT_PACKAGE_PREFIX MATCHES ".*Qt([0-9]+).*")
set(QML_FILE "resources/qml${CMAKE_MATCH_1}.qrc")
endif () endif ()
if (NOT QML_FILE) if (NOT QML_FILE)
message(FATAL_ERROR "Unable to add Qt-version-specific resource file for QT_PACKAGE_PREFIX \"${QT_PACKAGE_PREFIX}\".") 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 () endif ()
include(WindowsResources) include(WindowsResources)
include(AppTarget) include(AppTarget)
include(AndroidApk)
include(ShellCompletion) include(ShellCompletion)
include(ConfigHeader) 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 # create desktop file using previously defined meta data
add_desktop_file() add_desktop_file()

131
README.md
View File

@ -124,131 +124,44 @@ always requires the same major Qt version as your KDE modules use.
the desired location afterwards. the desired location afterwards.
#### Concrete example of 3. for building an Android APK under Arch Linux #### 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_dir=/path/to/keystore-dir
keystore_alias=$USER 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
keystore_url=$keystore_dir/$keystore_alias
# make up some password to protect the store; enter this on keytool invocation
keystore_password=<password>
# create keystore (do only once) # create keystore (do only once)
mkdir -p "$keystore_dir"
pushd "$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 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 # use Java 17, the latest Java doesn't work at this point and avoid unwanted Java options
_pkg_arch=aarch64 export PATH=/usr/lib/jvm/java-17-openjdk/bin:$PATH
_android_arch=arm64-v8a export _JAVA_OPTIONS=
_android_arch2=arm64
_android_api_level=22
# set project name # configure and build using helpers from android-cmake package
_reponame=passwordmanager android_arch=aarch64
_pkgname=passwordmanager 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 # install the app
android_sdk_root=${ANDROID_SDK_ROOT:-/opt/android-sdk} adb install "$build_dir/passwordmanager/android-build//build/outputs/apk/release/android-build-release-signed.apk"
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
``` ```
##### Notes ##### 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. my [PKGBUILDs](http://github.com/Martchus/PKGBUILDs) repo.
* The latest Java I was able to use was version 8 (`jdk8-openjdk` package). * The latest Java I was able to use was version 17.
### 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`
### Building without Qt GUI ### 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. It is possible to build without the GUI if only the CLI is needed. In this case no Qt dependencies (including qtutilities) are required.

View File

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

83
android/build.gradle Normal file
View File

@ -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(",")
}
}

View File

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

View File

@ -0,0 +1 @@
android.useAndroidX=true

View File

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">@ANDROID_APK_APPLICATION_LABEL@</string>
</resources>

View File

@ -7,9 +7,9 @@ import android.os.Bundle;
import android.os.ParcelFileDescriptor; import android.os.ParcelFileDescriptor;
import android.view.Window; import android.view.Window;
import android.view.WindowManager.LayoutParams; import android.view.WindowManager.LayoutParams;
import android.support.v4.provider.DocumentFile; import androidx.documentfile.provider.DocumentFile;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import org.qtproject.qt5.android.bindings.QtActivity; import org.qtproject.qt.android.bindings.QtActivity;
public class Activity extends QtActivity { public class Activity extends QtActivity {
private final int REQUEST_CODE_OPEN_EXISTING_FILE = 1; private final int REQUEST_CODE_OPEN_EXISTING_FILE = 1;

View File

@ -15,17 +15,17 @@ Kirigami.ScrollablePage {
var currentEntryName = entryModel.data(rootIndex) var currentEntryName = entryModel.data(rootIndex)
return currentEntryName ? currentEntryName : "" return currentEntryName ? currentEntryName : ""
} }
actions { actions:[
main: Kirigami.Action { Kirigami.Action {
iconName: "list-add" icon.name: "list-add"
text: qsTr("Add account") text: qsTr("Add account")
visible: !nativeInterface.hasEntryFilter visible: !nativeInterface.hasEntryFilter
enabled: !nativeInterface.hasEntryFilter enabled: !nativeInterface.hasEntryFilter
onTriggered: insertEntry("Account") onTriggered: insertEntry("Account")
shortcut: "Ctrl+A" shortcut: "Ctrl+A"
} },
left: Kirigami.Action { Kirigami.Action {
iconName: "edit-paste" icon.name: "edit-paste"
text: qsTr("Paste account") text: qsTr("Paste account")
visible: !nativeInterface.hasEntryFilter visible: !nativeInterface.hasEntryFilter
enabled: nativeInterface.canPaste && !nativeInterface.hasEntryFilter enabled: nativeInterface.canPaste && !nativeInterface.hasEntryFilter
@ -40,16 +40,16 @@ Kirigami.ScrollablePage {
pastedEntries.join(", "))) pastedEntries.join(", ")))
} }
shortcut: StandardKey.Paste shortcut: StandardKey.Paste
} },
right: Kirigami.Action { Kirigami.Action {
iconName: "folder-add" icon.name: "folder-add"
text: qsTr("Add category") text: qsTr("Add category")
visible: !nativeInterface.hasEntryFilter visible: !nativeInterface.hasEntryFilter
enabled: !nativeInterface.hasEntryFilter enabled: !nativeInterface.hasEntryFilter
onTriggered: insertEntry("Node") onTriggered: insertEntry("Node")
shortcut: "Ctrl+Shift+A" shortcut: "Ctrl+Shift+A"
} }
} ]
background: Rectangle { background: Rectangle {
color: Kirigami.Theme.backgroundColor color: Kirigami.Theme.backgroundColor
} }
@ -218,7 +218,7 @@ Kirigami.ScrollablePage {
} }
actions: [ actions: [
Kirigami.Action { Kirigami.Action {
iconName: "edit-cut" icon.name: "edit-cut"
text: qsTr("Cut") text: qsTr("Cut")
enabled: !nativeInterface.hasEntryFilter enabled: !nativeInterface.hasEntryFilter
onTriggered: { onTriggered: {
@ -229,7 +229,7 @@ Kirigami.ScrollablePage {
shortcut: StandardKey.Cut shortcut: StandardKey.Cut
}, },
Kirigami.Action { Kirigami.Action {
iconName: "edit-delete" icon.name: "edit-delete"
text: qsTr("Delete") text: qsTr("Delete")
enabled: !nativeInterface.hasEntryFilter enabled: !nativeInterface.hasEntryFilter
onTriggered: confirmDeletionDialog.confirmDeletion( onTriggered: confirmDeletionDialog.confirmDeletion(
@ -237,7 +237,7 @@ Kirigami.ScrollablePage {
shortcut: StandardKey.Delete shortcut: StandardKey.Delete
}, },
Kirigami.Action { Kirigami.Action {
iconName: "edit-rename" icon.name: "edit-rename"
text: qsTr("Rename") text: qsTr("Rename")
enabled: !nativeInterface.hasEntryFilter enabled: !nativeInterface.hasEntryFilter
onTriggered: renameDialog.renameEntry(model.name, index) onTriggered: renameDialog.renameEntry(model.name, index)
@ -259,11 +259,7 @@ Kirigami.ScrollablePage {
} }
model: DelegateModel { model: DelegateModel {
id: delegateModel id: delegateModel
delegate: listDelegateComponent
delegate: Kirigami.DelegateRecycler {
width: parent ? parent.width : implicitWidth
sourceComponent: listDelegateComponent
}
function isNode(rowNumber) { function isNode(rowNumber) {
return entryModel.isNode(entryModel.index(rowNumber, 0, return entryModel.isNode(entryModel.index(rowNumber, 0,

View File

@ -218,7 +218,7 @@ Kirigami.ScrollablePage {
} }
actions: [ actions: [
Kirigami.Action { 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( text: model.isPassword ? qsTr(
"Mark as normal field") : qsTr( "Mark as normal field") : qsTr(
"Mark as password field") "Mark as password field")
@ -228,7 +228,7 @@ Kirigami.ScrollablePage {
visible: !fieldRow.isLast visible: !fieldRow.isLast
}, },
Kirigami.Action { Kirigami.Action {
iconName: "edit-copy" icon.name: "edit-copy"
text: model.isPassword ? qsTr("Copy password") : qsTr( text: model.isPassword ? qsTr("Copy password") : qsTr(
"Copy value") "Copy value")
onTriggered: showPassiveNotification( onTriggered: showPassiveNotification(
@ -239,14 +239,14 @@ Kirigami.ScrollablePage {
visible: !fieldRow.isLast visible: !fieldRow.isLast
}, },
Kirigami.Action { Kirigami.Action {
iconName: "edit-delete" icon.name: "edit-delete"
text: qsTr("Delete field") text: qsTr("Delete field")
onTriggered: fieldsListView.model.removeRows(index, 1) onTriggered: fieldsListView.model.removeRows(index, 1)
shortcut: StandardKey.Delete shortcut: StandardKey.Delete
visible: !fieldRow.isLast visible: !fieldRow.isLast
}, },
Kirigami.Action { Kirigami.Action {
iconName: "list-add" icon.name: "list-add"
text: qsTr("Insert empty field after this") text: qsTr("Insert empty field after this")
enabled: !nativeInterface.hasEntryFilter enabled: !nativeInterface.hasEntryFilter
onTriggered: fieldsListView.model.insertRows(index + 1, 1) onTriggered: fieldsListView.model.insertRows(index + 1, 1)
@ -267,9 +267,6 @@ Kirigami.ScrollablePage {
easing.type: Easing.InOutQuad easing.type: Easing.InOutQuad
} }
} }
delegate: Kirigami.DelegateRecycler { delegate: fieldsListDelegateComponent
width: parent ? parent.width : implicitWidth
sourceComponent: fieldsListDelegateComponent
}
} }
} }

View File

@ -16,6 +16,12 @@ Kirigami.ApplicationWindow {
title: app.applicationName title: app.applicationName
titleIcon: "qrc://icons/hicolor/scalable/apps/passwordmanager.svg" titleIcon: "qrc://icons/hicolor/scalable/apps/passwordmanager.svg"
// FIXME: not sure why this doesn't work anymore
//onBannerClicked: () => {
// leftMenu.resetMenu()
// aboutDialog.open()
//}
visible: true visible: true
resetMenuOnTriggered: false resetMenuOnTriggered: false
topContent: ColumnLayout { topContent: ColumnLayout {
@ -101,20 +107,20 @@ Kirigami.ApplicationWindow {
actions: [ actions: [
Kirigami.Action { Kirigami.Action {
text: qsTr("Create new file") text: qsTr("Create new file")
iconName: "document-new" icon.name: "document-new"
onTriggered: fileDialog.createNew() onTriggered: fileDialog.createNew()
shortcut: StandardKey.New shortcut: StandardKey.New
}, },
Kirigami.Action { Kirigami.Action {
text: qsTr("Open existing file") text: qsTr("Open existing file")
iconName: "document-open" icon.name: "document-open"
onTriggered: fileDialog.openExisting() onTriggered: fileDialog.openExisting()
shortcut: StandardKey.Open shortcut: StandardKey.Open
}, },
Kirigami.Action { Kirigami.Action {
id: recentlyOpenedAction id: recentlyOpenedAction
text: qsTr("Recently opened ...") text: qsTr("Recently opened ...")
iconName: "document-open-recent" icon.name: "document-open-recent"
children: createRecentlyOpenedActions( children: createRecentlyOpenedActions(
nativeInterface.recentFiles) nativeInterface.recentFiles)
visible: nativeInterface.recentFiles.length > 0 visible: nativeInterface.recentFiles.length > 0
@ -123,14 +129,14 @@ Kirigami.ApplicationWindow {
Kirigami.Action { Kirigami.Action {
text: qsTr("Save modifications") text: qsTr("Save modifications")
enabled: nativeInterface.fileOpen enabled: nativeInterface.fileOpen
iconName: "document-save" icon.name: "document-save"
onTriggered: nativeInterface.save() onTriggered: nativeInterface.save()
shortcut: StandardKey.Save shortcut: StandardKey.Save
}, },
Kirigami.Action { Kirigami.Action {
text: qsTr("Save as") text: qsTr("Save as")
enabled: nativeInterface.fileOpen enabled: nativeInterface.fileOpen
iconName: "document-save-as" icon.name: "document-save-as"
onTriggered: fileDialog.saveAs() onTriggered: fileDialog.saveAs()
shortcut: StandardKey.SaveAs shortcut: StandardKey.SaveAs
}, },
@ -138,7 +144,7 @@ Kirigami.ApplicationWindow {
text: nativeInterface.passwordSet ? qsTr("Change password") : qsTr( text: nativeInterface.passwordSet ? qsTr("Change password") : qsTr(
"Add password") "Add password")
enabled: nativeInterface.fileOpen enabled: nativeInterface.fileOpen
iconName: "document-encrypt" icon.name: "document-encrypt"
onTriggered: enterPasswordDialog.askForNewPassword( onTriggered: enterPasswordDialog.askForNewPassword(
qsTr("Change password for %1").arg( qsTr("Change password for %1").arg(
nativeInterface.filePath)) nativeInterface.filePath))
@ -147,7 +153,7 @@ Kirigami.ApplicationWindow {
Kirigami.Action { Kirigami.Action {
text: qsTr("Details") text: qsTr("Details")
enabled: nativeInterface.fileOpen enabled: nativeInterface.fileOpen
iconName: "document-properties" icon.name: "document-properties"
onTriggered: { onTriggered: {
leftMenu.resetMenu() leftMenu.resetMenu()
fileSummaryDialog.show() fileSummaryDialog.show()
@ -159,7 +165,7 @@ Kirigami.ApplicationWindow {
"Adjust search") "Adjust search")
enabled: nativeInterface.fileOpen enabled: nativeInterface.fileOpen
visible: nativeInterface.filterAsDialog visible: nativeInterface.filterAsDialog
iconName: "search" icon.name: "search"
onTriggered: { onTriggered: {
leftMenu.resetMenu() leftMenu.resetMenu()
filterDialog.open() filterDialog.open()
@ -171,7 +177,7 @@ Kirigami.ApplicationWindow {
enabled: nativeInterface.fileOpen enabled: nativeInterface.fileOpen
visible: nativeInterface.filterAsDialog visible: nativeInterface.filterAsDialog
&& nativeInterface.entryFilter.length > 0 && nativeInterface.entryFilter.length > 0
iconName: "edit-clear" icon.name: "edit-clear"
onTriggered: { onTriggered: {
leftMenu.resetMenu() leftMenu.resetMenu()
nativeInterface.entryFilter = "" nativeInterface.entryFilter = ""
@ -183,7 +189,7 @@ Kirigami.ApplicationWindow {
visible: nativeInterface.undoText.length !== 0 visible: nativeInterface.undoText.length !== 0
&& nativeInterface.entryFilter.length === 0 && nativeInterface.entryFilter.length === 0
enabled: visible enabled: visible
iconName: "edit-undo" icon.name: "edit-undo"
shortcut: StandardKey.Undo shortcut: StandardKey.Undo
onTriggered: nativeInterface.undo() onTriggered: nativeInterface.undo()
}, },
@ -192,22 +198,18 @@ Kirigami.ApplicationWindow {
visible: nativeInterface.redoText.length !== 0 visible: nativeInterface.redoText.length !== 0
&& nativeInterface.entryFilter.length === 0 && nativeInterface.entryFilter.length === 0
enabled: visible enabled: visible
iconName: "edit-redo" icon.name: "edit-redo"
shortcut: StandardKey.Redo shortcut: StandardKey.Redo
onTriggered: nativeInterface.redo() onTriggered: nativeInterface.redo()
}, },
Kirigami.Action { Kirigami.Action {
text: qsTr("Close file") text: qsTr("Close file")
enabled: nativeInterface.fileOpen enabled: nativeInterface.fileOpen
iconName: "document-close" icon.name: "document-close"
shortcut: StandardKey.Close shortcut: StandardKey.Close
onTriggered: nativeInterface.close() onTriggered: nativeInterface.close()
} }
] ]
onBannerClicked: {
leftMenu.resetMenu()
aboutDialog.open()
}
Controls.Switch { Controls.Switch {
text: qsTr("Use native file dialog") text: qsTr("Use native file dialog")
@ -395,7 +397,7 @@ Kirigami.ApplicationWindow {
id: clearRecentFilesActionComponent id: clearRecentFilesActionComponent
Kirigami.Action { Kirigami.Action {
text: qsTr("Clear recently opened files") text: qsTr("Clear recently opened files")
iconName: "edit-clear" icon.name: "edit-clear"
onTriggered: { onTriggered: {
nativeInterface.clearRecentFiles() nativeInterface.clearRecentFiles()
leftMenu.resetMenu() leftMenu.resetMenu()

View File

@ -5,12 +5,11 @@
#include <c++utilities/conversion/stringbuilder.h> #include <c++utilities/conversion/stringbuilder.h>
#include <QAndroidJniObject> #include <QJniObject>
#include <QColor> #include <QColor>
#include <QCoreApplication> #include <QCoreApplication>
#include <QMessageLogContext> #include <QMessageLogContext>
#include <QMetaObject> #include <QMetaObject>
#include <QtAndroid>
#include <android/log.h> #include <android/log.h>
@ -35,9 +34,9 @@ static Controller *controllerForAndroid = nullptr;
void applyThemingForAndroid() void applyThemingForAndroid()
{ {
QtAndroid::runOnAndroidThread([=]() { QNativeInterface::QAndroidApplication::runOnAndroidMainThread([=]() {
const auto color = QColor(QLatin1String("#2c714a")).rgba(); 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>("addFlags", "(I)V", Android::WindowManager::LayoutParams::DrawsSystemBarBackgrounds);
window.callMethod<void>("clearFlags", "(I)V", Android::WindowManager::LayoutParams::TranslucentStatus); window.callMethod<void>("clearFlags", "(I)V", Android::WindowManager::LayoutParams::TranslucentStatus);
window.callMethod<void>("setStatusBarColor", "(I)V", color); window.callMethod<void>("setStatusBarColor", "(I)V", color);
@ -52,13 +51,13 @@ void registerControllerForAndroid(Controller *controller)
bool showAndroidFileDialog(bool existing, bool createNew) 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) int openFileDescriptorFromAndroidContentUrl(const QString &url, const QString &mode)
{ {
return QtAndroid::androidActivity().callMethod<jint>("openFileDescriptorFromAndroidContentUri", "(Ljava/lang/String;Ljava/lang/String;)I", return QJniObject(QNativeInterface::QAndroidApplication::context()).callMethod<jint>("openFileDescriptorFromAndroidContentUri", "(Ljava/lang/String;Ljava/lang/String;)I",
QAndroidJniObject::fromString(url).object<jstring>(), QAndroidJniObject::fromString(mode).object<jstring>()); QJniObject::fromString(url).object<jstring>(), QJniObject::fromString(mode).object<jstring>());
} }
void writeToAndroidLog(QtMsgType type, const QMessageLogContext &context, const QString &msg) void writeToAndroidLog(QtMsgType type, const QMessageLogContext &context, const QString &msg)
@ -102,19 +101,19 @@ void setupAndroidSpecifics()
static void onAndroidError(JNIEnv *, jobject, jstring message) static void onAndroidError(JNIEnv *, jobject, jstring message)
{ {
QMetaObject::invokeMethod( 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) static void onAndroidFileDialogAccepted(JNIEnv *, jobject, jstring fileName, jboolean existing, jboolean createNew)
{ {
QMetaObject::invokeMethod(QtGui::controllerForAndroid, "handleFileSelectionAccepted", Qt::QueuedConnection, 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) static void onAndroidFileDialogAcceptedDescriptor(JNIEnv *, jobject, jstring nativeUrl, jstring fileName, jint fileHandle, jboolean existing, jboolean createNew)
{ {
QMetaObject::invokeMethod(QtGui::controllerForAndroid, "handleFileSelectionAcceptedDescriptor", Qt::QueuedConnection, 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)); Q_ARG(int, fileHandle), Q_ARG(bool, existing), Q_ARG(bool, createNew));
} }

View File

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