Add notifications view
This commit is contained in:
@@ -139,6 +139,8 @@ add_library(neochat STATIC
|
|||||||
chatbarcache.h
|
chatbarcache.h
|
||||||
colorschemer.cpp
|
colorschemer.cpp
|
||||||
colorschemer.h
|
colorschemer.h
|
||||||
|
models/notificationsmodel.cpp
|
||||||
|
models/notificationsmodel.h
|
||||||
)
|
)
|
||||||
|
|
||||||
qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
|
qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
|
||||||
@@ -290,6 +292,7 @@ qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
|
|||||||
qml/QrCodeMaximizeComponent.qml
|
qml/QrCodeMaximizeComponent.qml
|
||||||
qml/SelectSpacesDialog.qml
|
qml/SelectSpacesDialog.qml
|
||||||
qml/AttachDialog.qml
|
qml/AttachDialog.qml
|
||||||
|
qml/NotificationsView.qml
|
||||||
RESOURCES
|
RESOURCES
|
||||||
qml/confetti.png
|
qml/confetti.png
|
||||||
qml/glowdot.png
|
qml/glowdot.png
|
||||||
|
|||||||
161
src/models/notificationsmodel.cpp
Normal file
161
src/models/notificationsmodel.cpp
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
|
||||||
|
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "notificationsmodel.h"
|
||||||
|
|
||||||
|
#include <Quotient/connection.h>
|
||||||
|
#include <Quotient/events/event.h>
|
||||||
|
|
||||||
|
#include "eventhandler.h"
|
||||||
|
#include "neochatroom.h"
|
||||||
|
|
||||||
|
using namespace Quotient;
|
||||||
|
|
||||||
|
NotificationsModel::NotificationsModel(QObject *parent)
|
||||||
|
: QAbstractListModel(parent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
int NotificationsModel::rowCount(const QModelIndex &parent) const
|
||||||
|
{
|
||||||
|
Q_UNUSED(parent)
|
||||||
|
return m_notifications.count();
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant NotificationsModel::data(const QModelIndex &index, int role) const
|
||||||
|
{
|
||||||
|
auto row = index.row();
|
||||||
|
if (row < 0 || row >= m_notifications.count()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
if (role == TextRole) {
|
||||||
|
return m_notifications[row].text;
|
||||||
|
}
|
||||||
|
if (role == RoomIdRole) {
|
||||||
|
return m_notifications[row].roomId;
|
||||||
|
}
|
||||||
|
if (role == AuthorName) {
|
||||||
|
return m_notifications[row].authorName;
|
||||||
|
}
|
||||||
|
if (role == AuthorAvatar) {
|
||||||
|
return m_notifications[row].authorAvatar;
|
||||||
|
}
|
||||||
|
if (role == RoomRole) {
|
||||||
|
return QVariant::fromValue(m_connection->room(m_notifications[row].roomId));
|
||||||
|
}
|
||||||
|
if (role == EventIdRole) {
|
||||||
|
return m_notifications[row].eventId;
|
||||||
|
}
|
||||||
|
if (role == RoomDisplayNameRole) {
|
||||||
|
return m_notifications[row].roomDisplayName;
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
QHash<int, QByteArray> NotificationsModel::roleNames() const
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
{TextRole, "text"},
|
||||||
|
{RoomIdRole, "roomId"},
|
||||||
|
{AuthorName, "authorName"},
|
||||||
|
{AuthorAvatar, "authorAvatar"},
|
||||||
|
{RoomRole, "room"},
|
||||||
|
{EventIdRole, "eventId"},
|
||||||
|
{RoomDisplayNameRole, "roomDisplayName"},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
NeoChatConnection *NotificationsModel::connection() const
|
||||||
|
{
|
||||||
|
return m_connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NotificationsModel::setConnection(NeoChatConnection *connection)
|
||||||
|
{
|
||||||
|
if (m_connection) {
|
||||||
|
// disconnect things...
|
||||||
|
}
|
||||||
|
if (!connection) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_connection = connection;
|
||||||
|
Q_EMIT connectionChanged();
|
||||||
|
connect(connection, &Connection::syncDone, this, [=]() {
|
||||||
|
loadData();
|
||||||
|
});
|
||||||
|
loadData();
|
||||||
|
}
|
||||||
|
|
||||||
|
void NotificationsModel::loadData()
|
||||||
|
{
|
||||||
|
Q_ASSERT(m_connection);
|
||||||
|
if (m_job || (m_notifications.size() && m_nextToken.isEmpty())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_job = m_connection->callApi<GetNotificationsJob>(m_nextToken);
|
||||||
|
Q_EMIT loadingChanged();
|
||||||
|
connect(m_job, &BaseJob::finished, this, [this]() {
|
||||||
|
m_nextToken = m_job->nextToken();
|
||||||
|
Q_EMIT nextTokenChanged();
|
||||||
|
for (const auto ¬ification : m_job->notifications()) {
|
||||||
|
if (std::any_of(notification.actions.constBegin(), notification.actions.constEnd(), [](const QVariant &it) {
|
||||||
|
if (it.canConvert<QVariantMap>()) {
|
||||||
|
auto map = it.toMap();
|
||||||
|
if (map["set_tweak"_ls] == "highlight"_ls) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
})) {
|
||||||
|
const auto &authorId = notification.event->fullJson()["sender"_ls].toString();
|
||||||
|
const auto &room = m_connection->room(notification.roomId);
|
||||||
|
if (!room) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
auto u = room->memberAvatarUrl(authorId);
|
||||||
|
auto avatar = u.isEmpty() ? QUrl() : connection()->makeMediaUrl(u);
|
||||||
|
const auto &authorAvatar = avatar.isValid() && avatar.scheme() == QStringLiteral("mxc") ? avatar : QUrl();
|
||||||
|
|
||||||
|
const auto &roomEvent = eventCast<const RoomEvent>(notification.event.get());
|
||||||
|
EventHandler eventHandler;
|
||||||
|
eventHandler.setRoom(dynamic_cast<NeoChatRoom *>(room));
|
||||||
|
eventHandler.setEvent(roomEvent);
|
||||||
|
beginInsertRows({}, m_notifications.length(), m_notifications.length());
|
||||||
|
m_notifications += Notification{
|
||||||
|
.roomId = notification.roomId,
|
||||||
|
.text = room->htmlSafeMemberName(authorId) + (roomEvent->is<StateEvent>() ? QStringLiteral(" ") : QStringLiteral(": "))
|
||||||
|
+ eventHandler.getPlainBody(true),
|
||||||
|
.authorName = room->htmlSafeMemberName(authorId),
|
||||||
|
.authorAvatar = authorAvatar,
|
||||||
|
.eventId = roomEvent->id(),
|
||||||
|
.roomDisplayName = room->displayName(),
|
||||||
|
};
|
||||||
|
endInsertRows();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_job = nullptr;
|
||||||
|
Q_EMIT loadingChanged();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NotificationsModel::canFetchMore(const QModelIndex &parent) const
|
||||||
|
{
|
||||||
|
Q_UNUSED(parent);
|
||||||
|
return !m_nextToken.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
void NotificationsModel::fetchMore(const QModelIndex &parent)
|
||||||
|
{
|
||||||
|
Q_UNUSED(parent);
|
||||||
|
loadData();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NotificationsModel::loading() const
|
||||||
|
{
|
||||||
|
return m_job;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString NotificationsModel::nextToken() const
|
||||||
|
{
|
||||||
|
return m_nextToken;
|
||||||
|
}
|
||||||
67
src/models/notificationsmodel.h
Normal file
67
src/models/notificationsmodel.h
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
|
||||||
|
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "neochatconnection.h"
|
||||||
|
#include <QAbstractListModel>
|
||||||
|
#include <QPointer>
|
||||||
|
#include <QQmlEngine>
|
||||||
|
#include <QVariant>
|
||||||
|
#include <Quotient/csapi/notifications.h>
|
||||||
|
|
||||||
|
class NotificationsModel : public QAbstractListModel
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
QML_ELEMENT
|
||||||
|
|
||||||
|
Q_PROPERTY(NeoChatConnection *connection READ connection WRITE setConnection NOTIFY connectionChanged)
|
||||||
|
Q_PROPERTY(bool loading READ loading NOTIFY loadingChanged)
|
||||||
|
Q_PROPERTY(QString nextToken READ nextToken NOTIFY nextTokenChanged)
|
||||||
|
|
||||||
|
public:
|
||||||
|
enum Roles {
|
||||||
|
TextRole = Qt::DisplayRole,
|
||||||
|
RoomIdRole,
|
||||||
|
AuthorName,
|
||||||
|
AuthorAvatar,
|
||||||
|
RoomRole,
|
||||||
|
EventIdRole,
|
||||||
|
RoomDisplayNameRole,
|
||||||
|
};
|
||||||
|
Q_ENUM(Roles);
|
||||||
|
|
||||||
|
struct Notification {
|
||||||
|
QString roomId;
|
||||||
|
QString text;
|
||||||
|
QString authorName;
|
||||||
|
QUrl authorAvatar;
|
||||||
|
QString eventId;
|
||||||
|
QString roomDisplayName;
|
||||||
|
};
|
||||||
|
|
||||||
|
NotificationsModel(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
int rowCount(const QModelIndex &parent) const override;
|
||||||
|
QVariant data(const QModelIndex &index, int role) const override;
|
||||||
|
QHash<int, QByteArray> roleNames() const override;
|
||||||
|
bool canFetchMore(const QModelIndex &parent) const override;
|
||||||
|
void fetchMore(const QModelIndex &parent) override;
|
||||||
|
|
||||||
|
NeoChatConnection *connection() const;
|
||||||
|
void setConnection(NeoChatConnection *connection);
|
||||||
|
bool loading() const;
|
||||||
|
QString nextToken() const;
|
||||||
|
|
||||||
|
Q_SIGNALS:
|
||||||
|
void connectionChanged();
|
||||||
|
void loadingChanged();
|
||||||
|
void nextTokenChanged();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QPointer<NeoChatConnection> m_connection;
|
||||||
|
void loadData();
|
||||||
|
QList<Notification> m_notifications;
|
||||||
|
QString m_nextToken;
|
||||||
|
QPointer<Quotient::GetNotificationsJob> m_job;
|
||||||
|
};
|
||||||
93
src/qml/NotificationsView.qml
Normal file
93
src/qml/NotificationsView.qml
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls as QQC2
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
import org.kde.kirigami as Kirigami
|
||||||
|
import org.kde.kirigami.delegates as KD
|
||||||
|
import org.kde.kirigamiaddons.components as Components
|
||||||
|
|
||||||
|
import org.kde.neochat
|
||||||
|
|
||||||
|
Kirigami.ScrollablePage {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
required property NeoChatConnection connection
|
||||||
|
|
||||||
|
title: i18nc("@title", "Notifications")
|
||||||
|
|
||||||
|
ListView {
|
||||||
|
id: listView
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
verticalLayoutDirection: ListView.BottomToTop
|
||||||
|
model: NotificationsModel {
|
||||||
|
id: notificationsModel
|
||||||
|
connection: root.connection
|
||||||
|
}
|
||||||
|
|
||||||
|
Kirigami.PlaceholderMessage {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
visible: listView.count === 0
|
||||||
|
text: notificationsModel.loading ? i18n("Loading…") : i18n("No Notifications")
|
||||||
|
}
|
||||||
|
|
||||||
|
footer: Kirigami.PlaceholderMessage {
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
text: i18n("Loading…")
|
||||||
|
visible: notificationsModel.nextToken.length > 0 && listView.count > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
delegate: QQC2.ItemDelegate {
|
||||||
|
width: parent?.width ?? 0
|
||||||
|
|
||||||
|
onClicked: RoomManager.visitRoom(model.room, model.eventId)
|
||||||
|
contentItem: RowLayout {
|
||||||
|
spacing: Kirigami.Units.largeSpacing
|
||||||
|
|
||||||
|
Components.Avatar {
|
||||||
|
source: model.authorAvatar
|
||||||
|
name: model.authorName
|
||||||
|
implicitHeight: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2
|
||||||
|
implicitWidth: implicitHeight
|
||||||
|
|
||||||
|
Layout.fillHeight: true
|
||||||
|
Layout.preferredWidth: height
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
|
||||||
|
QQC2.Label {
|
||||||
|
id: label
|
||||||
|
|
||||||
|
text: model.roomDisplayName
|
||||||
|
elide: Text.ElideRight
|
||||||
|
font.weight: Font.Normal
|
||||||
|
textFormat: Text.PlainText
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.alignment: Qt.AlignLeft | Qt.AlignBottom
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.Label {
|
||||||
|
id: subtitle
|
||||||
|
|
||||||
|
text: model.text
|
||||||
|
elide: Text.ElideRight
|
||||||
|
font: Kirigami.Theme.smallFont
|
||||||
|
opacity: root.hasNotifications ? 0.9 : 0.7
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ import QtQuick.Layouts
|
|||||||
import QtQml.Models
|
import QtQml.Models
|
||||||
|
|
||||||
import org.kde.kirigami as Kirigami
|
import org.kde.kirigami as Kirigami
|
||||||
|
import org.kde.kirigamiaddons.components as KirigamiComponents
|
||||||
|
|
||||||
import org.kde.neochat
|
import org.kde.neochat
|
||||||
import org.kde.neochat.config
|
import org.kde.neochat.config
|
||||||
@@ -128,6 +129,23 @@ Kirigami.Page {
|
|||||||
|
|
||||||
topMargin: Math.round(Kirigami.Units.smallSpacing / 2)
|
topMargin: Math.round(Kirigami.Units.smallSpacing / 2)
|
||||||
|
|
||||||
|
KirigamiComponents.FloatingButton {
|
||||||
|
icon.name: "notifications"
|
||||||
|
text: i18n("View notifications")
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: Kirigami.Units.largeSpacing
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.bottomMargin: Kirigami.Units.largeSpacing
|
||||||
|
width: Kirigami.Units.gridUnit * 2
|
||||||
|
height: width
|
||||||
|
QQC2.ToolTip.text: text
|
||||||
|
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||||
|
QQC2.ToolTip.visible: hovered
|
||||||
|
onClicked: pageStack.pushDialogLayer("qrc:/org/kde/neochat/qml/NotificationsView.qml", {connection: root.connection}, {
|
||||||
|
title: i18nc("@title", "Notifications")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
header: QQC2.ItemDelegate {
|
header: QQC2.ItemDelegate {
|
||||||
width: visible ? ListView.view.width : 0
|
width: visible ? ListView.view.width : 0
|
||||||
height: visible ? Kirigami.Units.gridUnit * 2 : 0
|
height: visible ? Kirigami.Units.gridUnit * 2 : 0
|
||||||
|
|||||||
Reference in New Issue
Block a user