Commit 75e849da authored by André Hoarau's avatar André Hoarau 🍆
Browse files

Merge branch '290-provide-android-apks' into 'master'

Resolve "Provide Android APKs"

Closes #290

See merge request !117
parents 8b20b08a 418a16dd
......@@ -74,14 +74,14 @@ configure_file(
string(TIMESTAMP BUILD_DATE %Y%m)
#if(ANDROID)
# set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android")
if(ANDROID)
set(ANDROID_PACKAGE_SOURCE_DIR "${PROJECT_SOURCE_DIR}/desktop/android")
# if (ANDROID_ABI STREQUAL "armeabi-v7a")
# set(ANDROID_EXTRA_LIBS
# ${CMAKE_CURRENT_SOURCE_DIR}/path/to/libcrypto.so
# ${CMAKE_CURRENT_SOURCE_DIR}/path/to/libssl.so)
# endif()
#endif()
endif()
find_package(Qt5 ${MIN_QT_VERSION}
COMPONENTS
......
......@@ -12,6 +12,7 @@ add_definitions(-DCORE_LIBRARY)
add_library(core SHARED
buildinfo.cpp
core_resources.qrc
filesystem.cpp
helpers.cpp
qrpdate.cpp
nametree.cpp
......@@ -67,4 +68,9 @@ add_library(core SHARED
)
target_link_libraries(core Qt5::Core Qt5::Quick Qt5::Gui Qt5::Sql Qt5::PrintSupport)
if (ANDROID)
message(STATUS "Linking Android extras...")
target_link_libraries(core Qt5::Core Qt5::Quick Qt5::Gui Qt5::Sql Qt5::PrintSupport Qt5::AndroidExtras)
else()
target_link_libraries(core Qt5::Core Qt5::Quick Qt5::Gui Qt5::Sql Qt5::PrintSupport)
endif()
......@@ -39,8 +39,7 @@ BuildInfo::BuildInfo(QObject *parent)
#else
m_branch(tr("Unknown commit branch"))
#endif
{
}
{}
QString BuildInfo::version() const
{
......
......@@ -17,6 +17,15 @@ public:
QString commit() const;
QString branch() const;
inline Q_INVOKABLE bool isMobileDevice()
{
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
return true;
#else
return false;
#endif
}
private:
QString m_version;
QString m_commit;
......
......@@ -436,7 +436,7 @@ void Planting::duplicateListToNextYear(const QList<int> &idList) const
{
qDebug() << "Batch duplicate to next year:" << idList;
QSqlDatabase::database().transaction();
for (const int id : idList){
for (const int id : idList) {
QDate fromDate = plantingDate(id);
duplicateToYear(id, fromDate.year() + 1);
}
......@@ -802,14 +802,18 @@ QVariantList Planting::highestRevenueCropRevenues(int year, bool greenhouse) con
return list;
}
void Planting::csvImportPlan(int year, const QUrl &path) const
QString Planting::csvImportPlan(int year, const QUrl &path) const
{
qDebug() << "[MB_TRACE][Planting::csvExportPlan] year: " << year << ", path: " << path.toString();
if (year < 1000 || year > 3000)
return;
return tr("Wrong year (must be between 1000 and 3000)");
QFile f(path.toLocalFile());
if (!f.open(QIODevice::ReadOnly))
return;
if (!f.open(QIODevice::ReadOnly)) {
qDebug() << "[MB_TRACE][Planting::csvImportPlan] can't open file...";
return tr("Can't open csv file %1").arg(path.toString());
}
QTextStream ts(&f);
QList<QString> fieldList = ts.readLine().split(";");
......@@ -904,18 +908,16 @@ void Planting::csvImportPlan(int year, const QUrl &path) const
sdate = QrpDate::dateFromWeekString(line[i].trimmed(), year);
if (!sdate.isValid()) {
sdate = QrpDate::dateFromIsoString(line[i].trimmed());
if(!sdate.isValid()){
qDebug() << "Bad date format, should be week number:" << line[i];
return;
if (!sdate.isValid()) {
return tr("Bad date format, should be week number or ISO date: %1").arg(line[i]);
}
}
} else if (field == "planting_date") {
pdate = QrpDate::dateFromWeekString(line[i].trimmed(), year);
if (!pdate.isValid()) {
pdate = QrpDate::dateFromIsoString(line[i].trimmed());
if(!pdate.isValid()){
qDebug() << "Bad date format, should be week number:" << line[i];
return;
if (!pdate.isValid()) {
return tr("Bad date format, should be week number or ISO date: %1").arg(line[i]);
}
}
map[field] = pdate;
......@@ -923,18 +925,16 @@ void Planting::csvImportPlan(int year, const QUrl &path) const
bhdate = QrpDate::dateFromWeekString(line[i].trimmed(), year);
if (!bhdate.isValid()) {
bhdate = QrpDate::dateFromIsoString(line[i].trimmed());
if(!bhdate.isValid()){
qDebug() << "Bad date format, should be week number:" << line[i];
return;
if (!bhdate.isValid()) {
return tr("Bad date format, should be week number or ISO date: %1").arg(line[i]);
}
}
} else if (field == "end_harvest_date") {
ehdate = QrpDate::dateFromWeekString(line[i].trimmed(), year);
if (!ehdate.isValid()) {
ehdate = QrpDate::dateFromIsoString(line[i].trimmed());
if(!ehdate.isValid()){
qDebug() << "Bad date format, should be week number:" << line[i];
return;
if (!ehdate.isValid()) {
return tr("Bad date format, should be week number or ISO date: %1").arg(line[i]);
}
}
} else if (field == "in_greenhouse") {
......@@ -1012,21 +1012,28 @@ void Planting::csvImportPlan(int year, const QUrl &path) const
}
QSqlDatabase::database().commit();
f.close();
return QString();
}
void Planting::csvExportPlan(int year, const QUrl &path) const
QString Planting::csvExportPlan(int year, const QUrl &path) const
{
qDebug() << "[MB_TRACE][Planting::csvExportPlan] year: " << year << ", path: " << path.toString();
QFile f(path.toLocalFile());
if (f.exists())
f.remove();
if (!f.open(QIODevice::ReadWrite))
return;
if (!f.open(QIODevice::WriteOnly | QIODevice::Text)) {
qDebug() << "[MB_TRACE][Planting::csvExportPlan] can't open file: " << path.toString();
return tr("Can't write file: %1").arg(path.toString());
}
QTextStream ts(&f);
auto idList = yearPlantingList(year);
qDebug() << "[MB_TRACE][Planting::csvExportPlan] idList: " << idList;
QList<QString> keyList = { "family",
"crop",
"seed_company",
......@@ -1096,6 +1103,7 @@ void Planting::csvExportPlan(int year, const QUrl &path) const
ts << "\n";
}
f.close();
return QString();
}
QString Planting::toolTip(int plantingId, int locationId) const
......
......@@ -94,8 +94,8 @@ public:
Q_INVOKABLE QVariantMap lastValues(int varietyId, int cropId, int plantingType,
bool inGreenhouse) const;
Q_INVOKABLE void csvImportPlan(int year, const QUrl &path) const;
Q_INVOKABLE void csvExportPlan(int year, const QUrl &path) const;
Q_INVOKABLE QString csvImportPlan(int year, const QUrl &path) const;
Q_INVOKABLE QString csvExportPlan(int year, const QUrl &path) const;
Q_INVOKABLE QString toolTip(int plantingId, int locationId) const;
QString growBarDescription(const QSqlRecord &record, int year, bool showNames) const;
......
#include "filesystem.h"
#include <QStandardPaths>
#include <QDebug>
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
#define APP_NAME "Qrop"
#include <QDir>
#if defined(Q_OS_ANDROID)
#include <QtAndroidExtras/QtAndroid>
#endif
#endif
QString FileSystem::s_rootPath = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
const QMap<QString, QString> FileSystem::s_subFolders { { "csv", "csv" }, { "pdf", "pdf" } };
#endif
FileSystem::FileSystem(QObject *parent)
: QObject(parent)
{
}
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
bool FileSystem::createDocumentsFolder()
{
QFileInfo fi(s_rootPath);
if (fi.exists()) {
if (fi.isDir())
return true;
else {
qCritical() << "Documents exists but is a File...";
return false;
}
}
qDebug() << "Creating Documents folder: " << s_rootPath;
QDir dir(fi.absolutePath());
if (!dir.mkdir(fi.fileName())) {
qCritical() << "Couldn't create Documents folder...";
return;
}
}
bool FileSystem::createQropFolder()
{
QFileInfo fi(QString("%1/%2").arg(s_rootPath, APP_NAME));
if (!fi.exists()) {
QDir dir(s_rootPath);
if (!dir.mkdir(APP_NAME)) {
qCritical() << "Couldn't create Qrop folder: " << fi.absoluteFilePath();
return false;
}
}
s_rootPath = fi.absoluteFilePath();
return true;
}
void FileSystem::createMobileRootFilesDirectories()
{
#if defined(Q_OS_ANDROID)
QtAndroid::PermissionResultMap res =
QtAndroid::requestPermissionsSync({ "android.permission.WRITE_EXTERNAL_STORAGE" });
if (res["android.permission.WRITE_EXTERNAL_STORAGE"] != QtAndroid::PermissionResult::Granted) {
qCritical() << "don't have WRITE_EXTERNAL_STORAGE permission...";
return;
}
#endif
if (createDocumentsFolder() && createQropFolder()) {
// create all subFolders
QDir dir(s_rootPath);
for (const auto &folder : s_subFolders.values())
dir.mkdir(folder);
}
}
QStringList FileSystem::getAvailableDataBasesNames()
{
QStringList dbNames;
QDir dir(s_rootPath);
for (const QFileInfo &fi :
dir.entryInfoList(QStringList("*.sqlite"), QDir::Filter::Files, QDir::SortFlag::Name))
dbNames << fi.completeBaseName();
return dbNames;
}
QStringList FileSystem::getAvailableCsvFileNames()
{
QStringList csvNames;
QDir dir(csvPath());
for (const QFileInfo &fi :
dir.entryInfoList(QStringList("*.csv"), QDir::Filter::Files, QDir::SortFlag::Name))
csvNames << fi.completeBaseName();
return csvNames;
}
#endif // #if defined(Q_OS_ANDROID) || defined (Q_OS_IOS)
#pragma once
#include "core_global.h"
#include <QObject>
#include <QMap>
class CORESHARED_EXPORT FileSystem : public QObject
{
Q_OBJECT
Q_PROPERTY(QString rootPath READ rootPath CONSTANT FINAL)
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
Q_PROPERTY(QString csvPath READ csvPath CONSTANT FINAL)
Q_PROPERTY(QString pdfPath READ pdfPath CONSTANT FINAL)
#endif
public:
explicit FileSystem(QObject *parent = nullptr);
inline static QString rootPath() { return s_rootPath; }
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
inline static QString csvPath()
{
return QString("%1/%2").arg(s_rootPath).arg(s_subFolders.value("csv"));
}
inline static QString pdfPath()
{
return QString("%1/%2").arg(s_rootPath).arg(s_subFolders.value("pdf"));
}
Q_INVOKABLE static QStringList getAvailableDataBasesNames();
Q_INVOKABLE static QStringList getAvailableCsvFileNames();
static void createMobileRootFilesDirectories();
#endif
private:
static QString s_rootPath;
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
static const QMap<QString, QString> s_subFolders;
static bool createDocumentsFolder();
static bool createQropFolder();
#endif
};
//#if defined(Q_OS_ANDROID) || defined (Q_OS_IOS)
//#endif
......@@ -26,16 +26,16 @@ SeedListModel::SeedListModel(QObject *parent, const QString &tableName)
setSortColumn("crop_id");
}
void SeedListModel::csvExport(const QUrl &path)
QString SeedListModel::csvExport(const QUrl &path)
{
QFile f(path.toLocalFile());
if (f.exists())
f.remove();
if (!f.open(QIODevice::ReadWrite)) {
qDebug() << "Cannot open file";
return;
if (!f.open(QIODevice::WriteOnly | QIODevice::Text)) {
qDebug() << "[MB_TRACE][SeedListModel::csvExport] can't open file: " << path.toString();
return tr("Can't write file: %1").arg(path.toString());
}
QTextStream ts(&f);
......@@ -62,6 +62,8 @@ void SeedListModel::csvExport(const QUrl &path)
}
ts << "\n";
}
return QString();
}
bool SeedListModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
......
......@@ -27,7 +27,7 @@ class CORESHARED_EXPORT SeedListModel : public SortFilterProxyModel
Q_OBJECT
public:
explicit SeedListModel(QObject *parent = nullptr, const QString &tableName = "seed_list_view");
Q_INVOKABLE void csvExport(const QUrl &path);
Q_INVOKABLE QString csvExport(const QUrl &path);
protected:
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
......
......@@ -26,16 +26,16 @@ TransplantListModel::TransplantListModel(QObject *parent, const QString &tableNa
setSortColumn("crop_id");
}
void TransplantListModel::csvExport(const QUrl &path)
QString TransplantListModel::csvExport(const QUrl &path)
{
QFile f(path.toLocalFile());
if (f.exists())
f.remove();
if (!f.open(QIODevice::ReadWrite)) {
qDebug() << "Cannot open file";
return;
if (!f.open(QIODevice::WriteOnly | QIODevice::Text)) {
qDebug() << "[MB_TRACE][TransplantListModel::csvExport] can't open file: " << path.toString();
return tr("Can't write file: %1").arg(path.toString());
}
QTextStream ts(&f);
......@@ -64,6 +64,7 @@ void TransplantListModel::csvExport(const QUrl &path)
}
ts << "\n";
}
return QString();
}
bool TransplantListModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
......
......@@ -29,7 +29,7 @@ class CORESHARED_EXPORT TransplantListModel : public SortFilterProxyModel
public:
explicit TransplantListModel(QObject *parent = nullptr,
const QString &tableName = "transplant_list_view");
Q_INVOKABLE void csvExport(const QUrl &path);
Q_INVOKABLE QString csvExport(const QUrl &path);
protected:
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
......
<?xml version="1.0"?>
<manifest package="io.qrop" xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="0.1" android:versionCode="1" android:installLocation="auto">
<application android:hardwareAccelerated="true" android:name="org.qtproject.qt5.android.bindings.QtApplication" android:label="Qrop" android:icon="@drawable/icon">
<activity android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation" android:name="org.qtproject.qt5.android.bindings.QtActivity" android:label="Qrop" android:screenOrientation="unspecified" android:launchMode="singleTop">
<?xml version='1.0' encoding='utf-8'?>
<manifest package="io.qrop" xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="1.0" android:versionCode="1" android:installLocation="auto">
<!-- The following comment will be replaced upon deployment with default permissions based on the dependencies of the application.
Remove the comment if you do not require these default permissions. -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!-- 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. -->
<supports-screens android:largeScreens="true" android:normalScreens="true" android:anyDensity="true" android:smallScreens="true"/>
<application android:hardwareAccelerated="true" android:name="org.qtproject.qt5.android.bindings.QtApplication" android:label="qrop" android:extractNativeLibs="true" android:icon="@drawable/icon">
<activity android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation|mcc|mnc|density"
android:name="org.qtproject.qt5.android.bindings.QtActivity"
android:label="qrop"
android:screenOrientation="unspecified"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
......@@ -11,30 +26,36 @@
<!-- meta-data android:name="android.app.arguments" android:value="arg1 arg2 arg3"/ -->
<!-- Application arguments -->
<meta-data android:name="android.app.lib_name" android:value="-- %%INSERT_APP_LIB_NAME%% --"/>
<meta-data android:name="android.app.lib_name" android:value="qrop"/>
<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"/>
<meta-data android:name="android.app.bundle_local_qt_libs" android:value="1"/>
<!-- 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.use_local_qt_libs" android:value="1"/>
<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_jars" android:value="-- %%INSERT_LOCAL_JARS%% --"/>
<meta-data android:name="android.app.static_init_classes" android:value="-- %%INSERT_INIT_CLASSES%% --"/>
<meta-data android:name="android.app.load_local_libs_resource_id" android:resource="@array/load_local_libs"/>
<meta-data android:name="android.app.load_local_jars" android:value="jar/QtAndroid.jar:jar/QtAndroidExtras.jar:jar/QtAndroidBearer.jar"/>
<meta-data android:name="android.app.static_init_classes" android:value=""/>
<!-- Used to specify custom system library path to run with local system libs -->
<!-- <meta-data android:name="android.app.system_libs_prefix" android:value="/system/lib/"/> -->
<!-- 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"/>
<meta-data android:value="@string/unsupported_android_version" android:name="android.app.unsupported_android_version"/>
<!-- Messages maps -->
<!-- Splash screen -->
<!-- Orientation-specific (portrait/landscape) data is checked first. If not available for current orientation,
then android.app.splash_screen_drawable. For best results, use together with splash_screen_sticky and
use hideSplashScreen() with a fade-out animation from Qt Android Extras to hide the splash screen when you
are done populating your window with content. -->
<!-- meta-data android:name="android.app.splash_screen_drawable_portrait" android:resource="@drawable/logo_portrait" / -->
<!-- meta-data android:name="android.app.splash_screen_drawable_landscape" android:resource="@drawable/logo_landscape" / -->
<!-- meta-data android:name="android.app.splash_screen_drawable" android:resource="@drawable/logo"/ -->
<!-- meta-data android:name="android.app.splash_screen_sticky" android:value="true"/ -->
<!-- Splash screen -->
......@@ -53,11 +74,12 @@
<!-- extract android style -->
<!-- available android:values :
* default - In most cases this will be the same as "full", but it can also be something else if needed, e.g., for compatibility reasons
* full - useful QWidget & Quick Controls 1 apps
* minimal - useful for Quick Controls 2 apps, it is much faster than "full"
* none - useful for apps that don't use any of the above Qt modules
-->
<meta-data android:name="android.app.extract_android_style" android:value="full"/>
<meta-data android:name="android.app.extract_android_style" android:value="default"/>
<!-- extract android style -->
</activity>
......@@ -65,15 +87,4 @@
</application>
<uses-sdk android:minSdkVersion="16" android:targetSdkVersion="16"/>
<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>
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.2.3'
classpath 'com.android.tools.build:gradle:3.6.0'
}
}
allprojects {
repositories {
jcenter()
}
repositories {
google()
jcenter()
}
apply plugin: 'com.android.application'
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
}
android {
......@@ -36,7 +36,7 @@ android {
compileSdkVersion androidCompileSdkVersion.toInteger()
buildToolsVersion androidBuildToolsVersion
buildToolsVersion '28.0.3'
sourceSets {
main {
......@@ -54,4 +54,15 @@ android {
lintOptions {
abortOnError false
}
// Do not compress Qt binary resources file
aaptOptions {
noCompress 'rcc'
}
defaultConfig {
resConfig "en"
minSdkVersion = qtMinSdkVersion
targetSdkVersion = qtTargetSdkVersion
}
}
#Mon Feb 20 10:43:22 EST 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-3.4-bin.zip
......@@ -33,11 +33,11 @@ DEFAULT_JVM_OPTS="-Xmx1024m -Dfile.encoding=UTF-8"
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
warn () {
echo "$*"
}
die ( ) {
die () {
echo
echo "$*"
echo
......@@ -155,7 +155,7 @@ if $cygwin ; then
fi
# Escape application args
save ( ) {
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
......
<?xml version='1.0' encoding='utf-8'?>
<resources>
<array name="qt_sources">
<item>https://download.qt.io/ministro/android/qt5/qt-5.9</item>
<item>https://download.qt.io/ministro/android/qt5/qt-5.14</item>
</array>
<!-- The following is handled automatically by the deployment tool. It should
......@@ -11,15 +11,12 @@
<!-- %%INSERT_EXTRA_LIBS%% -->