diff --git a/translations/updaterplugin_de.ts b/translations/updaterplugin_de.ts
new file mode 100644
index 0000000..0fd56e0
--- /dev/null
+++ b/translations/updaterplugin_de.ts
@@ -0,0 +1,28 @@
+
+
+
+
+ UpdaterDialog
+
+
+
+ New update available!
+ Neues Update verfügbar!
+
+
+
+ There is a new release available to download!
+ Ein neues Release steht zum Download bereit!
+
+
+
+ Dont show today anymore
+ Heute nicht mehr anzeigen
+
+
+
+ Could not open default webbrowser!
+ Konnte Standard-Browser nicht öffnen!
+
+
+
diff --git a/translations/updaterplugin_en.ts b/translations/updaterplugin_en.ts
new file mode 100644
index 0000000..fd1099e
--- /dev/null
+++ b/translations/updaterplugin_en.ts
@@ -0,0 +1,28 @@
+
+
+
+
+ UpdaterDialog
+
+
+
+ New update available!
+
+
+
+
+ There is a new release available to download!
+
+
+
+
+ Dont show today anymore
+
+
+
+
+ Could not open default webbrowser!
+
+
+
+
diff --git a/updaterdialog.cpp b/updaterdialog.cpp
new file mode 100644
index 0000000..0e3c6f4
--- /dev/null
+++ b/updaterdialog.cpp
@@ -0,0 +1,108 @@
+#include "updaterdialog.h"
+#include "ui_updaterdialog.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "mainwindow.h"
+#include "zeiterfassungsettings.h"
+#include "zeiterfassungapi.h"
+
+#include "updatersettings.h"
+
+UpdaterDialog::UpdaterDialog(MainWindow &mainWindow) :
+ ZeiterfassungDialog(&mainWindow),
+ ui(new Ui::UpdaterDialog),
+ m_mainWindow(mainWindow)
+{
+ ui->setupUi(this);
+
+ setAttribute(Qt::WA_DeleteOnClose);
+
+ connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &UpdaterDialog::acceptedSlot);
+ connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &UpdaterDialog::rejectedSlot);
+
+ m_reply = m_mainWindow.erfassung().manager()->get(QNetworkRequest(UpdaterSettings(mainWindow.settings()).url()));
+ connect(m_reply, &QNetworkReply::finished, this, &UpdaterDialog::finished);
+}
+
+UpdaterDialog::~UpdaterDialog()
+{
+ delete ui;
+}
+
+void UpdaterDialog::acceptedSlot()
+{
+ if(ui->checkBoxDontShow->isChecked())
+ UpdaterSettings(m_mainWindow.settings()).setLastUpdateCheck(QDate::currentDate());
+
+ if(!QDesktopServices::openUrl(m_url))
+ QMessageBox::warning(this, tr("Could not open default webbrowser!"), tr("Could not open default webbrowser!"));
+
+ accept();
+}
+
+void UpdaterDialog::rejectedSlot()
+{
+ if(ui->checkBoxDontShow->isChecked())
+ UpdaterSettings(m_mainWindow.settings()).setLastUpdateCheck(QDate::currentDate());
+
+ reject();
+}
+
+void UpdaterDialog::finished()
+{
+ if(m_reply->error() != QNetworkReply::NoError)
+ {
+ qWarning() << "request error" << m_reply->error() << m_reply->errorString();
+ return;
+ }
+
+ QJsonParseError error;
+ auto document = QJsonDocument::fromJson(m_reply->readAll(), &error);
+
+ if(error.error != QJsonParseError::NoError)
+ {
+ qWarning() << "parse error" << error.error << error.errorString();
+ return;
+ }
+
+ if(!document.isArray())
+ {
+ qWarning() << "document is not an array!";
+ return;
+ }
+
+ auto appVersion = QVersionNumber::fromString(QCoreApplication::applicationVersion());
+
+ auto array = document.array();
+
+ for(const auto &releaseVal : array)
+ {
+ auto releaseObj = releaseVal.toObject();
+ auto version = QVersionNumber::fromString(releaseObj.value(QStringLiteral("tag_name")).toString());
+
+ if(appVersion < version)
+ {
+ m_url = QUrl(releaseObj.value(QStringLiteral("html_url")).toString());
+ ui->labelDescription->setText(releaseObj.value(QStringLiteral("body")).toString());
+
+ show();
+ return;
+ }
+ }
+
+ UpdaterSettings(m_mainWindow.settings()).setLastUpdateCheck(QDate::currentDate());
+
+ deleteLater();
+}
diff --git a/updaterdialog.h b/updaterdialog.h
new file mode 100644
index 0000000..2ea7560
--- /dev/null
+++ b/updaterdialog.h
@@ -0,0 +1,31 @@
+#pragma once
+
+#include
+
+#include "zeiterfassungdialog.h"
+
+class QNetworkReply;
+
+namespace Ui { class UpdaterDialog; }
+class MainWindow;
+
+class UpdaterDialog : public ZeiterfassungDialog
+{
+ Q_OBJECT
+
+public:
+ explicit UpdaterDialog(MainWindow &parent);
+ ~UpdaterDialog();
+
+private Q_SLOTS:
+ void acceptedSlot();
+ void rejectedSlot();
+ void finished();
+
+private:
+ Ui::UpdaterDialog *ui;
+ MainWindow &m_mainWindow;
+ QNetworkReply *m_reply;
+
+ QUrl m_url;
+};
diff --git a/updaterdialog.ui b/updaterdialog.ui
new file mode 100644
index 0000000..8d8f8d3
--- /dev/null
+++ b/updaterdialog.ui
@@ -0,0 +1,90 @@
+
+
+ UpdaterDialog
+
+
+
+ 0
+ 0
+ 447
+ 280
+
+
+
+ New update available!
+
+
+ -
+
+
+
+ 20
+
+
+
+ New update available!
+
+
+
+ -
+
+
+ There is a new release available to download!
+
+
+
+ -
+
+
+ QFrame::WinPanel
+
+
+ QFrame::Sunken
+
+
+ TextLabel
+
+
+ Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop
+
+
+
+ -
+
+
-
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ Dont show today anymore
+
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QDialogButtonBox::Cancel|QDialogButtonBox::Ok
+
+
+
+
+
+
+
+
diff --git a/updaterplugin.cpp b/updaterplugin.cpp
new file mode 100644
index 0000000..5654138
--- /dev/null
+++ b/updaterplugin.cpp
@@ -0,0 +1,46 @@
+#include "updaterplugin.h"
+
+#include
+#include
+#include
+#include
+
+#include "mainwindow.h"
+#include "zeiterfassungsettings.h"
+#include "zeiterfassungapi.h"
+
+#include "updatersettings.h"
+#include "updaterdialog.h"
+#include "updatersettingswidget.h"
+
+UpdaterPlugin::UpdaterPlugin(QObject *parent) :
+ ZeiterfassungPlugin(parent)
+{
+ qDebug() << "called";
+
+ static auto dir = QDir(QCoreApplication::applicationDirPath()).absoluteFilePath(QStringLiteral("translations"));
+
+ if(m_translator.load(QLocale(), QStringLiteral("updaterplugin"), QStringLiteral("_"), dir))
+ {
+ if(!QCoreApplication::installTranslator(&m_translator))
+ {
+ qWarning() << "could not install translation updaterplugin";
+ }
+ }
+ else
+ {
+ qWarning() << "could not load translation updaterplugin";
+ }
+}
+
+void UpdaterPlugin::attachTo(MainWindow &mainWindow)
+{
+ auto lastUpdateCheck = UpdaterSettings(mainWindow.settings()).lastUpdateCheck();
+ if(lastUpdateCheck.isNull() || lastUpdateCheck < QDate::currentDate())
+ new UpdaterDialog(mainWindow);
+}
+
+SettingsWidget *UpdaterPlugin::settingsWidget(ZeiterfassungSettings &settings, QWidget *parent) const
+{
+ return new UpdaterSettingsWidget(settings, parent);
+}
diff --git a/updaterplugin.h b/updaterplugin.h
new file mode 100644
index 0000000..e98d448
--- /dev/null
+++ b/updaterplugin.h
@@ -0,0 +1,26 @@
+#pragma once
+
+#include
+#include
+
+#include "zeiterfassungplugin.h"
+
+class MainWindow;
+
+class Q_DECL_EXPORT UpdaterPlugin : public ZeiterfassungPlugin
+{
+ Q_OBJECT
+ Q_PLUGIN_METADATA(IID "dbsoftware.zeiterfassung.plugin/1.0" FILE "updaterplugin.json")
+ Q_INTERFACES(ZeiterfassungPlugin)
+
+public:
+ explicit UpdaterPlugin(QObject *parent = Q_NULLPTR);
+
+ // ZeiterfassungPlugin interface
+ void attachTo(MainWindow &mainWindow) Q_DECL_OVERRIDE;
+
+ virtual SettingsWidget *settingsWidget(ZeiterfassungSettings &settings, QWidget *parent = Q_NULLPTR) const Q_DECL_OVERRIDE;
+
+private:
+ QTranslator m_translator;
+};
diff --git a/updaterplugin.json b/updaterplugin.json
new file mode 100644
index 0000000..e69de29
diff --git a/updaterplugin.pro b/updaterplugin.pro
new file mode 100644
index 0000000..ed617ce
--- /dev/null
+++ b/updaterplugin.pro
@@ -0,0 +1,26 @@
+QT += core network gui widgets
+
+DBLIBS += zeiterfassungcore zeiterfassunggui
+
+TARGET = updaterplugin
+
+HEADERS += updaterdialog.h \
+ updaterplugin.h \
+ updatersettings.h \
+ updatersettingswidget.h
+
+SOURCES += updaterdialog.cpp \
+ updaterplugin.cpp \
+ updatersettings.cpp \
+ updatersettingswidget.cpp
+
+FORMS += updaterdialog.ui
+
+RESOURCES +=
+
+TRANSLATIONS += translations/updaterplugin_en.ts \
+ translations/updaterplugin_de.ts
+
+OTHER_FILES += updaterplugin.json
+
+include(../plugin.pri)
diff --git a/updatersettings.cpp b/updatersettings.cpp
new file mode 100644
index 0000000..9d439e0
--- /dev/null
+++ b/updatersettings.cpp
@@ -0,0 +1,72 @@
+#include "updatersettings.h"
+
+#include "zeiterfassungsettings.h"
+
+const QString UpdaterSettings::m_url("UpdaterPlugin/url");
+const QString UpdaterSettings::m_lastUpdateCheck("UpdaterPlugin/lastUpdateCheck");
+const QUrl UpdaterSettings::m_defaultUrl(QStringLiteral("https://api.github.com/repos/0xFEEDC0DE64/QtZeiterfassung/releases"));
+
+UpdaterSettings::UpdaterSettings(ZeiterfassungSettings &settings, QObject *parent) :
+ QObject(parent),
+ m_settings(settings)
+{
+
+}
+
+QUrl UpdaterSettings::url() const
+{
+ return m_settings.value(m_url, m_defaultUrl).toUrl();
+}
+
+bool UpdaterSettings::setUrl(const QUrl &url)
+{
+ if(this->url() == url)
+ return true;
+
+ if(url == m_defaultUrl)
+ m_settings.remove(m_url);
+ else
+ m_settings.setValue(m_url, url);
+
+ m_settings.sync();
+
+ const auto success = m_settings.status() == QSettings::NoError;
+ if(success)
+ Q_EMIT urlChanged(url);
+ else
+ {
+ Q_EMIT m_settings.saveErrorOccured();
+ Q_EMIT saveErrorOccured();
+ }
+
+ return success;
+}
+
+QDate UpdaterSettings::lastUpdateCheck() const
+{
+ return m_settings.value(m_lastUpdateCheck).toDate();
+}
+
+bool UpdaterSettings::setLastUpdateCheck(const QDate &lastUpdateCheck)
+{
+ if(this->lastUpdateCheck() == lastUpdateCheck)
+ return true;
+
+ if(!lastUpdateCheck.isValid())
+ m_settings.remove(m_lastUpdateCheck);
+ else
+ m_settings.setValue(m_lastUpdateCheck, lastUpdateCheck);
+
+ m_settings.sync();
+
+ const auto success = m_settings.status() == QSettings::NoError;
+ if(success)
+ Q_EMIT lastUpdateCheckChanged(lastUpdateCheck);
+ else
+ {
+ Q_EMIT m_settings.saveErrorOccured();
+ Q_EMIT saveErrorOccured();
+ }
+
+ return success;
+}
diff --git a/updatersettings.h b/updatersettings.h
new file mode 100644
index 0000000..f6998eb
--- /dev/null
+++ b/updatersettings.h
@@ -0,0 +1,36 @@
+#pragma once
+
+#include
+#include
+#include
+
+class ZeiterfassungSettings;
+
+class UpdaterSettings : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(QUrl url READ url WRITE setUrl NOTIFY urlChanged)
+ Q_PROPERTY(QDate lastUpdateCheck READ lastUpdateCheck WRITE setLastUpdateCheck NOTIFY lastUpdateCheckChanged)
+
+public:
+ explicit UpdaterSettings(ZeiterfassungSettings &settings, QObject *parent = Q_NULLPTR);
+
+ QUrl url() const;
+ bool setUrl(const QUrl &url);
+
+ QDate lastUpdateCheck() const;
+ bool setLastUpdateCheck(const QDate &lastUpdateCheck);
+
+Q_SIGNALS:
+ void saveErrorOccured();
+
+ void urlChanged(const QUrl &url);
+ void lastUpdateCheckChanged(const QDate &lastUpdateCheck);
+
+private:
+ ZeiterfassungSettings &m_settings;
+
+ static const QString m_url;
+ static const QString m_lastUpdateCheck;
+ static const QUrl m_defaultUrl;
+};
diff --git a/updatersettingswidget.cpp b/updatersettingswidget.cpp
new file mode 100644
index 0000000..883bcc1
--- /dev/null
+++ b/updatersettingswidget.cpp
@@ -0,0 +1,32 @@
+#include "updatersettingswidget.h"
+
+#include
+#include
+
+UpdaterSettingsWidget::UpdaterSettingsWidget(ZeiterfassungSettings &settings, QWidget *parent) :
+ SettingsWidget(parent),
+ m_settings(settings)
+{
+ auto layout = new QFormLayout(this);
+ layout->setMargin(0);
+
+ m_lineEdit = new QLineEdit(m_settings.url().toString(), this);
+ layout->addRow(tr("Updater url:"), m_lineEdit);
+
+ setLayout(layout);
+}
+
+bool UpdaterSettingsWidget::isValid(QString &message) const
+{
+ auto valid = QUrl::fromUserInput(m_lineEdit->text()).isValid();
+
+ if(!valid)
+ message = tr("The updater url is invalid!");
+
+ return valid;
+}
+
+bool UpdaterSettingsWidget::apply()
+{
+ return m_settings.setUrl(QUrl(m_lineEdit->text()));
+}
diff --git a/updatersettingswidget.h b/updatersettingswidget.h
new file mode 100644
index 0000000..a3568d2
--- /dev/null
+++ b/updatersettingswidget.h
@@ -0,0 +1,25 @@
+#pragma once
+
+#include "settingswidget.h"
+
+#include "updatersettings.h"
+
+class QLineEdit;
+
+class UpdaterSettingsWidget : public SettingsWidget
+{
+ Q_OBJECT
+
+public:
+ explicit UpdaterSettingsWidget(ZeiterfassungSettings &settings, QWidget *parent = Q_NULLPTR);
+
+ virtual bool isValid(QString &message) const Q_DECL_OVERRIDE;
+
+public Q_SLOTS:
+ virtual bool apply() Q_DECL_OVERRIDE;
+
+private:
+ UpdaterSettings m_settings;
+
+ QLineEdit *m_lineEdit;
+};