Add notifications view
This commit is contained in:
@@ -139,6 +139,8 @@ add_library(neochat STATIC
|
||||
chatbarcache.h
|
||||
colorschemer.cpp
|
||||
colorschemer.h
|
||||
models/notificationsmodel.cpp
|
||||
models/notificationsmodel.h
|
||||
)
|
||||
|
||||
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/SelectSpacesDialog.qml
|
||||
qml/AttachDialog.qml
|
||||
qml/NotificationsView.qml
|
||||
RESOURCES
|
||||
qml/confetti.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 org.kde.kirigami as Kirigami
|
||||
import org.kde.kirigamiaddons.components as KirigamiComponents
|
||||
|
||||
import org.kde.neochat
|
||||
import org.kde.neochat.config
|
||||
@@ -128,6 +129,23 @@ Kirigami.Page {
|
||||
|
||||
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 {
|
||||
width: visible ? ListView.view.width : 0
|
||||
height: visible ? Kirigami.Units.gridUnit * 2 : 0
|
||||
|
||||
Reference in New Issue
Block a user