Add delegates to show room upgrades into the timeline model.

The delegates are at the beginning for upgraded rooms and end for predecessors.

Closes: network/neochat#620 and network/neochat#619
This commit is contained in:
James Graham
2024-08-22 17:21:36 +00:00
parent 656558850c
commit b3afa9f595
7 changed files with 213 additions and 2 deletions

View File

@@ -38,6 +38,8 @@ public:
ReadMarker, /**< The local user read marker. */
Loading, /**< A delegate to tell the user more messages are being loaded. */
TimelineEnd, /**< A delegate to inform that all messages are loaded. */
Predecessor, /**< A delegate to show a room predecessor. */
Successor, /**< A delegate to show a room successor. */
Other, /**< Anything that cannot be classified as another type. */
};
Q_ENUM(Type);

View File

@@ -3,11 +3,15 @@
#include "timelinemodel.h"
#include <Quotient/qt_connection_util.h>
#include "delegatetype.h"
TimelineModel::TimelineModel(QObject *parent)
: QConcatenateTablesProxyModel(parent)
{
m_timelineBeginningModel = new TimelineBeginningModel(this);
addSourceModel(m_timelineBeginningModel);
m_messageEventModel = new MessageEventModel(this);
addSourceModel(m_messageEventModel);
m_timelineEndModel = new TimelineEndModel(this);
@@ -23,6 +27,7 @@ void TimelineModel::setRoom(NeoChatRoom *room)
{
// Both models do their own null checking so just pass along.
m_messageEventModel->setRoom(room);
m_timelineBeginningModel->setRoom(room);
m_timelineEndModel->setRoom(room);
}
@@ -36,6 +41,69 @@ QHash<int, QByteArray> TimelineModel::roleNames() const
return m_messageEventModel->roleNames();
}
TimelineBeginningModel::TimelineBeginningModel(QObject *parent)
: QAbstractListModel(parent)
{
}
void TimelineBeginningModel::setRoom(NeoChatRoom *room)
{
if (room == m_room) {
return;
}
beginResetModel();
if (m_room != nullptr) {
m_room->disconnect(this);
}
m_room = room;
if (m_room != nullptr) {
Quotient::connectUntil(m_room.get(), &Quotient::Room::eventsHistoryJobChanged, this, [this]() {
if (m_room->allHistoryLoaded()) {
// HACK: We have to do it this way because DelegateChooser doesn't update dynamically.
beginRemoveRows({}, 0, 0);
endRemoveRows();
beginInsertRows({}, 0, 0);
endInsertRows();
return true;
}
return false;
});
}
endResetModel();
}
QVariant TimelineBeginningModel::data(const QModelIndex &idx, int role) const
{
Q_UNUSED(idx)
if (m_room == nullptr) {
return {};
}
if (role == DelegateTypeRole) {
return DelegateType::Successor;
}
return {};
}
int TimelineBeginningModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
if (m_room == nullptr) {
return 0;
}
return m_room->successorId().isEmpty() ? 0 : 1;
}
QHash<int, QByteArray> TimelineBeginningModel::roleNames() const
{
return {{DelegateTypeRole, "delegateType"}};
}
TimelineEndModel::TimelineEndModel(QObject *parent)
: QAbstractListModel(parent)
{
@@ -78,7 +146,11 @@ QVariant TimelineEndModel::data(const QModelIndex &idx, int role) const
}
if (role == DelegateTypeRole) {
return m_room->allHistoryLoaded() ? DelegateType::TimelineEnd : DelegateType::Loading;
if (idx.row() == 1 || rowCount() == 1) {
return m_room->allHistoryLoaded() ? DelegateType::TimelineEnd : DelegateType::Loading;
} else {
return DelegateType::Predecessor;
}
}
return {};
}
@@ -86,7 +158,10 @@ QVariant TimelineEndModel::data(const QModelIndex &idx, int role) const
int TimelineEndModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return 1;
if (m_room == nullptr) {
return 0;
}
return m_room->predecessorId().isEmpty() ? 1 : (m_room->allHistoryLoaded() ? 2 : 1);
}
QHash<int, QByteArray> TimelineEndModel::roleNames() const

View File

