From 7611d56fd665ced328d1693a5aad89826b011975 Mon Sep 17 00:00:00 2001 From: Martin Straka <martin.straka@nic.cz> Date: Thu, 16 Jan 2025 10:49:11 +0100 Subject: [PATCH 1/9] Show change log dialog after update on new version. --- CMakeLists.txt | 1 + qml/components/ChangeLogBox.qml | 98 +++++++++++++++++++++++++++++++++ qml/main.qml | 8 +++ qml/pages/PageAboutApp.qml | 6 ++ res/qml.qrc | 1 + src/main.cpp | 1 + src/setwrapper.cpp | 69 +++++++++++++++++++++++ src/setwrapper.h | 8 +++ 8 files changed, 192 insertions(+) create mode 100644 qml/components/ChangeLogBox.qml diff --git a/CMakeLists.txt b/CMakeLists.txt index 608346e75..c78116890 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -238,6 +238,7 @@ qt_add_qml_module(mobile-datovka res/../qml/components/FilterBar.qml res/../qml/components/GovFormList.qml res/../qml/components/GovServiceList.qml + res/../qml/components/ChangeLogBox.qml res/../qml/components/MessageBox.qml res/../qml/components/MessageList.qml res/../qml/components/PageHeader.qml diff --git a/qml/components/ChangeLogBox.qml b/qml/components/ChangeLogBox.qml new file mode 100644 index 000000000..dc8dd84b2 --- /dev/null +++ b/qml/components/ChangeLogBox.qml @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2014-2025 CZ.NIC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * OpenSSL library under certain conditions as described in each + * individual source file, and distribute linked combinations including + * the two. + */ + +import QtQuick 2.7 +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.2 + +/* + * Provides a simple change log dialog. + */ +Popup { + id: root + anchors.centerIn: parent + modal: true + + property int preferredMinWidth: 280 + property string logContent: qsTr(" + Added a preferences editor. + + Using dedicated redirection URL to access donation page. + + Custom images can be assigned to individual data boxes as logos in the data-box list. + + Fixed white border around launcher icon in iOS. + + On application start-up when INI configuration is missing the application searches for a copy before creating a blank INI file. + ") + + /* Public interface. */ + function showChangeLog() { + changeLogText.text = logContent + root.open() + } + + background: Rectangle { + color: datovkaPalette.base + border.color: datovkaPalette.text + radius: defaultMargin + } + + ColumnLayout { + spacing: defaultMargin * 2 + AccessibleText { + font.bold: true + Layout.alignment: Qt.AlignCenter + color: datovkaPalette.highlightedText + text: qsTr("Version") + ": " + Qt.application.version + } + AccessibleText { + Layout.preferredWidth: root.preferredMinWidth + horizontalAlignment : Text.Center + wrapMode: Text.Wrap + font.bold: true + text: qsTr("What is news?") + } + Flickable { + clip: true + Layout.preferredWidth: root.preferredMinWidth + implicitHeight: preferredMinWidth + contentHeight: flickContent.implicitHeight + Pane { + id: flickContent + anchors.fill: parent + AccessibleText { + id: changeLogText + width: parent.width + wrapMode: Text.WordWrap + } + } + ScrollIndicator.vertical: ScrollIndicator {} + } + AccessibleButton { + Layout.alignment: Qt.AlignCenter + text: "OK" + onClicked: root.close() + } + } +} diff --git a/qml/main.qml b/qml/main.qml index 7a3c4b7f9..3abb7b1ec 100644 --- a/qml/main.qml +++ b/qml/main.qml @@ -262,6 +262,10 @@ ApplicationWindow { id: okMsgBox } + ChangeLogBox { + id: changeLogBox + } + DrawerMenuDatovka { id: drawerMenuDatovka } @@ -480,6 +484,10 @@ ApplicationWindow { target: settings function onRunOnAppStartUpSig() { console.log("Running actions after application start-up.") + var isNewVersion = settings.isNewVersion() + if (isNewVersion) { + changeLogBox.showChangeLog() + } var areNews = messages.checkNewDatabasesFormat() if (!areNews) { pageView.push(pageConvertDatabase, { diff --git a/qml/pages/PageAboutApp.qml b/qml/pages/PageAboutApp.qml index 7d661a8c9..bc0709a58 100644 --- a/qml/pages/PageAboutApp.qml +++ b/qml/pages/PageAboutApp.qml @@ -88,6 +88,12 @@ Page { + "<br/>" + "<a href=\"%1\">%2</a>".arg("https://datovka.nic.cz/redirect/donation-mobile.html").arg(qsTr("Make a donation.")) } + AccessibleButton { + anchors.horizontalCenter: parent.horizontalCenter + text: qsTr("Show ChangeLog") + accessibleName: qsTr("What is news?") + onClicked: changeLogBox.showChangeLog() + } AccessibleTextInfo { horizontalAlignment: Text.AlignHCenter textFormat: TextEdit.RichText diff --git a/res/qml.qrc b/res/qml.qrc index c44523fa6..cae41eb70 100644 --- a/res/qml.qrc +++ b/res/qml.qrc @@ -24,6 +24,7 @@ <file>../qml/components/FilterBar.qml</file> <file>../qml/components/GovFormList.qml</file> <file>../qml/components/GovServiceList.qml</file> + <file>../qml/components/ChangeLogBox.qml</file> <file>../qml/components/MessageBox.qml</file> <file>../qml/components/MessageList.qml</file> <file>../qml/components/PageHeader.qml</file> diff --git a/src/main.cpp b/src/main.cpp index 44d9d51a5..a4dbab879 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -194,6 +194,7 @@ const struct QmlTypeEntry qmlComponents[] = { { "FilterBar", 1, 0 }, { "GovFormList", 1, 0 }, { "GovServiceList", 1, 0 }, + { "ChangeLogBox", 1, 0 }, { "MessageBox", 1, 0 }, { "MessageList", 1, 0 }, { "PageHeader", 1, 0 }, diff --git a/src/setwrapper.cpp b/src/setwrapper.cpp index 3759a6803..2f54d5ec5 100644 --- a/src/setwrapper.cpp +++ b/src/setwrapper.cpp @@ -22,6 +22,7 @@ */ #include <QtGlobal> /* QT_VERSION, qVersion() */ +#include <QVersionNumber> #if defined (Q_OS_ANDROID) #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) @@ -782,3 +783,71 @@ bool GlobalSettingsQmlWrapper::useIosDocumentPicker(void) return false; } } + +/*! + * @brief Compare current app version and pref db version. + * + * @param[in] cVersion App current version string. + * @param[in] dbVersion Pref database version string. + * @return True if current app version is higher. + */ +static +bool compareVersionStrings(const QString &cVersion, const QString &dbVersion) +{ + QVersionNumber v1 = QVersionNumber::fromString(cVersion); + if (v1.isNull()) { + logErrorNL( + "Version string '%s' does not match required format.", + cVersion.toUtf8().constData()); + return false; + } + QVersionNumber v2 = QVersionNumber::fromString(dbVersion); + if (v2.isNull()) { + logErrorNL( + "Version string '%s' does not match required format.", + dbVersion.toUtf8().constData()); + return false; + } + + int cmp = QVersionNumber::compare(v1, v2); + if (cmp < 0) { + return false; + } else if (cmp == 0) { + return false; + } else { + return true; + } +} + +bool GlobalSettingsQmlWrapper::isNewVersion(void) +{ + bool isNew = true; + QString dbVersion; + + if (GlobInstcs::prefsPtr != Q_NULLPTR) { + GlobInstcs::prefsPtr->strVal("app.version", dbVersion); + } else { + Q_ASSERT(0); + } + if (Q_UNLIKELY(dbVersion.isEmpty())) { + if (GlobInstcs::prefsPtr != Q_NULLPTR) { + GlobInstcs::prefsPtr->setStrVal("app.version", VERSION); + return isNew; + } else { + Q_ASSERT(0); + } + } + + isNew = compareVersionStrings(VERSION, dbVersion); + + if (isNew) { + if (GlobInstcs::prefsPtr != Q_NULLPTR) { + GlobInstcs::prefsPtr->setStrVal("app.version", VERSION); + return isNew; + } else { + Q_ASSERT(0); + } + } + + return isNew; +} diff --git a/src/setwrapper.h b/src/setwrapper.h index 718863aa0..8d3adf1e7 100644 --- a/src/setwrapper.h +++ b/src/setwrapper.h @@ -491,6 +491,14 @@ public: Q_INVOKABLE static bool useIosDocumentPicker(void); + /*! + * @brief Check if new version after first startup. + * + * @return True if it is new version after first startup. + */ + Q_INVOKABLE static + bool isNewVersion(void); + signals: /*! * @brief Send PIN verification result to QML. -- GitLab From e3031004c58795a394076968abbce98d4cd901a2 Mon Sep 17 00:00:00 2001 From: Martin Straka <martin.straka@nic.cz> Date: Thu, 16 Jan 2025 14:14:55 +0100 Subject: [PATCH 2/9] Added textarea border. --- qml/components/ChangeLogBox.qml | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/qml/components/ChangeLogBox.qml b/qml/components/ChangeLogBox.qml index dc8dd84b2..a3dca80ea 100644 --- a/qml/components/ChangeLogBox.qml +++ b/qml/components/ChangeLogBox.qml @@ -34,8 +34,7 @@ Popup { modal: true property int preferredMinWidth: 280 - property string logContent: qsTr(" - Added a preferences editor. + property string logContent: qsTr("Added a preferences editor. Using dedicated redirection URL to access donation page. @@ -43,8 +42,7 @@ Popup { Fixed white border around launcher icon in iOS. - On application start-up when INI configuration is missing the application searches for a copy before creating a blank INI file. - ") + On application start-up when INI configuration is missing the application searches for a copy before creating a blank INI file.") /* Public interface. */ function showChangeLog() { @@ -73,21 +71,28 @@ Popup { font.bold: true text: qsTr("What is news?") } - Flickable { - clip: true + Rectangle { Layout.preferredWidth: root.preferredMinWidth implicitHeight: preferredMinWidth - contentHeight: flickContent.implicitHeight - Pane { - id: flickContent + color: "transparent" + border.color: datovkaPalette.line + border.width: 1 + Flickable { anchors.fill: parent - AccessibleText { - id: changeLogText - width: parent.width - wrapMode: Text.WordWrap + anchors.margins: 1 + contentHeight: flickContent.implicitHeight + clip: true + Pane { + id: flickContent + anchors.fill: parent + AccessibleText { + id: changeLogText + width: parent.width + wrapMode: Text.WordWrap + } } + ScrollIndicator.vertical: ScrollIndicator {} } - ScrollIndicator.vertical: ScrollIndicator {} } AccessibleButton { Layout.alignment: Qt.AlignCenter -- GitLab From 536e4f26e85ce69b35dfd9da4a26bd2f0fba50fc Mon Sep 17 00:00:00 2001 From: Martin Straka <martin.straka@nic.cz> Date: Thu, 16 Jan 2025 15:29:38 +0100 Subject: [PATCH 3/9] Moved changelog content into separate file/class. --- CMakeLists.txt | 1 + mobile-datovka.pro | 2 ++ qml/components/ChangeLogBox.qml | 12 ++-------- src/changelog.cpp | 42 +++++++++++++++++++++++++++++++++ src/changelog.h | 40 +++++++++++++++++++++++++++++++ src/setwrapper.cpp | 6 +++++ src/setwrapper.h | 8 +++++++ 7 files changed, 101 insertions(+), 10 deletions(-) create mode 100644 src/changelog.cpp create mode 100644 src/changelog.h diff --git a/CMakeLists.txt b/CMakeLists.txt index c78116890..8a517c523 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -109,6 +109,7 @@ qt_add_executable(mobile-datovka src/gov_services/models/gov_form_list_model.cpp src/gov_services/models/gov_form_list_model.h src/gov_services/models/gov_service_list_model.cpp src/gov_services/models/gov_service_list_model.h src/gov_wrapper.cpp src/gov_wrapper.h + src/changelog.cpp src/changelog.h src/initialisation.cpp src/initialisation.h src/io/filesystem.cpp src/io/filesystem.h src/isds/conversion/isds_conversion.cpp src/isds/conversion/isds_conversion.h diff --git a/mobile-datovka.pro b/mobile-datovka.pro index b6c4c4a72..15415d971 100644 --- a/mobile-datovka.pro +++ b/mobile-datovka.pro @@ -242,6 +242,7 @@ SOURCES += \ src/gov_services/models/gov_form_list_model.cpp \ src/gov_services/models/gov_service_list_model.cpp \ src/gov_wrapper.cpp \ + src/changelog.cpp \ src/initialisation.cpp \ src/io/filesystem.cpp \ src/isds/conversion/isds_conversion.cpp \ @@ -426,6 +427,7 @@ HEADERS += \ src/gov_services/models/gov_form_list_model.h \ src/gov_services/models/gov_service_list_model.h \ src/gov_wrapper.h \ + src/changelog.h \ src/initialisation.h \ src/io/filesystem.h \ src/isds/conversion/isds_conversion.h \ diff --git a/qml/components/ChangeLogBox.qml b/qml/components/ChangeLogBox.qml index a3dca80ea..13b0b172b 100644 --- a/qml/components/ChangeLogBox.qml +++ b/qml/components/ChangeLogBox.qml @@ -34,19 +34,10 @@ Popup { modal: true property int preferredMinWidth: 280 - property string logContent: qsTr("Added a preferences editor. - - Using dedicated redirection URL to access donation page. - - Custom images can be assigned to individual data boxes as logos in the data-box list. - - Fixed white border around launcher icon in iOS. - - On application start-up when INI configuration is missing the application searches for a copy before creating a blank INI file.") /* Public interface. */ function showChangeLog() { - changeLogText.text = logContent + changeLogText.text = settings.loadChangeLogText() root.open() } @@ -89,6 +80,7 @@ Popup { id: changeLogText width: parent.width wrapMode: Text.WordWrap + textFormat: TextEdit.RichText } } ScrollIndicator.vertical: ScrollIndicator {} diff --git a/src/changelog.cpp b/src/changelog.cpp new file mode 100644 index 000000000..77bb70944 --- /dev/null +++ b/src/changelog.cpp @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2014-2025 CZ.NIC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * OpenSSL library under certain conditions as described in each + * individual source file, and distribute linked combinations including + * the two. + */ + +#include "src/changelog.h" + +#define textLineNL(text) \ + (QLatin1String("<style>li{margin-left:-30px;}</style><ul><li>") + (text) + QLatin1String("</li></ul>")) + +QString ChangeLog::changeLogContent(void) +{ + QString content; + + content.append(textLineNL(tr("Added a preferences editor."))); + content.append(textLineNL(tr("Custom images can be assigned to individual data boxes as logos in the data-box list."))); + content.append(textLineNL(tr("Using dedicated redirection URL to access donation page."))); + content.append(textLineNL(tr("INI configuration file is written in three stages. A copy is written then the original is removed and finally the copy replaces the original."))); + content.append(textLineNL(tr("On application start-up when INI configuration is missing the application searches for a copy before creating a blank INI file."))); + content.append(textLineNL(tr("Fixed white border around launcher icon in iOS."))); + content.append(textLineNL(tr("Fixed logging of debug information when building with cmake."))); + + return content; +} diff --git a/src/changelog.h b/src/changelog.h new file mode 100644 index 000000000..6934d3127 --- /dev/null +++ b/src/changelog.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2014-2025 CZ.NIC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * OpenSSL library under certain conditions as described in each + * individual source file, and distribute linked combinations including + * the two. + */ + +#pragma once + +#include <QObject> +#include <QString> + +class ChangeLog : public QObject { + Q_OBJECT + +public: + /*! + * @brief Changelog text. + * + * @return Changelog text. + */ + static + QString changeLogContent(void); +}; diff --git a/src/setwrapper.cpp b/src/setwrapper.cpp index 2f54d5ec5..a32978c75 100644 --- a/src/setwrapper.cpp +++ b/src/setwrapper.cpp @@ -45,6 +45,7 @@ #include "src/datovka_shared/utility/date_time.h" #include "src/font/font.h" #include "src/global.h" +#include "src/changelog.h" #include "src/io/filesystem.h" #include "src/settings/accounts.h" #include "src/settings/ini_preferences.h" @@ -851,3 +852,8 @@ bool GlobalSettingsQmlWrapper::isNewVersion(void) return isNew; } + +QString GlobalSettingsQmlWrapper::loadChangeLogText(void) +{ + return ChangeLog::changeLogContent(); +} diff --git a/src/setwrapper.h b/src/setwrapper.h index 8d3adf1e7..9ac28a408 100644 --- a/src/setwrapper.h +++ b/src/setwrapper.h @@ -499,6 +499,14 @@ public: Q_INVOKABLE static bool isNewVersion(void); + /*! + * @brief Load changelog content. + * + * @return Changelog text. + */ + Q_INVOKABLE static + QString loadChangeLogText(void); + signals: /*! * @brief Send PIN verification result to QML. -- GitLab From cbf1cc02630868cb4b30bfaae34b2101ce225a5e Mon Sep 17 00:00:00 2001 From: Karel Slany <karel.slany@nic.cz> Date: Tue, 18 Feb 2025 13:47:36 +0100 Subject: [PATCH 4/9] Moved code shared with the desktop application. --- mobile-datovka.pro | 5 +- src/{changelog.cpp => app_version_info.cpp} | 4 +- src/datovka_shared/app_version_info.cpp | 168 ++++++++++++++++++ .../app_version_info.h} | 26 ++- src/setwrapper.cpp | 54 +----- 5 files changed, 202 insertions(+), 55 deletions(-) rename src/{changelog.cpp => app_version_info.cpp} (95%) create mode 100644 src/datovka_shared/app_version_info.cpp rename src/{changelog.h => datovka_shared/app_version_info.h} (57%) diff --git a/mobile-datovka.pro b/mobile-datovka.pro index 15415d971..734d849a8 100644 --- a/mobile-datovka.pro +++ b/mobile-datovka.pro @@ -164,9 +164,11 @@ TRANSLATIONS_FILES += \ res/locale/datovka_uk.qm SOURCES += \ + src/app_version_info.cpp \ src/auxiliaries/email_helper.cpp \ src/auxiliaries/ios_helper.cpp \ src/backup_zip.cpp \ + src/datovka_shared/app_version_info.cpp \ src/datovka_shared/compat_qt/random.cpp \ src/datovka_shared/graphics/colour.cpp \ src/datovka_shared/gov_services/helper.cpp \ @@ -242,7 +244,6 @@ SOURCES += \ src/gov_services/models/gov_form_list_model.cpp \ src/gov_services/models/gov_service_list_model.cpp \ src/gov_wrapper.cpp \ - src/changelog.cpp \ src/initialisation.cpp \ src/io/filesystem.cpp \ src/isds/conversion/isds_conversion.cpp \ @@ -344,6 +345,7 @@ HEADERS += \ src/auxiliaries/email_helper.h \ src/auxiliaries/ios_helper.h \ src/backup_zip.h \ + src/datovka_shared/app_version_info.h \ src/datovka_shared/compat/compiler.h \ src/datovka_shared/compat_qt/misc.h \ src/datovka_shared/compat_qt/random.h \ @@ -427,7 +429,6 @@ HEADERS += \ src/gov_services/models/gov_form_list_model.h \ src/gov_services/models/gov_service_list_model.h \ src/gov_wrapper.h \ - src/changelog.h \ src/initialisation.h \ src/io/filesystem.h \ src/isds/conversion/isds_conversion.h \ diff --git a/src/changelog.cpp b/src/app_version_info.cpp similarity index 95% rename from src/changelog.cpp rename to src/app_version_info.cpp index 77bb70944..b51dee8e2 100644 --- a/src/changelog.cpp +++ b/src/app_version_info.cpp @@ -21,12 +21,12 @@ * the two. */ -#include "src/changelog.h" +#include "src/datovka_shared/app_version_info.h" #define textLineNL(text) \ (QLatin1String("<style>li{margin-left:-30px;}</style><ul><li>") + (text) + QLatin1String("</li></ul>")) -QString ChangeLog::changeLogContent(void) +QString AppVersionInfo::releaseNewsText(void) { QString content; diff --git a/src/datovka_shared/app_version_info.cpp b/src/datovka_shared/app_version_info.cpp new file mode 100644 index 000000000..da5ad0a2c --- /dev/null +++ b/src/datovka_shared/app_version_info.cpp @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2014-2025 CZ.NIC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * OpenSSL library under certain conditions as described in each + * individual source file, and distribute linked combinations including + * the two. + */ + +#include <QRegularExpression> +#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) +# include <QVersionNumber> +#else /* < Qt-5.6 */ +# include <QVector> +#endif /* >= Qt-5.6 */ + +#include "src/datovka_shared/app_version_info.h" +#include "src/datovka_shared/log/log.h" + +/*! + * @brief Strip unwanted data from version string. + * + * @param[in,out] vStr Version string. Must contain a substring in format + * '[0-9]+.[0-9]+.[0-9]+' . + * @return True if such substring is found. + */ +static +bool stripVersionString(QString &vStr) +{ + vStr.remove(QRegularExpression(QLatin1String("^[^0-9.]*"))); + vStr.remove(QRegularExpression(QLatin1String("[^0-9.].*$"))); + vStr.remove(QRegularExpression(QLatin1String("^[.]*"))); + vStr.remove(QRegularExpression(QLatin1String("[.]*$"))); + + QRegularExpression verExpr( + QLatin1String("[0-9][0-9]*\\.[0-9][0-9]*\\.[0-9][0-9]*")); + QRegularExpressionMatch match(verExpr.match(vStr)); + + return match.hasMatch() && (match.capturedLength() == vStr.length()); +} + +#if (QT_VERSION < QT_VERSION_CHECK(5, 6, 0)) +# warning "Compiling against version < Qt-5.6 which does not have QVersionNumber." + +/*! + * @brief Replacement for QVersionNumber which is not present in Qt before 5.6. + */ +class QVersionNumber { +public: + QVersionNumber(void) : m_major(-1), m_micro(-1), m_minor(-1) + { + } + + bool isNull(void) const + { + return (m_major < 0) || (m_micro < 0) || (m_minor < 0); + } + + static + int compare(const QVersionNumber &v1, const QVersionNumber &v2) + { + if (v1.m_major < v2.m_major) { + return -1; + } else if (v1.m_major > v2.m_major) { + return 1; + } else if (v1.m_micro < v2.m_micro) { + return -1; + } else if (v1.m_micro > v2.m_micro) { + return 1; + } else if (v1.m_minor < v2.m_minor) { + return -1; + } else if (v1.m_minor > v2.m_minor) { + return 1; + } else { + return 0; + } + } + + static + QVersionNumber fromString(const QString &str) + { + QVersionNumber verNum; + + const int elemNum = 3; + QStringList elemList(str.split(QChar('.'))); + if (elemList.size() != elemNum) { + return verNum; + } + + QVector<int> elemVect(3, -1); + for (int i = 0; i < elemNum; ++i) { + bool ok = false; + elemVect[i] = elemList[i].toInt(&ok); + if (!ok) { + elemVect[i] = -1; + } + } + + verNum.m_major = elemVect[0]; + verNum.m_micro = elemVect[1]; + verNum.m_minor = elemVect[2]; + return verNum; + } + +private: + int m_major; + int m_micro; + int m_minor; +}; +#endif + +int AppVersionInfo::compareVersionStrings(const QString &vStr1, const QString &vStr2) +{ + QString vs1(vStr1), vs2(vStr2); + + if (Q_UNLIKELY(!stripVersionString(vs1))) { + logErrorNL("Cannot strip version string '%s'.", + vs1.toUtf8().constData()); + return -2; + } + if (Q_UNLIKELY(!stripVersionString(vs2))) { + logErrorNL("Cannot strip version string '%s'.", + vs2.toUtf8().constData()); + return 2; + } + + QVersionNumber v1 = QVersionNumber::fromString(vs1); + if (Q_UNLIKELY(v1.isNull())) { + logErrorNL( + "Version string '%s' doesn't match required format.", + vs1.toUtf8().constData()); + return -2; + } + QVersionNumber v2 = QVersionNumber::fromString(vs2); + if (Q_UNLIKELY(v2.isNull())) { + logErrorNL( + "Version string '%s' doesn't match required format.", + vs2.toUtf8().constData()); + return 2; + } + + /* + * Documentation of QVersionNumber::compare() only mentions negative + * or positive values. It doesn't mention -1 or 1. + */ + int cmp = QVersionNumber::compare(v1, v2); + if (cmp < 0) { + return -1; + } else if (cmp == 0) { + return 0; + } else { + return 1; + } +} diff --git a/src/changelog.h b/src/datovka_shared/app_version_info.h similarity index 57% rename from src/changelog.h rename to src/datovka_shared/app_version_info.h index 6934d3127..ec832ee14 100644 --- a/src/changelog.h +++ b/src/datovka_shared/app_version_info.h @@ -23,18 +23,32 @@ #pragma once -#include <QObject> +#include <QCoreApplication> /* Q_DECLARE_TR_FUNCTIONS */ #include <QString> -class ChangeLog : public QObject { - Q_OBJECT +class AppVersionInfo { + Q_DECLARE_TR_FUNCTIONS(AppVersionInfo) public: /*! - * @brief Changelog text. + * @brief Compare newest available version and application version. * - * @return Changelog text. + * @param[in] vStr1 Version string. + * @param[in] vStr2 Version string. + * @retval -2 if vStr1 doesn't contain a suitable version string + * @retval -1 if vStr1 is less than vStr2 + * @retval 0 if vStr1 is equal to vStr2 + * @retval 1 if vStr1 is greater than vStr2 + * @retval 2 if vStr2 doesn't contain a suitable version string */ static - QString changeLogContent(void); + int compareVersionStrings(const QString &vStr1, const QString &vStr2); + + /*! + * @brief Release news. + * + * @return Release news text. + */ + static + QString releaseNewsText(void); }; diff --git a/src/setwrapper.cpp b/src/setwrapper.cpp index a32978c75..6eb66cb4d 100644 --- a/src/setwrapper.cpp +++ b/src/setwrapper.cpp @@ -22,20 +22,20 @@ */ #include <QtGlobal> /* QT_VERSION, qVersion() */ -#include <QVersionNumber> #if defined (Q_OS_ANDROID) -#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) - #include "android_qt6/src/android_io.h" -#else - #include "android/src/android_io.h" -#endif +# if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) +# include "android_qt6/src/android_io.h" +# else +# include "android/src/android_io.h" +# endif #endif #if defined (Q_OS_IOS) -#include "ios/src/url_opener.h" +# include "ios/src/url_opener.h" #endif +#include "src/datovka_shared/app_version_info.h" #include "src/datovka_shared/compat_qt/random.h" #include "src/datovka_shared/localisation/localisation.h" #include "src/datovka_shared/log/log.h" @@ -45,7 +45,6 @@ #include "src/datovka_shared/utility/date_time.h" #include "src/font/font.h" #include "src/global.h" -#include "src/changelog.h" #include "src/io/filesystem.h" #include "src/settings/accounts.h" #include "src/settings/ini_preferences.h" @@ -785,41 +784,6 @@ bool GlobalSettingsQmlWrapper::useIosDocumentPicker(void) } } -/*! - * @brief Compare current app version and pref db version. - * - * @param[in] cVersion App current version string. - * @param[in] dbVersion Pref database version string. - * @return True if current app version is higher. - */ -static -bool compareVersionStrings(const QString &cVersion, const QString &dbVersion) -{ - QVersionNumber v1 = QVersionNumber::fromString(cVersion); - if (v1.isNull()) { - logErrorNL( - "Version string '%s' does not match required format.", - cVersion.toUtf8().constData()); - return false; - } - QVersionNumber v2 = QVersionNumber::fromString(dbVersion); - if (v2.isNull()) { - logErrorNL( - "Version string '%s' does not match required format.", - dbVersion.toUtf8().constData()); - return false; - } - - int cmp = QVersionNumber::compare(v1, v2); - if (cmp < 0) { - return false; - } else if (cmp == 0) { - return false; - } else { - return true; - } -} - bool GlobalSettingsQmlWrapper::isNewVersion(void) { bool isNew = true; @@ -839,7 +803,7 @@ bool GlobalSettingsQmlWrapper::isNewVersion(void) } } - isNew = compareVersionStrings(VERSION, dbVersion); + isNew = AppVersionInfo::compareVersionStrings(VERSION, dbVersion) > 0; if (isNew) { if (GlobInstcs::prefsPtr != Q_NULLPTR) { @@ -855,5 +819,5 @@ bool GlobalSettingsQmlWrapper::isNewVersion(void) QString GlobalSettingsQmlWrapper::loadChangeLogText(void) { - return ChangeLog::changeLogContent(); + return AppVersionInfo::releaseNewsText(); } -- GitLab From bc9c4c0adeff3ec503b11945a9c42829ba0d91b2 Mon Sep 17 00:00:00 2001 From: Karel Slany <karel.slany@nic.cz> Date: Tue, 18 Feb 2025 17:42:20 +0100 Subject: [PATCH 5/9] Fixed cmakelist. --- CMakeLists.txt | 5 +++-- res/qml.qrc | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8a517c523..7c44c6b42 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,10 +19,12 @@ find_package(Qt6 REQUIRED COMPONENTS LinguistTools) qt_standard_project_setup(REQUIRES 6.5) qt_add_executable(mobile-datovka + src/app_version_info.cpp src/auxiliaries/email_helper.cpp src/auxiliaries/email_helper.h src/auxiliaries/ios_helper.cpp src/auxiliaries/ios_helper.h src/backup_zip.cpp src/backup_zip.h src/crypto/crypto.c src/crypto/crypto.h + src/datovka_shared/app_version_info.cpp src/datovka_shared/app_version_info.h src/datovka_shared/compat/compiler.h src/datovka_shared/compat_qt/misc.h src/datovka_shared/compat_qt/random.cpp src/datovka_shared/compat_qt/random.h @@ -109,7 +111,6 @@ qt_add_executable(mobile-datovka src/gov_services/models/gov_form_list_model.cpp src/gov_services/models/gov_form_list_model.h src/gov_services/models/gov_service_list_model.cpp src/gov_services/models/gov_service_list_model.h src/gov_wrapper.cpp src/gov_wrapper.h - src/changelog.cpp src/changelog.h src/initialisation.cpp src/initialisation.h src/io/filesystem.cpp src/io/filesystem.h src/isds/conversion/isds_conversion.cpp src/isds/conversion/isds_conversion.h @@ -233,13 +234,13 @@ qt_add_qml_module(mobile-datovka res/../qml/components/AccessibleTextInfoSmall.qml res/../qml/components/AccessibleToolButton.qml res/../qml/components/AccountList.qml + res/../qml/components/ChangeLogBox.qml res/../qml/components/ControlGroupItem.qml res/../qml/components/DataboxList.qml res/../qml/components/FileDialogue.qml res/../qml/components/FilterBar.qml res/../qml/components/GovFormList.qml res/../qml/components/GovServiceList.qml - res/../qml/components/ChangeLogBox.qml res/../qml/components/MessageBox.qml res/../qml/components/MessageList.qml res/../qml/components/PageHeader.qml diff --git a/res/qml.qrc b/res/qml.qrc index cae41eb70..8f36184fd 100644 --- a/res/qml.qrc +++ b/res/qml.qrc @@ -18,13 +18,13 @@ <file>../qml/components/AccessibleTextInfoSmall.qml</file> <file>../qml/components/AccessibleToolButton.qml</file> <file>../qml/components/AccountList.qml</file> + <file>../qml/components/ChangeLogBox.qml</file> <file>../qml/components/ControlGroupItem.qml</file> <file>../qml/components/DataboxList.qml</file> <file>../qml/components/FileDialogue.qml</file> <file>../qml/components/FilterBar.qml</file> <file>../qml/components/GovFormList.qml</file> <file>../qml/components/GovServiceList.qml</file> - <file>../qml/components/ChangeLogBox.qml</file> <file>../qml/components/MessageBox.qml</file> <file>../qml/components/MessageList.qml</file> <file>../qml/components/PageHeader.qml</file> -- GitLab From c3a0539be241f81b7eb69f52eb24fa8129d6aabd Mon Sep 17 00:00:00 2001 From: Karel Slany <karel.slany@nic.cz> Date: Tue, 18 Feb 2025 17:43:36 +0100 Subject: [PATCH 6/9] Removed repeated pointer checks. --- src/setwrapper.cpp | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/src/setwrapper.cpp b/src/setwrapper.cpp index 6eb66cb4d..293b9c8df 100644 --- a/src/setwrapper.cpp +++ b/src/setwrapper.cpp @@ -787,31 +787,24 @@ bool GlobalSettingsQmlWrapper::useIosDocumentPicker(void) bool GlobalSettingsQmlWrapper::isNewVersion(void) { bool isNew = true; - QString dbVersion; + QString storedVersion; if (GlobInstcs::prefsPtr != Q_NULLPTR) { - GlobInstcs::prefsPtr->strVal("app.version", dbVersion); + GlobInstcs::prefsPtr->strVal("application.notification_shown.last_version", storedVersion); } else { Q_ASSERT(0); + return false; } - if (Q_UNLIKELY(dbVersion.isEmpty())) { - if (GlobInstcs::prefsPtr != Q_NULLPTR) { - GlobInstcs::prefsPtr->setStrVal("app.version", VERSION); - return isNew; - } else { - Q_ASSERT(0); - } + + if (Q_UNLIKELY(storedVersion.isEmpty())) { + GlobInstcs::prefsPtr->setStrVal("application.notification_shown.last_version", VERSION); + return isNew; } - isNew = AppVersionInfo::compareVersionStrings(VERSION, dbVersion) > 0; + isNew = (1 == AppVersionInfo::compareVersionStrings(VERSION, storedVersion)); if (isNew) { - if (GlobInstcs::prefsPtr != Q_NULLPTR) { - GlobInstcs::prefsPtr->setStrVal("app.version", VERSION); - return isNew; - } else { - Q_ASSERT(0); - } + GlobInstcs::prefsPtr->setStrVal("application.notification_shown.last_version", VERSION); } return isNew; -- GitLab From 3234d1000f9a8eb22b37bcd8411cadae8d4e08d9 Mon Sep 17 00:00:00 2001 From: Karel Slany <karel.slany@nic.cz> Date: Wed, 19 Feb 2025 16:25:36 +0100 Subject: [PATCH 7/9] Pulled changes from desktop application. --- src/datovka_shared/app_version_info.cpp | 38 +++++++++++++++++++++++-- src/datovka_shared/app_version_info.h | 38 ++++++++++++++++++++----- src/setwrapper.cpp | 26 +++++++++++------ src/setwrapper.h | 3 ++ 4 files changed, 86 insertions(+), 19 deletions(-) diff --git a/src/datovka_shared/app_version_info.cpp b/src/datovka_shared/app_version_info.cpp index da5ad0a2c..66ee35067 100644 --- a/src/datovka_shared/app_version_info.cpp +++ b/src/datovka_shared/app_version_info.cpp @@ -31,6 +31,40 @@ #include "src/datovka_shared/app_version_info.h" #include "src/datovka_shared/log/log.h" +/* Release version string. */ +static const QRegularExpression releaseVerExpr( + QLatin1String("[0-9][0-9]*\\.[0-9][0-9]*\\.[0-9][0-9]*")); +/* Test build version string. */ +static const QRegularExpression gitAchiveVerExr( + QLatin1String("[0-9][0-9]*\\.[0-9][0-9]*\\.[0-9][0-9]*\\.[0-9][0-9]*\\.[0-9][0-9]*\\.[0-9][0-9]*\\.[0-9a-f][0-9a-f]*")); + +/*! + * @brief Check whether trimmed \a str contains exactly a string matching \a re. + * + * @param[in] str String to be checked for match. + * @param[in] re Regular expression. + * @return True if trimmed \a str matches \a re. + */ +static inline +bool trimmedMatchesRegExpr(const QString &str, const QRegularExpression &re) +{ + const QString trimmedStr = str.trimmed(); + + QRegularExpressionMatch match(re.match(trimmedStr)); + + return match.hasMatch() && (match.capturedLength() == trimmedStr.length()); +} + +bool AppVersionInfo::isReleaseVersionString(const QString &vStr) +{ + return trimmedMatchesRegExpr(vStr, releaseVerExpr); +} + +bool AppVersionInfo::isGitArchiveString(const QString &vStr) +{ + return trimmedMatchesRegExpr(vStr, gitAchiveVerExr); +} + /*! * @brief Strip unwanted data from version string. * @@ -46,9 +80,7 @@ bool stripVersionString(QString &vStr) vStr.remove(QRegularExpression(QLatin1String("^[.]*"))); vStr.remove(QRegularExpression(QLatin1String("[.]*$"))); - QRegularExpression verExpr( - QLatin1String("[0-9][0-9]*\\.[0-9][0-9]*\\.[0-9][0-9]*")); - QRegularExpressionMatch match(verExpr.match(vStr)); + QRegularExpressionMatch match(releaseVerExpr.match(vStr)); return match.hasMatch() && (match.capturedLength() == vStr.length()); } diff --git a/src/datovka_shared/app_version_info.h b/src/datovka_shared/app_version_info.h index ec832ee14..e7b24ebc4 100644 --- a/src/datovka_shared/app_version_info.h +++ b/src/datovka_shared/app_version_info.h @@ -30,6 +30,30 @@ class AppVersionInfo { Q_DECLARE_TR_FUNCTIONS(AppVersionInfo) public: + /*! + * @brief Check whether string contains only release version + * (eg. 4.25.0). + * + * @note The string may contain some leading and trailing white-space characters. + * + * @param[in] vStr Version string. + * @return If \a vStr is a release version. + */ + static + bool isReleaseVersionString(const QString &vStr); + + /*! + * @brief Check whether string contains git development build version + * (eg. 4.25.0.9999.20250106.151005.ee2327675e2f1a9a). + * + * @note The string may contain some leading and trailing white-space characters. + * + * @param[in] vStr Version string. + * @return If \a vStr is a git development build version. + */ + static + bool isGitArchiveString(const QString &vStr); + /*! * @brief Compare newest available version and application version. * @@ -44,11 +68,11 @@ public: static int compareVersionStrings(const QString &vStr1, const QString &vStr2); - /*! - * @brief Release news. - * - * @return Release news text. - */ - static - QString releaseNewsText(void); + /*! + * @brief Release news. + * + * @return Release news text. + */ + static + QString releaseNewsText(void); }; diff --git a/src/setwrapper.cpp b/src/setwrapper.cpp index 293b9c8df..f731a0c62 100644 --- a/src/setwrapper.cpp +++ b/src/setwrapper.cpp @@ -786,22 +786,30 @@ bool GlobalSettingsQmlWrapper::useIosDocumentPicker(void) bool GlobalSettingsQmlWrapper::isNewVersion(void) { + if (Q_UNLIKELY(Q_NULLPTR == GlobInstcs::prefsPtr)) { + Q_ASSERT(0); + return false; + } + bool isNew = true; QString storedVersion; - if (GlobInstcs::prefsPtr != Q_NULLPTR) { - GlobInstcs::prefsPtr->strVal("application.notification_shown.last_version", storedVersion); - } else { - Q_ASSERT(0); + /* + * If running app doesn't have release version then don't check. + */ + if (Q_UNLIKELY(!AppVersionInfo::isReleaseVersionString(VERSION))) { return false; } - if (Q_UNLIKELY(storedVersion.isEmpty())) { - GlobInstcs::prefsPtr->setStrVal("application.notification_shown.last_version", VERSION); - return isNew; - } + GlobInstcs::prefsPtr->strVal("application.notification_shown.last_version", storedVersion); - isNew = (1 == AppVersionInfo::compareVersionStrings(VERSION, storedVersion)); + /* + * If stored version is empty or non-release version string then behave + * as having a new version. + */ + isNew = storedVersion.isEmpty() + || (!AppVersionInfo::isReleaseVersionString(storedVersion)) + || (1 == AppVersionInfo::compareVersionStrings(VERSION, storedVersion)); if (isNew) { GlobInstcs::prefsPtr->setStrVal("application.notification_shown.last_version", VERSION); diff --git a/src/setwrapper.h b/src/setwrapper.h index 9ac28a408..aa1a08e25 100644 --- a/src/setwrapper.h +++ b/src/setwrapper.h @@ -494,6 +494,9 @@ public: /*! * @brief Check if new version after first startup. * + * @note This check isn't performed if running app doesn't have a valid + * release version. + * * @return True if it is new version after first startup. */ Q_INVOKABLE static -- GitLab From a5e038ba36ba13f7aca80f0232b570f7d1892aeb Mon Sep 17 00:00:00 2001 From: Karel Slany <karel.slany@nic.cz> Date: Wed, 19 Feb 2025 17:40:37 +0100 Subject: [PATCH 8/9] Fixed some strings. --- qml/components/ChangeLogBox.qml | 2 +- qml/pages/PageAboutApp.qml | 4 ++-- src/app_version_info.cpp | 12 ++++-------- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/qml/components/ChangeLogBox.qml b/qml/components/ChangeLogBox.qml index 13b0b172b..9b580a377 100644 --- a/qml/components/ChangeLogBox.qml +++ b/qml/components/ChangeLogBox.qml @@ -60,7 +60,7 @@ Popup { horizontalAlignment : Text.Center wrapMode: Text.Wrap font.bold: true - text: qsTr("What is news?") + text: qsTr("What's new?") } Rectangle { Layout.preferredWidth: root.preferredMinWidth diff --git a/qml/pages/PageAboutApp.qml b/qml/pages/PageAboutApp.qml index bc0709a58..9dd306477 100644 --- a/qml/pages/PageAboutApp.qml +++ b/qml/pages/PageAboutApp.qml @@ -90,8 +90,8 @@ Page { } AccessibleButton { anchors.horizontalCenter: parent.horizontalCenter - text: qsTr("Show ChangeLog") - accessibleName: qsTr("What is news?") + text: qsTr("Show News") + accessibleName: qsTr("What's new?") onClicked: changeLogBox.showChangeLog() } AccessibleTextInfo { diff --git a/src/app_version_info.cpp b/src/app_version_info.cpp index b51dee8e2..685d33104 100644 --- a/src/app_version_info.cpp +++ b/src/app_version_info.cpp @@ -21,22 +21,18 @@ * the two. */ +#include <QStringBuilder> + #include "src/datovka_shared/app_version_info.h" #define textLineNL(text) \ - (QLatin1String("<style>li{margin-left:-30px;}</style><ul><li>") + (text) + QLatin1String("</li></ul>")) + (QLatin1String("<style>li{margin-left:-30px;}</style><ul><li>") % (text) % QLatin1String("</li></ul>")) QString AppVersionInfo::releaseNewsText(void) { QString content; - content.append(textLineNL(tr("Added a preferences editor."))); - content.append(textLineNL(tr("Custom images can be assigned to individual data boxes as logos in the data-box list."))); - content.append(textLineNL(tr("Using dedicated redirection URL to access donation page."))); - content.append(textLineNL(tr("INI configuration file is written in three stages. A copy is written then the original is removed and finally the copy replaces the original."))); - content.append(textLineNL(tr("On application start-up when INI configuration is missing the application searches for a copy before creating a blank INI file."))); - content.append(textLineNL(tr("Fixed white border around launcher icon in iOS."))); - content.append(textLineNL(tr("Fixed logging of debug information when building with cmake."))); + content.append(textLineNL(tr("Showing news. :)"))); return content; } -- GitLab From 08da4ba80fef1e1f2872bf9e8f071f2edc0b903f Mon Sep 17 00:00:00 2001 From: Martin Straka <martin.straka@nic.cz> Date: Thu, 20 Feb 2025 09:06:42 +0100 Subject: [PATCH 9/9] Moved changelog button into development tab. --- qml/pages/PageAboutApp.qml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/qml/pages/PageAboutApp.qml b/qml/pages/PageAboutApp.qml index 9dd306477..8d47d48ee 100644 --- a/qml/pages/PageAboutApp.qml +++ b/qml/pages/PageAboutApp.qml @@ -88,12 +88,6 @@ Page { + "<br/>" + "<a href=\"%1\">%2</a>".arg("https://datovka.nic.cz/redirect/donation-mobile.html").arg(qsTr("Make a donation.")) } - AccessibleButton { - anchors.horizontalCenter: parent.horizontalCenter - text: qsTr("Show News") - accessibleName: qsTr("What's new?") - onClicked: changeLogBox.showChangeLog() - } AccessibleTextInfo { horizontalAlignment: Text.AlignHCenter textFormat: TextEdit.RichText @@ -157,6 +151,12 @@ Page { + "<br/>" + "<a href=\"mailto:datovka@labs.nic.cz?Subject=[Mobile Datovka%20" + settings.appVersion() + "]\">datovka@labs.nic.cz</a>" } + AccessibleButton { + anchors.horizontalCenter: parent.horizontalCenter + text: qsTr("Show News") + accessibleName: qsTr("What's new?") + onClicked: changeLogBox.showChangeLog() + } } // Column } // Pane } // Flickable -- GitLab