work on better notifications

This commit is contained in:
Martin Rotter 2021-06-15 15:02:14 +02:00
parent a357c97226
commit 5e4ae7657b
23 changed files with 407 additions and 29 deletions

View file

@ -6,6 +6,11 @@
<file>text/COPYING_GNU_GPL</file> <file>text/COPYING_GNU_GPL</file>
<file>text/COPYING_GNU_GPL_HTML</file> <file>text/COPYING_GNU_GPL_HTML</file>
<file>sounds/boing.wav</file>
<file>sounds/doorbell.wav</file>
<file>sounds/rooster.wav</file>
<file>sounds/sheep.wav</file>
<file>scripts/adblock/adblock-server.js</file> <file>scripts/adblock/adblock-server.js</file>
<file>scripts/public_suffix_list.dat</file> <file>scripts/public_suffix_list.dat</file>

BIN
resources/sounds/boing.wav Executable file

Binary file not shown.

BIN
resources/sounds/doorbell.wav Executable file

Binary file not shown.

BIN
resources/sounds/rooster.wav Executable file

Binary file not shown.

BIN
resources/sounds/sheep.wav Executable file

Binary file not shown.

View file

@ -45,6 +45,7 @@
#define MSG_SCORE_MAX 100.0 #define MSG_SCORE_MAX 100.0
#define MSG_SCORE_MIN 0.0 #define MSG_SCORE_MIN 0.0
#define SOUNDS_BUILTIN_DIRECTORY ":/sounds"
#define ARGUMENTS_LIST_SEPARATOR "\n" #define ARGUMENTS_LIST_SEPARATOR "\n"
#define IS_IN_ARRAY(offset, array) ((offset >= 0) && (offset < array.count())) #define IS_IN_ARRAY(offset, array) ((offset >= 0) && (offset < array.count()))
#define DEFAULT_SQL_MESSAGES_FILTER "0 > 1" #define DEFAULT_SQL_MESSAGES_FILTER "0 > 1"

View file

