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
# Android-specific
/android/AndroidManifest.xml

View File

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

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

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.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;

View File

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

View File

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

View File

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

View File

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

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>