@@ -10,6 +10,57 @@
#include "messageeventmodel.h"
#include "neochatroom.h"
/**
* @class TimelineBeginningModel
*
* A model to provide a delegate at the start of the timeline to show upgrades.
*/
class TimelineBeginningModel : public QAbstractListModel
{
Q_OBJECT
QML_ELEMENT
public:
/**
* @brief Defines the model roles.
*/
enum Roles {
DelegateTypeRole = MessageEventModel::DelegateTypeRole, /**< The delegate type of the message. */
};
Q_ENUM(Roles)
explicit TimelineBeginningModel(QObject *parent = nullptr);
/**
* @brief Set the room for the timeline.
*/
void setRoom(NeoChatRoom *room);
/**
* @brief Get the given role value at the given index.
*
* @sa QAbstractItemModel::data
*/
[[nodiscard]] QVariant data(const QModelIndex &idx, int role = Qt::DisplayRole) const override;
/**
* @brief 1, the answer is always 1.
*
* @sa QAbstractItemModel::rowCount
*/
[[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override;
/**
* @brief Returns a map with DelegateTypeRole it's the only one.
*
* @sa Roles, QAbstractItemModel::roleNames()
*/
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
private:
QPointer<NeoChatRoom> m_room = nullptr;
};
/**
* @class TimelineEndModel
*
@@ -108,5 +159,6 @@ Q_SIGNALS:
private:
MessageEventModel *m_messageEventModel = nullptr;
TimelineBeginningModel *m_timelineBeginningModel = nullptr;
TimelineEndModel *m_timelineEndModel = nullptr;
};

View File

@@ -10,8 +10,10 @@ ecm_add_qml_module(timeline GENERATE_PLUGIN_SOURCE
HiddenDelegate.qml
MessageDelegate.qml
LoadingDelegate.qml
PredecessorDelegate.qml
ReadMarkerDelegate.qml
StateDelegate.qml
SuccessorDelegate.qml
TimelineEndDelegate.qml
Bubble.qml
AvatarFlow.qml

View File

@@ -41,6 +41,20 @@ DelegateChooser {
delegate: LoadingDelegate {}
}
DelegateChoice {
roleValue: DelegateType.Predecessor
delegate: PredecessorDelegate {
room: root.room
}
}
DelegateChoice {
roleValue: DelegateType.Successor
delegate: SuccessorDelegate {
room: root.room
}
}
DelegateChoice {
roleValue: DelegateType.TimelineEnd
delegate: TimelineEndDelegate {

View File

@@ -0,0 +1,33 @@
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
import QtQuick
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.neochat
TimelineDelegate {
id: root
/**
* @brief The current room that user is viewing.
*/
required property NeoChatRoom room
width: parent?.width
rightPadding: NeoChatConfig.compactLayout && root.ListView.view.width >= Kirigami.Units.gridUnit * 20 ? Kirigami.Units.gridUnit * 2 + Kirigami.Units.largeSpacing : Kirigami.Units.largeSpacing
alwaysFillWidth: NeoChatConfig.compactLayout
contentItem: Kirigami.InlineMessage {
visible: true
text: i18n("This room continues another conversation.")
type: Kirigami.MessageType.Information
actions: Kirigami.Action {
text: i18n("See older messages…")
onTriggered: RoomManager.resolveResource(root.room.predecessorId)
}
}
}

View File

@@ -0,0 +1,33 @@
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
import QtQuick
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.neochat
TimelineDelegate {
id: root
/**
* @brief The current room that user is viewing.
*/
required property NeoChatRoom room
width: parent?.width
rightPadding: NeoChatConfig.compactLayout && root.ListView.view.width >= Kirigami.Units.gridUnit * 20 ? Kirigami.Units.gridUnit * 2 + Kirigami.Units.largeSpacing : Kirigami.Units.largeSpacing
alwaysFillWidth: NeoChatConfig.compactLayout
contentItem: Kirigami.InlineMessage {
visible: true
text: i18n("This room has been replaced.")
type: Kirigami.MessageType.Information
actions: Kirigami.Action {
text: i18n("See new room…")
onTriggered: RoomManager.resolveResource(root.room.successorId)
}
}
}