@ -42,13 +42,13 @@ void NewspaperPreviewer::showMoreMessages() {
prev->loadMessage(msg, m_root.data()); prev->loadMessage(msg, m_root.data());
} }
m_ui->m_btnShowMoreMessages->setText(tr("Show more messages (%n remaining)", "", m_messages.size())); m_ui->m_btnShowMoreMessages->setText(tr("Show more articles (%n remaining)", "", m_messages.size()));
m_ui->m_btnShowMoreMessages->setEnabled(!m_messages.isEmpty()); m_ui->m_btnShowMoreMessages->setEnabled(!m_messages.isEmpty());
m_ui->scrollArea->verticalScrollBar()->setValue(current_scroll); m_ui->scrollArea->verticalScrollBar()->setValue(current_scroll);
} }
else { else {
qApp->showGuiMessage(tr("Cannot show more messages"), qApp->showGuiMessage(tr("Cannot show more articles"),
tr("Cannot show more messages because parent feed was removed."), tr("Cannot show more articles because parent feed was removed."),
QSystemTrayIcon::MessageIcon::Warning, QSystemTrayIcon::MessageIcon::Warning,
qApp->mainForm(), qApp->mainForm(),
true); true);

View file

@ -0,0 +1,41 @@
// For license of this file, see <project-root-folder>/LICENSE.md.
#include "gui/notifications/notificationseditor.h"
#include "3rd-party/boolinq/boolinq.h"
#include "gui/notifications/singlenotificationeditor.h"
#include <QVBoxLayout>
NotificationsEditor::NotificationsEditor(QWidget* parent) : QScrollArea(parent), m_layout(new QVBoxLayout(this)) {
m_ui.setupUi(this);
setLayout(m_layout);
}
void NotificationsEditor::loadNotifications(const QList<Notification>& notifications) {
auto all_events = Notification::allEvents();
auto notif = boolinq::from(notifications);
for (auto ev : all_events) {
if (notif.any([ev](auto n) {
return n.event() == ev;
})) {
auto* notif_editor = new SingleNotificationEditor(notif.first([ev](auto n) {
return n.event() == ev;
}), this);
notif_editor->setNotificationEnabled(true);
m_layout->addWidget(notif_editor);
}
else {
auto* notif_editor = new SingleNotificationEditor(Notification(ev, {}), this);
notif_editor->setNotificationEnabled(false);
m_layout->addWidget(notif_editor);
}
}
m_layout->addSpacerItem(new QSpacerItem(20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding));
}

View file

@ -0,0 +1,27 @@
// For license of this file, see <project-root-folder>/LICENSE.md.
#ifndef NOTIFICATIONSEDITOR_H
#define NOTIFICATIONSEDITOR_H
#include <QScrollArea>
#include "ui_notificationseditor.h"
#include "miscellaneous/notification.h"
class QVBoxLayout;
class NotificationsEditor : public QScrollArea {
Q_OBJECT
public:
explicit NotificationsEditor(QWidget* parent = nullptr);
void loadNotifications(const QList<Notification>& notifications);
private:
Ui::NotificationsEditor m_ui;
QVBoxLayout* m_layout;
};
#endif // NOTIFICATIONSEDITOR_H

View file

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>NotificationsEditor</class>
<widget class="QScrollArea" name="NotificationsEditor">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
</widget>
<resources/>
<connections/>
</ui>

View file

@ -0,0 +1,41 @@
// For license of this file, see <project-root-folder>/LICENSE.md.
#include "gui/notifications/singlenotificationeditor.h"
#include "miscellaneous/application.h"
#include "miscellaneous/iconfactory.h"
SingleNotificationEditor::SingleNotificationEditor(const Notification& notification, QWidget* parent)
: QWidget(parent), m_notificationEvent(Notification::Event::UnknownEvent) {
m_ui.setupUi(this);
m_ui.m_btnBrowseSound->setIcon(qApp->icons()->fromTheme(QSL("document-open")));
m_ui.m_btnPlaySound->setIcon(qApp->icons()->fromTheme(QSL("media-playback-start")));
connect(m_ui.m_btnPlaySound, &QPushButton::clicked, this, &SingleNotificationEditor::playSound);
loadNotification(notification);
}
Notification SingleNotificationEditor::notification() const {
return Notification(m_notificationEvent, m_ui.m_txtSound->text());
}
bool SingleNotificationEditor::notificationEnabled() const {
return m_ui.m_gbNotification->isChecked();
}
void SingleNotificationEditor::setNotificationEnabled(bool enabled) {
m_ui.m_gbNotification->setChecked(enabled);
}
void SingleNotificationEditor::playSound() {
Notification({}, m_ui.m_txtSound->text()).playSound(qApp);
}
void SingleNotificationEditor::loadNotification(const Notification& notification) {
m_ui.m_txtSound->setText(notification.soundPath());
m_ui.m_gbNotification->setTitle(Notification::nameForEvent(notification.event()));
m_notificationEvent = notification.event();
}

View file

@ -0,0 +1,34 @@
// For license of this file, see <project-root-folder>/LICENSE.md.
#ifndef SINGLENOTIFICATIONEDITOR_H
#define SINGLENOTIFICATIONEDITOR_H
#include <QWidget>
#include "ui_singlenotificationeditor.h"
#include "miscellaneous/notification.h"
class SingleNotificationEditor : public QWidget {
Q_OBJECT
public:
explicit SingleNotificationEditor(const Notification& notification, QWidget* parent = nullptr);
Notification notification() const;
bool notificationEnabled() const;
void setNotificationEnabled(bool enabled);
private slots:
void playSound();
private:
void loadNotification(const Notification& notification);
private:
Ui::SingleNotificationEditor m_ui;
Notification::Event m_notificationEvent;
};
#endif // SINGLENOTIFICATIONEDITOR_H

View file

@ -0,0 +1,86 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SingleNotificationEditor</class>
<widget class="QWidget" name="SingleNotificationEditor">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>125</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QGroupBox" name="m_gbNotification">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Sound</string>
</property>
</widget>
</item>
<item row="0" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLineEdit" name="m_txtSound">
<property name="placeholderText">
<string>Full path to your WAV sound file</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="m_btnBrowseSound">
<property name="text">
<string>&amp;Browse</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="m_btnPlaySound">
<property name="text">
<string>&amp;Play</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View file

@ -2,19 +2,38 @@
#include "gui/settings/settingsnotifications.h" #include "gui/settings/settingsnotifications.h"
#include "3rd-party/boolinq/boolinq.h"
#include "gui/guiutilities.h"
#include "gui/notifications/notificationseditor.h"
#include "miscellaneous/application.h"
#include "miscellaneous/notificationfactory.h"
#include "miscellaneous/settings.h" #include "miscellaneous/settings.h"
#include <QDir>
SettingsNotifications::SettingsNotifications(Settings* settings, QWidget* parent) : SettingsPanel(settings, parent) { SettingsNotifications::SettingsNotifications(Settings* settings, QWidget* parent) : SettingsPanel(settings, parent) {
m_ui.setupUi(this); m_ui.setupUi(this);
GuiUtilities::setLabelAsNotice(*m_ui.m_lblAvailableSounds, false);
connect(m_ui.m_checkEnableNotifications, &QCheckBox::toggled, this, &SettingsNotifications::dirtifySettings); connect(m_ui.m_checkEnableNotifications, &QCheckBox::toggled, this, &SettingsNotifications::dirtifySettings);
} }
void SettingsNotifications::loadSettings() { void SettingsNotifications::loadSettings() {
onBeginLoadSettings(); onBeginLoadSettings();
auto builtin_sounds = QDir(SOUNDS_BUILTIN_DIRECTORY).entryInfoList(QDir::Filter::Files,
QDir::SortFlag::Name);
auto iter = boolinq::from(builtin_sounds).select([](const QFileInfo& i) {
return QSL(" %1").arg(i.absoluteFilePath());
}).toStdList();
auto descs = FROM_STD_LIST(QStringList, iter).join(QSL("\n"));
m_ui.m_lblAvailableSounds->setText(QSL("Built-in sounds:\n%1").arg(descs));
// Load fancy notification settings. // Load fancy notification settings.
m_ui.m_checkEnableNotifications->setChecked(settings()->value(GROUP(Notifications), SETTING(Notifications::EnableNotifications)).toBool()); m_ui.m_checkEnableNotifications->setChecked(settings()->value(GROUP(Notifications), SETTING(Notifications::EnableNotifications)).toBool());
m_ui.m_editor->loadNotifications(qApp->notifications()->allNotifications());
onEndLoadSettings(); onEndLoadSettings();
} }

View file

@ -26,28 +26,59 @@
<property name="bottomMargin"> <property name="bottomMargin">
<number>0</number> <number>0</number>
</property> </property>
<item row="1" column="0" colspan="2">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="0" colspan="2"> <item row="0" column="0" colspan="2">
<widget class="QCheckBox" name="m_checkEnableNotifications"> <widget class="QCheckBox" name="m_checkEnableNotifications">
<property name="text"> <property name="text">
<string>Enable popup balloon tooltips</string> <string>Enable notifications</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="0" colspan="2">
<widget class="NotificationsEditor" name="m_editor" native="true">
<property name="enabled">
<bool>false</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QLabel" name="m_lblAvailableSounds"/>
</item>
</layout> </layout>
</widget> </widget>
<customwidgets>
<customwidget>
<class>NotificationsEditor</class>
<extends>QWidget</extends>
<header>notificationseditor.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>m_checkEnableNotifications</tabstop>
</tabstops>
<resources/> <resources/>
<connections/> <connections>
<connection>
<sender>m_checkEnableNotifications</sender>
<signal>toggled(bool)</signal>
<receiver>m_editor</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>199</x>
<y>8</y>
</hint>
<hint type="destinationlabel">
<x>0</x>
<y>31</y>
</hint>
</hints>
</connection>
</connections>
</ui> </ui>

View file

@ -60,6 +60,8 @@ HEADERS += core/feeddownloader.h \
exceptions/ioexception.h \ exceptions/ioexception.h \
exceptions/networkexception.h \ exceptions/networkexception.h \
exceptions/scriptexception.h \ exceptions/scriptexception.h \
gui/notifications/notificationseditor.h \
gui/notifications/singlenotificationeditor.h \
gui/reusable/baselineedit.h \ gui/reusable/baselineedit.h \
gui/settings/settingsnotifications.h \ gui/settings/settingsnotifications.h \
gui/toolbars/basetoolbar.h \ gui/toolbars/basetoolbar.h \
@ -242,6 +244,8 @@ SOURCES += core/feeddownloader.cpp \
exceptions/ioexception.cpp \ exceptions/ioexception.cpp \
exceptions/networkexception.cpp \ exceptions/networkexception.cpp \
exceptions/scriptexception.cpp \ exceptions/scriptexception.cpp \
gui/notifications/notificationseditor.cpp \
gui/notifications/singlenotificationeditor.cpp \
gui/reusable/baselineedit.cpp \ gui/reusable/baselineedit.cpp \
gui/settings/settingsnotifications.cpp \ gui/settings/settingsnotifications.cpp \
gui/toolbars/basetoolbar.cpp \ gui/toolbars/basetoolbar.cpp \
@ -402,6 +406,8 @@ FORMS += gui/dialogs/formabout.ui \
gui/dialogs/formrestoredatabasesettings.ui \ gui/dialogs/formrestoredatabasesettings.ui \
gui/dialogs/formsettings.ui \ gui/dialogs/formsettings.ui \
gui/dialogs/formupdate.ui \ gui/dialogs/formupdate.ui \
gui/notifications/notificationseditor.ui \
gui/notifications/singlenotificationeditor.ui \
gui/reusable/networkproxydetails.ui \ gui/reusable/networkproxydetails.ui \
gui/newspaperpreviewer.ui \ gui/newspaperpreviewer.ui \
gui/reusable/searchtextwidget.ui \ gui/reusable/searchtextwidget.ui \
@ -494,6 +500,7 @@ INCLUDEPATH += $$PWD/. \
$$PWD/gui/dialogs \ $$PWD/gui/dialogs \
$$PWD/gui/reusable \ $$PWD/gui/reusable \
$$PWD/gui/toolbars \ $$PWD/gui/toolbars \
$$PWD/gui/notifications \
$$PWD/dynamic-shortcuts $$PWD/dynamic-shortcuts
TRANSLATIONS += $$files($$PWD/../../localization/rssguard_*.ts, false) \ TRANSLATIONS += $$files($$PWD/../../localization/rssguard_*.ts, false) \

View file

@ -87,7 +87,15 @@ Application::Application(const QString& id, int& argc, char** argv)
}); });
#endif #endif
if (isFirstRun()) {
m_notifications->save({
Notification(Notification::Event::NewArticlesFetched,
QSL("%1/rooster.wav").arg(SOUNDS_BUILTIN_DIRECTORY))
}, settings());
}
else {
m_notifications->load(settings()); m_notifications->load(settings());
}
qDebugNN << LOGSEC_CORE qDebugNN << LOGSEC_CORE
<< "OpenSSL version:" << "OpenSSL version:"
@ -231,6 +239,11 @@ void Application::eliminateFirstRuns() {
settings()->setValue(GROUP(General), QString(General::FirstRun) + QL1C('_') + APP_VERSION, false); settings()->setValue(GROUP(General), QString(General::FirstRun) + QL1C('_') + APP_VERSION, false);
} }
NotificationFactory* Application::notifications() const
{
return m_notifications;
}
void Application::setFeedReader(FeedReader* feed_reader) { void Application::setFeedReader(FeedReader* feed_reader) {
m_feedReader = feed_reader; m_feedReader = feed_reader;
connect(m_feedReader, &FeedReader::feedUpdatesFinished, this, &Application::onFeedUpdatesFinished); connect(m_feedReader, &FeedReader::feedUpdatesFinished, this, &Application::onFeedUpdatesFinished);

View file

@ -76,6 +76,7 @@ class RSSGUARD_DLLSPEC Application : public SingleApplication {
FormMain* mainForm(); FormMain* mainForm();
QWidget* mainFormWidget(); QWidget* mainFormWidget();
SystemTrayIcon* trayIcon(); SystemTrayIcon* trayIcon();
NotificationFactory* notifications() const;
QIcon desktopAwareIcon() const; QIcon desktopAwareIcon() const;

View file

@ -30,3 +30,27 @@ void Notification::setSoundPath(const QString& sound_path) {
void Notification::playSound(Application* app) const { void Notification::playSound(Application* app) const {
QSound::play(QDir::toNativeSeparators(app->replaceDataUserDataFolderPlaceholder(m_soundPath))); QSound::play(QDir::toNativeSeparators(app->replaceDataUserDataFolderPlaceholder(m_soundPath)));
} }
QList<Notification::Event> Notification::allEvents() {
return {
Event::NewArticlesFetched,
Event::ArticlesFetchingStarted,
Event::LoginDataRefreshed
};
}
QString Notification::nameForEvent(Notification::Event event) {
switch (event) {
case Notification::Event::NewArticlesFetched:
return QObject::tr("New articles fetched");
case Notification::Event::ArticlesFetchingStarted:
return QObject::tr("Fetching articles right now");
case Notification::Event::LoginDataRefreshed:
return QObject::tr("Login data refreshed");
default:
return QObject::tr("Unknown event");
}
}

View file

@ -10,16 +10,20 @@ class Application;
class Notification { class Notification {
public: public:
enum class Event { enum class Event {
UnknownEvent = 0,
// New (unread) messages were downloaded for some feed. // New (unread) messages were downloaded for some feed.
NewMessagesDownloaded = 1, NewArticlesFetched = 1,
// RSS Guard started downloading messages for some feed. // RSS Guard started downloading messages for some feed.
MesssagesDownloadStarted = 2, ArticlesFetchingStarted = 2,
// Login tokens were successfuly refreshed. // Login tokens were successfuly refreshed.
// NOTE: This is primarily used in accounts which use // NOTE: This is primarily used in accounts which use
// OAuth or similar mechanism. // OAuth or similar mechanism.
LoginDataRefreshed = 4 LoginDataRefreshed = 4,
// TODO: app update is available
}; };
explicit Notification(); explicit Notification();
@ -36,6 +40,9 @@ class Notification {
void playSound(Application* app) const; void playSound(Application* app) const;
static QList<Event> allEvents();
static QString nameForEvent(Event event);
private: private:
Event m_event; Event m_event;
QString m_soundPath; QString m_soundPath;

View file

@ -7,8 +7,14 @@
#include "exceptions/applicationexception.h" #include "exceptions/applicationexception.h"
#include "miscellaneous/settings.h" #include "miscellaneous/settings.h"
#include <QRegularExpression>
NotificationFactory::NotificationFactory(QObject* parent) : QObject(parent) {} NotificationFactory::NotificationFactory(QObject* parent) : QObject(parent) {}
QList<Notification> NotificationFactory::allNotifications() const {
return m_notifications;
}
Notification NotificationFactory::notificationForEvent(Notification::Event event) const { Notification NotificationFactory::notificationForEvent(Notification::Event event) const {
auto good_n = boolinq::from(m_notifications).where([event](const Notification& n) { auto good_n = boolinq::from(m_notifications).where([event](const Notification& n) {
return n.event() == event; return n.event() == event;
@ -23,15 +29,23 @@ Notification NotificationFactory::notificationForEvent(Notification::Event event
} }
void NotificationFactory::load(Settings* settings) { void NotificationFactory::load(Settings* settings) {
//settings->allKeys(Notifications::ID) auto notif_keys = settings->allKeys(GROUP(Notifications)).filter(QRegularExpression(QSL("^\\d+$")));
m_notifications = { m_notifications.clear();
Notification()
}; for (const auto& key : notif_keys) {
auto event = Notification::Event(key.toInt());
auto sound = settings->value(GROUP(Notifications), key).toString();
m_notifications.append(Notification(event, sound));
}
} }
void NotificationFactory::save(const QList<Notification>& new_notifications, Settings* settings) { void NotificationFactory::save(const QList<Notification>& new_notifications, Settings* settings) {
settings->remove(GROUP(Notifications));
m_notifications = new_notifications; m_notifications = new_notifications;
// for (const auto& n : qAsConst(m_notifications)) {
settings->setValue(GROUP(Notifications), QString::number(int(n.event())), n.soundPath());
}
} }

View file

@ -15,6 +15,7 @@ class NotificationFactory : public QObject {
public: public:
explicit NotificationFactory(QObject* parent = nullptr); explicit NotificationFactory(QObject* parent = nullptr);
QList<Notification> allNotifications() const;
Notification notificationForEvent(Notification::Event event) const; Notification notificationForEvent(Notification::Event event) const;
public slots: public slots:

View file

@ -432,7 +432,7 @@ class Settings : public QSettings {
void setValue(const QString& key, const QVariant& value); void setValue(const QString& key, const QVariant& value);
bool contains(const QString& section, const QString& key) const; bool contains(const QString& section, const QString& key) const;
void remove(const QString& section, const QString& key); void remove(const QString& section, const QString& key = {});
// Returns the path which contains the settings. // Returns the path which contains the settings.
QString pathName() const; QString pathName() const;
@ -487,7 +487,14 @@ inline bool Settings::contains(const QString& section, const QString& key) const
} }
inline void Settings::remove(const QString& section, const QString& key) { inline void Settings::remove(const QString& section, const QString& key) {
if (key.isEmpty()) {
beginGroup(section);
QSettings::remove({});
endGroup();
}
else {
QSettings::remove(QString(QSL("%1/%2")).arg(section, key)); QSettings::remove(QString(QSL("%1/%2")).arg(section, key));
}
} }
#endif // SETTINGS_H #endif // SETTINGS_H