From 4b627ab25b42b7ead0a919d282b4447a25d37b9b Mon Sep 17 00:00:00 2001 From: reionwong Date: Sat, 9 Oct 2021 10:47:45 +0800 Subject: [PATCH] Add notification window --- notificationd/CMakeLists.txt | 18 +- notificationd/application.cpp | 118 ++++++++++ notificationd/application.h | 47 ++++ notificationd/com.cutefish.Notification.xml | 6 + notificationd/historymodel.cpp | 130 +++++++++++ notificationd/historymodel.h | 42 ++++ notificationd/images/dark/clear.svg | 18 ++ notificationd/images/light/clear.svg | 13 ++ notificationd/main.cpp | 19 +- notificationd/notification.cpp | 18 ++ notificationd/notification.h | 15 ++ notificationd/notificationsmodel.cpp | 24 +- notificationd/notificationsmodel.h | 3 + notificationd/notificationwindow.cpp | 53 +++++ notificationd/notificationwindow.h | 18 ++ notificationd/qml/IconButton.qml | 79 +++++++ notificationd/qml/NotificationPopup.qml | 2 +- notificationd/qml/NotificationWindow.qml | 236 ++++++++++++++++++++ notificationd/qml/main.qml | 4 - notificationd/resources.qrc | 4 + notificationd/translations/en_US.ts | 17 ++ notificationd/translations/zh_CN.ts | 17 ++ notificationd/utils.cpp | 19 ++ notificationd/utils.h | 19 ++ 24 files changed, 918 insertions(+), 21 deletions(-) create mode 100644 notificationd/application.cpp create mode 100644 notificationd/application.h create mode 100644 notificationd/com.cutefish.Notification.xml create mode 100644 notificationd/historymodel.cpp create mode 100644 notificationd/historymodel.h create mode 100644 notificationd/images/dark/clear.svg create mode 100644 notificationd/images/light/clear.svg create mode 100644 notificationd/notificationwindow.cpp create mode 100644 notificationd/notificationwindow.h create mode 100644 notificationd/qml/IconButton.qml create mode 100644 notificationd/qml/NotificationWindow.qml create mode 100644 notificationd/translations/en_US.ts create mode 100644 notificationd/translations/zh_CN.ts diff --git a/notificationd/CMakeLists.txt b/notificationd/CMakeLists.txt index 25e8c4a..9f055a1 100644 --- a/notificationd/CMakeLists.txt +++ b/notificationd/CMakeLists.txt @@ -1,8 +1,11 @@ set(SRCS main.cpp + application.cpp screenhelper.cpp notificationsmodel.cpp notificationserver.cpp notification.cpp + notificationwindow.cpp + historymodel.cpp utils.cpp dbus/notificationsadaptor.cpp resources.qrc @@ -11,16 +14,29 @@ set(SRCS main.cpp # for Ubuntu # qt_add_dbus_adaptor(SRCS org.freedesktop.Notifications.xml notificationserver.h NotificationServer) -add_executable(cutefish-notificationd ${SRCS}) +qt5_add_dbus_adaptor(DBUS_SOURCES com.cutefish.Notification.xml application.h Application) +set_source_files_properties(${DBUS_SOURCES} PROPERTIES SKIP_AUTOGEN ON) + +find_package(KF5WindowSystem) + +add_executable(cutefish-notificationd ${SRCS} ${DBUS_SOURCES}) target_link_libraries(cutefish-notificationd Qt5::Core Qt5::DBus Qt5::Quick Qt5::Widgets + KF5::WindowSystem ) install(TARGETS cutefish-notificationd DESTINATION /usr/bin COMPONENT Runtime ) + +file(GLOB TS_FILES translations/*.ts) +qt5_create_translation(QM_FILES ${TS_FILES}) +add_custom_target(notificationd_translations DEPENDS ${QM_FILES} SOURCES ${TS_FILES}) +add_dependencies(cutefish-notificationd notificationd_translations) + +install(FILES ${QM_FILES} DESTINATION /usr/share/cutefish-notificationd/translations) diff --git a/notificationd/application.cpp b/notificationd/application.cpp new file mode 100644 index 0000000..c2e26c4 --- /dev/null +++ b/notificationd/application.cpp @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2021 CutefishOS Team. + * + * Author: Reion Wong + * + * 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 + * 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 . + */ + +#include "application.h" +#include "notificationsmodel.h" +#include "screenhelper.h" +#include "notificationadaptor.h" +#include "historymodel.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +Application::Application(int& argc, char** argv) + : QApplication(argc, argv) + , m_notificationServer(NotificationServer::self()) + , m_model(NotificationsModel::self()) + , m_window(nullptr) + , m_instance(false) +{ + if (QDBusConnection::sessionBus().registerService("com.cutefish.Notification")) { + setOrganizationName("cutefishos"); + + // Translations + QLocale locale; + QString qmFilePath = QString("%1/%2.qm").arg("/usr/share/cutefish-notificationd/translations/").arg(locale.name()); + if (QFile::exists(qmFilePath)) { + QTranslator *translator = new QTranslator(this); + if (translator->load(qmFilePath)) { + installTranslator(translator); + } else { + translator->deleteLater(); + } + } + + new NotificationAdaptor(this); + QDBusConnection::sessionBus().registerObject("/Notification", this); + + qmlRegisterType("Cutefish.Notification", 1, 0, "NotificationsModel"); + qmlRegisterType("Cutefish.Notification", 1, 0, "HistoryModel"); + qmlRegisterType("Cutefish.Notification", 1, 0, "ScreenHelper"); + + m_instance = true; + } +} + +void Application::showWindow() +{ + if (m_window) + m_window->open(); +} + +int Application::run() +{ + if (!parseCommandLineArgs()) + return 0; + + QQmlApplicationEngine engine; + engine.rootContext()->setContextProperty("notificationsModel", m_model); + engine.load(QUrl("qrc:/qml/main.qml")); + + m_window = new NotificationWindow; + + return QApplication::exec(); +} + +bool Application::parseCommandLineArgs() +{ + QCommandLineParser parser; + parser.setApplicationDescription(QStringLiteral("Notification")); + parser.addHelpOption(); + + QCommandLineOption showOption(QStringList() << "s" << "show" << "Show dialog"); + parser.addOption(showOption); + + parser.process(arguments()); + + if (m_instance) { + QPixmapCache::setCacheLimit(2048); + } else { + QDBusInterface iface("com.cutefish.Notification", + "/Notification", + "com.cutefish.Notification", + QDBusConnection::sessionBus(), this); + if (iface.isValid() && parser.isSet(showOption)) { + iface.call("showWindow"); + } + } + + return m_instance; +} diff --git a/notificationd/application.h b/notificationd/application.h new file mode 100644 index 0000000..af2fc6e --- /dev/null +++ b/notificationd/application.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2021 CutefishOS Team. + * + * Author: Reion Wong + * + * 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 + * 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 . + */ + +#ifndef APPLICATION_H +#define APPLICATION_H + +#include +#include "notificationwindow.h" + +class NotificationServer; +class NotificationsModel; +class Application : public QApplication +{ + Q_OBJECT + +public: + explicit Application(int& argc, char** argv); + + void showWindow(); + + int run(); + bool parseCommandLineArgs(); + +private: + NotificationServer *m_notificationServer; + NotificationsModel *m_model; + NotificationWindow *m_window; + bool m_instance; +}; + +#endif // APPLICATION_H diff --git a/notificationd/com.cutefish.Notification.xml b/notificationd/com.cutefish.Notification.xml new file mode 100644 index 0000000..9dabaab --- /dev/null +++ b/notificationd/com.cutefish.Notification.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/notificationd/historymodel.cpp b/notificationd/historymodel.cpp new file mode 100644 index 0000000..78a694c --- /dev/null +++ b/notificationd/historymodel.cpp @@ -0,0 +1,130 @@ +#include "historymodel.h" + +#include +#include +#include + +static HistoryModel *s_historyModel = nullptr; + +HistoryModel *HistoryModel::self() +{ + if (!s_historyModel) + s_historyModel = new HistoryModel; + + return s_historyModel; +} + +HistoryModel::HistoryModel(QObject *parent) + : QAbstractListModel(parent) +{ + initDatas(); +} + +QVariant HistoryModel::data(const QModelIndex &index, int role) const +{ + const Notification ¬ification = m_notifications.at(index.row()); + + switch (role) { + case HistoryModel::IdRole: + return notification.id; + case HistoryModel::SummaryRole: + return notification.summary; + case HistoryModel::ImageRole: + return ""; + case HistoryModel::CreatedRole: + return notification.created; + case HistoryModel::BodyRole: + return notification.body; + case HistoryModel::IconNameRole: + return notification.appIcon; + case HistoryModel::HasDefaultActionRole: + return notification.actions.contains("default"); + default: + break; + } + + return QVariant(); +} + +int HistoryModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + + return m_notifications.size(); +} + +QHash HistoryModel::roleNames() const +{ + static QHash s_roles; + + if (s_roles.isEmpty()) { + // This generates role names from the Roles enum in the form of: FooRole -> foo + const QMetaEnum e = QMetaEnum::fromType(); + + // Qt built-in roles we use + s_roles.insert(Qt::DisplayRole, QByteArrayLiteral("display")); + s_roles.insert(Qt::DecorationRole, QByteArrayLiteral("decoration")); + + for (int i = 0; i < e.keyCount(); ++i) { + const int value = e.value(i); + + QByteArray key(e.key(i)); + key[0] = key[0] + 32; // lower case first letter + key.chop(4); // strip "Role" suffix + + s_roles.insert(value, key); + } + + s_roles.insert(HistoryModel::IdRole, QByteArrayLiteral("notificationId")); // id is QML-reserved + } + + return s_roles; +} + +void HistoryModel::add(const Notification ¬ification) +{ + beginInsertRows(QModelIndex(), m_notifications.size(), m_notifications.size()); + m_notifications.append(std::move(notification)); + endInsertRows(); + save(); +} + +void HistoryModel::remove(int index) +{ + if (index > m_notifications.size() && index < 0) + return; + + beginRemoveRows(QModelIndex(), index, index); + m_notifications.removeAt(index); + endRemoveRows(); + save(); +} + +void HistoryModel::clearAll() +{ + beginResetModel(); + m_notifications.clear(); + endResetModel(); + save(); +} + +void HistoryModel::save() +{ + QSettings settings(QSettings::UserScope, "cutefishos", "notifications"); + settings.clear(); + + QByteArray datas; + QDataStream out(&datas, QIODevice::WriteOnly); + + out << m_notifications; + + settings.setValue("datas", datas); +} + +void HistoryModel::initDatas() +{ + QSettings settings(QSettings::UserScope, "cutefishos", "notifications"); + QByteArray listByteArray = settings.value("datas").toByteArray(); + QDataStream in(&listByteArray, QIODevice::ReadOnly); + in >> m_notifications; +} diff --git a/notificationd/historymodel.h b/notificationd/historymodel.h new file mode 100644 index 0000000..8dba4c9 --- /dev/null +++ b/notificationd/historymodel.h @@ -0,0 +1,42 @@ +#ifndef HISTORYMODEL_H +#define HISTORYMODEL_H + +#include +#include "notification.h" + +class HistoryModel : public QAbstractListModel +{ + Q_OBJECT + +public: + enum Roles { + IdRole = Qt::UserRole + 1, + SummaryRole = Qt::DisplayRole, + ImageRole = Qt::DecorationRole, + CreatedRole, + UpdatedRole, + BodyRole, + IconNameRole, + HasDefaultActionRole + }; + Q_ENUM(Roles) + + static HistoryModel* self(); + explicit HistoryModel(QObject *parent = nullptr); + + QVariant data(const QModelIndex &index, int role) const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QHash roleNames() const override; + + Q_INVOKABLE void add(const Notification ¬ification); + Q_INVOKABLE void remove(int index); + Q_INVOKABLE void clearAll(); + Q_INVOKABLE void save(); + + void initDatas(); + +private: + QVector m_notifications; +}; + +#endif // HISTORYMODEL_H diff --git a/notificationd/images/dark/clear.svg b/notificationd/images/dark/clear.svg new file mode 100644 index 0000000..4d66103 --- /dev/null +++ b/notificationd/images/dark/clear.svg @@ -0,0 +1,18 @@ + + + + + + image/svg+xml + + + + + + + + + + diff --git a/notificationd/images/light/clear.svg b/notificationd/images/light/clear.svg new file mode 100644 index 0000000..91fa997 --- /dev/null +++ b/notificationd/images/light/clear.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/notificationd/main.cpp b/notificationd/main.cpp index dc13c61..c49f13f 100644 --- a/notificationd/main.cpp +++ b/notificationd/main.cpp @@ -17,26 +17,17 @@ * along with this program. If not, see . */ -#include -#include -#include +#include "application.h" -#include "view.h" +#include #include "notificationsmodel.h" #include "screenhelper.h" int main(int argc, char *argv[]) { // QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps, true); - QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling, true); - - QApplication a(argc, argv); - - qmlRegisterType("Cutefish.Notification", 1, 0, "NotificationsModel"); - qmlRegisterType("Cutefish.Notification", 1, 0, "ScreenHelper"); - - QQmlApplicationEngine engine; - engine.load(QUrl("qrc:/qml/main.qml")); + // QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling, true); - return a.exec(); + Application a(argc, argv); + return a.run(); } diff --git a/notificationd/notification.cpp b/notificationd/notification.cpp index 37c8f91..17e7146 100644 --- a/notificationd/notification.cpp +++ b/notificationd/notification.cpp @@ -18,3 +18,21 @@ */ #include "notification.h" + +QDataStream &operator<<(QDataStream &argument, const Notification &info) +{ + argument << info.service << info.summary + << info.body << info.appName << info.appIcon + << info.created << info.updated; + + return argument; +} + +const QDataStream &operator>>(QDataStream &argument, Notification &info) +{ + argument >> info.service >> info.summary; + argument >> info.body >> info.appName >> info.appIcon; + argument >> info.created >> info.updated; + + return argument; +} diff --git a/notificationd/notification.h b/notificationd/notification.h index 53d89e7..582626f 100644 --- a/notificationd/notification.h +++ b/notificationd/notification.h @@ -22,6 +22,7 @@ #include #include +#include class Notification { @@ -37,6 +38,20 @@ public: QDateTime created; QDateTime updated; + + inline bool operator==(const Notification &other) const { + return service == other.service && + summary == other.summary && + body == other.body && + appName == other.appName && + appIcon == other.appIcon && + created == other.created && + updated == other.updated; + } + friend QDataStream &operator<<(QDataStream &argument, const Notification &info); + friend const QDataStream &operator>>(QDataStream &argument, Notification &info); }; +Q_DECLARE_METATYPE(Notification) + #endif // NOTIFICATION_H diff --git a/notificationd/notificationsmodel.cpp b/notificationd/notificationsmodel.cpp index 24a7a10..c7597e5 100644 --- a/notificationd/notificationsmodel.cpp +++ b/notificationd/notificationsmodel.cpp @@ -5,12 +5,22 @@ */ #include "notificationsmodel.h" +#include "historymodel.h" #include "notification.h" #include #include static const int s_notificationsLimit = 1000; +static NotificationsModel *NOTIFICATIONS_MODEL = nullptr; + +NotificationsModel *NotificationsModel::self() +{ + if (!NOTIFICATIONS_MODEL) + NOTIFICATIONS_MODEL = new NotificationsModel; + + return NOTIFICATIONS_MODEL; +} NotificationsModel::NotificationsModel(QObject *parent) : QAbstractListModel(parent) @@ -106,6 +116,15 @@ QHash NotificationsModel::roleNames() const return s_roles; } +void NotificationsModel::expired(uint id) +{ + int row = rowOfNotification(id); + + if (row > -1) { + onNotificationRemoved(id, NotificationServer::CloseReason::Expired); + } +} + void NotificationsModel::close(uint id) { if (rowOfNotification(id) > -1) { @@ -221,7 +240,10 @@ void NotificationsModel::onNotificationRemoved(uint removedId, NotificationServe // notification.setExpired(true); notification.actions.clear(); emit dataChanged(idx, idx); - return; + + HistoryModel::self()->add(notification); + + // return; } // Otherwise if explicitly closed by either user or app, mark it for removal diff --git a/notificationd/notificationsmodel.h b/notificationd/notificationsmodel.h index 58e2a25..bc18140 100644 --- a/notificationd/notificationsmodel.h +++ b/notificationd/notificationsmodel.h @@ -31,6 +31,7 @@ public: }; Q_ENUM(Roles) + static NotificationsModel *self(); explicit NotificationsModel(QObject *parent = nullptr); QVariant data(const QModelIndex &index, int role) const override; @@ -38,6 +39,8 @@ public: int rowCount(const QModelIndex &parent = QModelIndex()) const override; QHash roleNames() const override; + Q_INVOKABLE void expired(uint id); + Q_INVOKABLE void close(uint id); Q_INVOKABLE void invokeDefaultAction(uint id); diff --git a/notificationd/notificationwindow.cpp b/notificationd/notificationwindow.cpp new file mode 100644 index 0000000..95e9e6a --- /dev/null +++ b/notificationd/notificationwindow.cpp @@ -0,0 +1,53 @@ +#include "notificationwindow.h" +#include "notificationsmodel.h" +#include "historymodel.h" + +#include + +#include +#include + +NotificationWindow::NotificationWindow(QQuickView *parent) + : QQuickView(parent) +{ + installEventFilter(this); + + setFlags(Qt::Popup); + setResizeMode(QQuickView::SizeRootObjectToView); + setColor(Qt::transparent); + + rootContext()->setContextProperty("NotificationDialog", this); + rootContext()->setContextProperty("notificationsModel", NotificationsModel::self()); + rootContext()->setContextProperty("historyModel", HistoryModel::self()); + + // KWindowEffects::slideWindow(winId(), KWindowEffects::RightEdge); + setSource(QUrl("qrc:/qml/NotificationWindow.qml")); + setVisible(false); +} + +void NotificationWindow::open() +{ + setVisible(true); + setMouseGrabEnabled(true); + setKeyboardGrabEnabled(true); +} + +bool NotificationWindow::eventFilter(QObject *object, QEvent *event) +{ + if (event->type() == QEvent::MouseButtonPress) { + if (QWindow *w = qobject_cast(object)) { + if (!w->geometry().contains(static_cast(event)->globalPos())) { + QQuickView::setVisible(false); + } + } + } else if (event->type() == QEvent::KeyPress) { + QKeyEvent *keyEvent = static_cast(event); + if (keyEvent->key() == Qt::Key_Escape) { + QQuickView::setVisible(false); + } + } else if (event->type() == QEvent::Show) { + KWindowSystem::setState(winId(), NET::SkipTaskbar | NET::SkipPager | NET::SkipSwitcher); + } + + return QObject::eventFilter(object, event); +} diff --git a/notificationd/notificationwindow.h b/notificationd/notificationwindow.h new file mode 100644 index 0000000..f0380c1 --- /dev/null +++ b/notificationd/notificationwindow.h @@ -0,0 +1,18 @@ +#ifndef NOTIFICATIONWINDOW_H +#define NOTIFICATIONWINDOW_H + +#include + +class NotificationWindow : public QQuickView +{ + Q_OBJECT + +public: + explicit NotificationWindow(QQuickView *parent = nullptr); + + void open(); + + bool eventFilter(QObject *object, QEvent *event) override; +}; + +#endif // NOTIFICATIONWINDOW_H diff --git a/notificationd/qml/IconButton.qml b/notificationd/qml/IconButton.qml new file mode 100644 index 0000000..fa47fbc --- /dev/null +++ b/notificationd/qml/IconButton.qml @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2021 CutefishOS Team. + * + * Author: Reion Wong + * + * 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 + * 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 . + */ + +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 +import FishUI 1.0 as FishUI + +Item { + id: control + + property url source + property real size: 30 + property string popupText + + signal leftButtonClicked + signal rightButtonClicked + + MouseArea { + id: mouseArea + anchors.fill: parent + acceptedButtons: Qt.LeftButton | Qt.RightButton + hoverEnabled: control.visible ? true : false + + onClicked: { + if (mouse.button === Qt.LeftButton) + control.leftButtonClicked() + else if (mouse.button === Qt.RightButton) + control.rightButtonClicked() + } + } + + Rectangle { + anchors.fill: parent + anchors.margins: 1 + // radius: parent.height * 0.2 + radius: parent.height / 2 + + color: { + if (mouseArea.containsMouse) { + if (mouseArea.containsPress) + return (FishUI.Theme.darkMode) ? Qt.rgba(255, 255, 255, 0.3) : Qt.rgba(0, 0, 0, 0.2) + else + return (FishUI.Theme.darkMode) ? Qt.rgba(255, 255, 255, 0.2) : Qt.rgba(0, 0, 0, 0.1) + } + + return "transparent" + } + } + + Image { + id: iconImage + anchors.centerIn: parent + width: parent.height * 0.8 + height: width + sourceSize.width: width + sourceSize.height: height + source: control.source + asynchronous: true + antialiasing: true + smooth: false + } +} diff --git a/notificationd/qml/NotificationPopup.qml b/notificationd/qml/NotificationPopup.qml index ad7dfa3..b25e31d 100644 --- a/notificationd/qml/NotificationPopup.qml +++ b/notificationd/qml/NotificationPopup.qml @@ -166,7 +166,7 @@ Window { interval: control.defaultTimeout onTriggered: { - notificationsModel.close(model.notificationId) + notificationsModel.expired(model.notificationId) } } } diff --git a/notificationd/qml/NotificationWindow.qml b/notificationd/qml/NotificationWindow.qml new file mode 100644 index 0000000..c4b80b5 --- /dev/null +++ b/notificationd/qml/NotificationWindow.qml @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2021 CutefishOS Team. + * + * Author: Reion Wong + * + * 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 + * 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 . + */ + +import QtQuick 2.12 +import QtQml 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Window 2.12 +import QtGraphicalEffects 1.0 +import FishUI 1.0 as FishUI +import Cutefish.Notification 1.0 + +Item { + id: control + + visible: true + + Rectangle { + id: _background + anchors.fill: parent + color: FishUI.Theme.secondBackgroundColor + radius: NotificationDialog.width * 0.05 //FishUI.Theme.bigRadius + opacity: 0.7 + + border.width: 1 + border.color: FishUI.Theme.darkMode ? Qt.rgba(255, 255, 255, 0.1) + : Qt.rgba(0, 0, 0, 0.05) + } + + readonly property rect screenRect: { + let rect = Qt.rect(screen.screenGeometry.x + screen.availableScreenRect.x, + screen.screenGeometry.y + screen.availableScreenRect.y, + screen.availableScreenRect.width, + screen.availableScreenRect.height) + return rect + } + + onScreenRectChanged: { + NotificationDialog.width = 350 + NotificationDialog.height = screenRect.height - FishUI.Units.smallSpacing * 3 + NotificationDialog.x = screenRect.x + screenRect.width - NotificationDialog.width - FishUI.Units.smallSpacing * 1.5 + NotificationDialog.y = screenRect.y + FishUI.Units.smallSpacing * 1.5 + } + + ScreenHelper { + id: screen + } + + FishUI.WindowHelper { + id: windowHelper + } + + FishUI.WindowShadow { + view: NotificationDialog + radius: _background.radius + } + + FishUI.WindowBlur { + view: NotificationDialog + geometry: Qt.rect(NotificationDialog.x, + NotificationDialog.y, + NotificationDialog.width, + NotificationDialog.height) + windowRadius: _background.radius + enabled: true + } + + ColumnLayout { + anchors.fill: parent + anchors.margins: FishUI.Units.largeSpacing + spacing: FishUI.Units.largeSpacing + + RowLayout { + Label { + text: qsTr("Notification Center") + Layout.fillWidth: true + elide: Text.ElideRight + leftPadding: FishUI.Units.smallSpacing + color: FishUI.Theme.textColor + font.pointSize: 15 + } + + IconButton { + visible: _view.count > 0 + Layout.preferredHeight: 30 + Layout.preferredWidth: 30 + source: FishUI.Theme.darkMode ? "qrc:/images/dark/clear.svg" + : "qrc:/images/light/clear.svg" + onLeftButtonClicked: historyModel.clearAll() + } + } + + Item { + Layout.fillWidth: true + Layout.fillHeight: true + + ListView { + id: _view + anchors.fill: parent + model: historyModel + spacing: FishUI.Units.largeSpacing + highlightFollowsCurrentItem: true + clip: true + + Label { + anchors.centerIn: parent + text: qsTr("No notifications") + color: FishUI.Theme.disabledTextColor + font.pointSize: 15 + visible: _view.count === 0 + } + + addDisplaced: Transition { + NumberAnimation { properties: "x, y"; duration: 300 } + } + + removeDisplaced: Transition { + NumberAnimation { properties: "x, y"; duration: 300 } + } + + delegate: Item { + width: ListView.view.width + height: 70 + + Rectangle { + anchors.fill: parent + color: FishUI.Theme.darkMode ? "white" + : "black" + radius: FishUI.Theme.bigRadius + opacity: FishUI.Theme.darkMode ? 0.1 + : 0.03 + } + + RowLayout { + anchors.fill: parent + anchors.margins: FishUI.Units.smallSpacing + spacing: FishUI.Units.smallSpacing + + Image { + id: _icon + width: 48 + height: width + source: "image://icontheme/%1".arg(model.iconName) + sourceSize: Qt.size(width, height) + Layout.alignment: Qt.AlignVCenter + antialiasing: true + smooth: true + } + + ColumnLayout { + spacing: 0 + + Item { + Layout.fillHeight: true + } + + Label { + text: model.summary + visible: text + elide: Text.ElideRight + Layout.fillWidth: true + rightPadding: FishUI.Units.smallSpacing + } + + Label { + text: model.body + visible: text + rightPadding: FishUI.Units.smallSpacing + maximumLineCount: 2 + elide: Text.ElideRight + wrapMode: Text.Wrap + Layout.fillWidth: true + // Layout.fillHeight: true + Layout.alignment: Qt.AlignVCenter + } + + Item { + Layout.fillHeight: true + } + } + } + + Image { + anchors.top: parent.top + anchors.right: parent.right + anchors.topMargin: FishUI.Units.smallSpacing / 2 + anchors.rightMargin: FishUI.Units.smallSpacing / 2 + width: 24 + height: 24 + source: "qrc:/images/" + (FishUI.Theme.darkMode ? "dark" : "light") + "/close.svg" + sourceSize: Qt.size(width, height) + visible: true + z: 9999 + + Rectangle { + property color hoveredColor: FishUI.Theme.darkMode ? Qt.lighter(FishUI.Theme.backgroundColor, 2) + : Qt.darker(FishUI.Theme.backgroundColor, 1.2) + property color pressedColor: FishUI.Theme.darkMode ? Qt.lighter(FishUI.Theme.backgroundColor, 1.5) + : Qt.darker(FishUI.Theme.backgroundColor, 1.3) + + z: -1 + anchors.fill: parent + color: _closeBtnArea.pressed ? pressedColor : _closeBtnArea.containsMouse ? hoveredColor : "transparent" + radius: height / 2 + } + + MouseArea { + id: _closeBtnArea + anchors.fill: parent + hoverEnabled: true + onClicked: { + historyModel.remove(index) + } + } + } + } + } + } + } +} diff --git a/notificationd/qml/main.qml b/notificationd/qml/main.qml index d048894..d1c1389 100644 --- a/notificationd/qml/main.qml +++ b/notificationd/qml/main.qml @@ -51,10 +51,6 @@ Item { id: screen } - NotificationsModel { - id: notificationsModel - } - function positionPopups() { const screenRect = root.screenRect diff --git a/notificationd/resources.qrc b/notificationd/resources.qrc index 14901a9..23edb7d 100644 --- a/notificationd/resources.qrc +++ b/notificationd/resources.qrc @@ -4,5 +4,9 @@ qml/NotificationPopup.qml images/dark/close.svg images/light/close.svg + qml/NotificationWindow.qml + images/dark/clear.svg + images/light/clear.svg + qml/IconButton.qml diff --git a/notificationd/translations/en_US.ts b/notificationd/translations/en_US.ts new file mode 100644 index 0000000..05e633e --- /dev/null +++ b/notificationd/translations/en_US.ts @@ -0,0 +1,17 @@ + + + + + NotificationWindow + + + Notification Center + + + + + No notifications + + + + diff --git a/notificationd/translations/zh_CN.ts b/notificationd/translations/zh_CN.ts new file mode 100644 index 0000000..da0f268 --- /dev/null +++ b/notificationd/translations/zh_CN.ts @@ -0,0 +1,17 @@ + + + + + NotificationWindow + + + Notification Center + 通知中心 + + + + No notifications + 无通知 + + + diff --git a/notificationd/utils.cpp b/notificationd/utils.cpp index af10f09..eb3d137 100644 --- a/notificationd/utils.cpp +++ b/notificationd/utils.cpp @@ -1,3 +1,22 @@ +/* + * Copyright (C) 2021 CutefishOS Team. + * + * Author: Reion Wong + * + * 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 + * 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 . + */ + #include "utils.h" #include diff --git a/notificationd/utils.h b/notificationd/utils.h index d7ad4f0..1707a44 100644 --- a/notificationd/utils.h +++ b/notificationd/utils.h @@ -1,3 +1,22 @@ +/* + * Copyright (C) 2021 CutefishOS Team. + * + * Author: Reion Wong + * + * 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 + * 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 . + */ + #ifndef UTILS_H #define UTILS_H