Treat read markers as item in the model
This commit is contained in:
@@ -182,13 +182,4 @@ QQC2.ItemDelegate {
|
||||
visible: active
|
||||
sourceComponent: ReactionDelegate { }
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width * 0.9
|
||||
x: parent.width * 0.05
|
||||
height: Kirigami.Units.smallSpacing / 2
|
||||
anchors.top: loader.bottom
|
||||
visible: readMarker
|
||||
color: Kirigami.Theme.positiveTextColor
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,22 +232,11 @@ Kirigami.ScrollablePage {
|
||||
|
||||
model: !isLoaded ? undefined : sortedMessageEventModel
|
||||
|
||||
onContentYChanged: fetchMoreContent()
|
||||
|
||||
onContentYChanged: updateReadMarker()
|
||||
onCountChanged: updateReadMarker()
|
||||
|
||||
function updateReadMarker() {
|
||||
if(!noNeedMoreContent && contentY - 5000 < originY)
|
||||
function fetchMoreContent() {
|
||||
if(!noNeedMoreContent && contentY - 5000 < originY) {
|
||||
currentRoom.getPreviousContent(20);
|
||||
const index = currentRoom.readMarkerEventId ? eventToIndex(currentRoom.readMarkerEventId) : 0
|
||||
if(index === -1) {
|
||||
return
|
||||
}
|
||||
if(firstVisibleIndex() === -1 || lastVisibleIndex() === -1) {
|
||||
return
|
||||
}
|
||||
if(index < firstVisibleIndex() && index > lastVisibleIndex()) {
|
||||
currentRoom.readMarkerEventId = sortedMessageEventModel.data(sortedMessageEventModel.index(lastVisibleIndex(), 0), MessageEventModel.EventIdRole)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -536,6 +525,71 @@ Kirigami.ScrollablePage {
|
||||
}
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: "readMarker"
|
||||
delegate: QQC2.ItemDelegate {
|
||||
padding: Kirigami.Units.largeSpacing
|
||||
topInset: Kirigami.Units.largeSpacing
|
||||
topPadding: Kirigami.Units.largeSpacing * 2
|
||||
width: ListView.view.width - Kirigami.Units.gridUnit
|
||||
x: Kirigami.Units.gridUnit / 2
|
||||
contentItem: QQC2.Label {
|
||||
text: i18nc("Relative time since the room was last read", "Last read: %1", time)
|
||||
}
|
||||
|
||||
background: Kirigami.ShadowedRectangle {
|
||||
color: Kirigami.Theme.backgroundColor
|
||||
opacity: 0.6
|
||||
radius: Kirigami.Units.smallSpacing
|
||||
shadow.size: Kirigami.Units.smallSpacing
|
||||
shadow.color: Qt.rgba(0.0, 0.0, 0.0, 0.10)
|
||||
border.color: Kirigami.ColorUtils.tintWithAlpha(color, Kirigami.Theme.textColor, 0.15)
|
||||
border.width: Kirigami.Units.devicePixelRatio
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: makeMeDisapearTimer
|
||||
interval: Kirigami.Units.humanMoment * 2
|
||||
onTriggered: currentRoom.markAllMessagesAsRead();
|
||||
}
|
||||
|
||||
ListView.onPooled: makeMeDisapearTimer.stop()
|
||||
|
||||
ListView.onAdd: {
|
||||
const view = ListView.view;
|
||||
if (view.atYEnd) {
|
||||
makeMeDisapearTimer.start()
|
||||
}
|
||||
}
|
||||
|
||||
// When the read marker is visible and we are at the end of the list,
|
||||
// start the makeMeDisapearTimer
|
||||
Connections {
|
||||
target: ListView.view
|
||||
function onAtYEndChanged() {
|
||||
makeMeDisapearTimer.start();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ListView.onRemove: {
|
||||
const view = ListView.view;
|
||||
|
||||
if (view.atYEnd) {
|
||||
// easy case just mark everything as read
|
||||
currentRoom.markAllMessagesAsRead();
|
||||
return;
|
||||
}
|
||||
|
||||
// mark the last visible index
|
||||
const lastVisibleIdx = lastVisibleIndex();
|
||||
|
||||
if (lastVisibleIdx < index) {
|
||||
currentRoom.readMarkerEventId = sortedMessageEventModel.data(sortedMessageEventModel.index(lastVisibleIdx, 0), MessageEventModel.EventIdRole)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: "other"
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include <QTimeZone>
|
||||
|
||||
#include <KLocalizedString>
|
||||
#include <KFormat>
|
||||
|
||||
#include "utils.h"
|
||||
|
||||
@@ -34,7 +35,6 @@ QHash<int, QByteArray> MessageEventModel::roleNames() const
|
||||
roles[ContentRole] = "content";
|
||||
roles[ContentTypeRole] = "contentType";
|
||||
roles[HighlightRole] = "isHighlighted";
|
||||
roles[ReadMarkerRole] = "readMarker";
|
||||
roles[SpecialMarksRole] = "marks";
|
||||
roles[LongOperationRole] = "progressInfo";
|
||||
roles[AnnotationRole] = "annotation";
|
||||
@@ -90,6 +90,7 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
|
||||
|
||||
m_currentRoom = room;
|
||||
if (room) {
|
||||
m_lastReadEventIndex = QPersistentModelIndex(QModelIndex());
|
||||
room->setDisplayed();
|
||||
if (m_currentRoom->timelineSize() < 10) {
|
||||
room->getPreviousContent(50);
|
||||
@@ -141,6 +142,10 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
|
||||
});
|
||||
connect(m_currentRoom, &Room::addedMessages, this, [=](int lowest, int biggest) {
|
||||
endInsertRows();
|
||||
if (!m_lastReadEventIndex.isValid()) {
|
||||
// no read marker, so see if we need to create one.
|
||||
moveReadMarker(QString(), m_currentRoom->readMarkerEventId());
|
||||
}
|
||||
if (biggest < m_currentRoom->maxTimelineIndex()) {
|
||||
auto rowBelowInserted = m_currentRoom->maxTimelineIndex() - biggest + timelineBaseIndex() - 1;
|
||||
refreshEventRoles(rowBelowInserted, {ShowAuthorRole});
|
||||
@@ -170,9 +175,6 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
|
||||
}
|
||||
refreshRow(timelineBaseIndex()); // Refresh the looks
|
||||
refreshLastUserEvents(0);
|
||||
if (m_currentRoom->timelineSize() > 1) { // Refresh above
|
||||
refreshEventRoles(timelineBaseIndex() + 1, {ReadMarkerRole});
|
||||
}
|
||||
if (timelineBaseIndex() > 0) { // Refresh below, see #312
|
||||
refreshEventRoles(timelineBaseIndex() - 1, {ShowAuthorRole});
|
||||
}
|
||||
@@ -182,10 +184,7 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
|
||||
beginRemoveRows({}, i, i);
|
||||
});
|
||||
connect(m_currentRoom, &Room::pendingEventDiscarded, this, &MessageEventModel::endRemoveRows);
|
||||
connect(m_currentRoom, &Room::readMarkerMoved, this, [this] {
|
||||
refreshEventRoles(std::exchange(lastReadEventId, m_currentRoom->readMarkerEventId()), {ReadMarkerRole});
|
||||
refreshEventRoles(lastReadEventId, {ReadMarkerRole});
|
||||
});
|
||||
connect(m_currentRoom, &Room::readMarkerMoved, this, &MessageEventModel::moveReadMarker);
|
||||
connect(m_currentRoom, &Room::replacedEvent, this, [this](const RoomEvent *newEvent) {
|
||||
refreshLastUserEvents(refreshEvent(newEvent->id()) - timelineBaseIndex());
|
||||
});
|
||||
@@ -199,10 +198,6 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
|
||||
connect(m_currentRoom, &Room::fileTransferCompleted, this, &MessageEventModel::refreshEvent);
|
||||
connect(m_currentRoom, &Room::fileTransferFailed, this, &MessageEventModel::refreshEvent);
|
||||
connect(m_currentRoom, &Room::fileTransferCancelled, this, &MessageEventModel::refreshEvent);
|
||||
connect(m_currentRoom, &Room::readMarkerForUserMoved, this, [=](User *, const QString &fromEventId, const QString &toEventId) {
|
||||
refreshEventRoles(fromEventId, {UserMarkerRole});
|
||||
refreshEventRoles(toEventId, {UserMarkerRole});
|
||||
});
|
||||
connect(m_currentRoom->connection(), &Connection::ignoredUsersListChanged, this, [=] {
|
||||
beginResetModel();
|
||||
endResetModel();
|
||||
@@ -235,6 +230,43 @@ void MessageEventModel::refreshEventRoles(int row, const QVector<int> &roles)
|
||||
Q_EMIT dataChanged(idx, idx, roles);
|
||||
}
|
||||
|
||||
void MessageEventModel::moveReadMarker(const QString &fromEventId, const QString &toEventId)
|
||||
{
|
||||
const auto timelineIt = m_currentRoom->findInTimeline(toEventId);
|
||||
if (timelineIt == m_currentRoom->timelineEdge()) {
|
||||
return;
|
||||
}
|
||||
int newRow = int(timelineIt - m_currentRoom->messageEvents().rbegin()) + timelineBaseIndex();
|
||||
|
||||
if (!m_lastReadEventIndex.isValid()) {
|
||||
// Not valid index means we don't display any marker yet, in this case
|
||||
// we create the new index and insert the row in case the read marker
|
||||
// need to be displayed.
|
||||
if (newRow > timelineBaseIndex()) {
|
||||
// The user didn't read all the messages yet.
|
||||
beginInsertRows({}, newRow, newRow);
|
||||
m_lastReadEventIndex = QPersistentModelIndex(index(newRow, 0));
|
||||
endInsertRows();
|
||||
return;
|
||||
}
|
||||
// The user read all the messages and we didn't display any read marker yet
|
||||
// => do nothing
|
||||
return;
|
||||
}
|
||||
if (newRow <= timelineBaseIndex()) {
|
||||
// The user read all the messages => remove read marker
|
||||
beginRemoveRows({}, m_lastReadEventIndex.row(), m_lastReadEventIndex.row());
|
||||
m_lastReadEventIndex = QModelIndex();
|
||||
endRemoveRows();
|
||||
return;
|
||||
}
|
||||
|
||||
// The user didn't read all the messages yet but moved the reader marker.
|
||||
beginMoveRows({}, m_lastReadEventIndex.row(), m_lastReadEventIndex.row(), {}, newRow);
|
||||
m_lastReadEventIndex = QPersistentModelIndex(index(newRow, 0));
|
||||
endMoveRows();
|
||||
}
|
||||
|
||||
int MessageEventModel::refreshEventRoles(const QString &id, const QVector<int> &roles)
|
||||
{
|
||||
// On 64-bit platforms, difference_type for std containers is long long
|
||||
@@ -327,7 +359,15 @@ int MessageEventModel::rowCount(const QModelIndex &parent) const
|
||||
if (!m_currentRoom || parent.isValid()) {
|
||||
return 0;
|
||||
}
|
||||
return m_currentRoom->timelineSize();
|
||||
|
||||
const auto firstIt = m_currentRoom->messageEvents().crbegin();
|
||||
if (firstIt != m_currentRoom->messageEvents().crend()) {
|
||||
const auto &firstEvt = **firstIt;
|
||||
return m_currentRoom->timelineSize() + (lastReadEventId != firstEvt.id() ? 1 : 0);
|
||||
} else {
|
||||
return m_currentRoom->timelineSize();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
inline QVariantMap userAtEvent(NeoChatUser *user, NeoChatRoom *room, const RoomEvent &evt)
|
||||
@@ -354,7 +394,22 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
};
|
||||
|
||||
bool isPending = row < timelineBaseIndex();
|
||||
const auto timelineIt = m_currentRoom->messageEvents().crbegin() + std::max(0, row - timelineBaseIndex());
|
||||
|
||||
if (m_lastReadEventIndex.row() == row) {
|
||||
switch(role) {
|
||||
case EventTypeRole:
|
||||
return QStringLiteral("readMarker");
|
||||
case TimeRole:
|
||||
{
|
||||
const QDateTime eventDate = data(index(m_lastReadEventIndex.row() + 1, 0), TimeRole).toDateTime();
|
||||
const KFormat format;
|
||||
return format.formatRelativeDateTime(eventDate, QLocale::ShortFormat);
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto timelineIt = m_currentRoom->messageEvents().crbegin() + std::max(0, row - timelineBaseIndex() - (m_lastReadEventIndex.isValid() && m_lastReadEventIndex.row() < row ? 1 : 0));
|
||||
const auto pendingIt = m_currentRoom->pendingEvents().crbegin() + std::min(row, timelineBaseIndex());
|
||||
const auto &evt = isPending ? **pendingIt : **timelineIt;
|
||||
|
||||
@@ -457,10 +512,6 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
return m_currentRoom->isEventHighlighted(&evt);
|
||||
}
|
||||
|
||||
if (role == ReadMarkerRole) {
|
||||
return evt.id() == lastReadEventId && row > timelineBaseIndex();
|
||||
}
|
||||
|
||||
if (role == SpecialMarksRole) {
|
||||
if (isPending) {
|
||||
return pendingIt->deliveryStatus();
|
||||
|
||||
@@ -24,7 +24,6 @@ public:
|
||||
ContentRole,
|
||||
ContentTypeRole,
|
||||
HighlightRole,
|
||||
ReadMarkerRole,
|
||||
SpecialMarksRole,
|
||||
LongOperationRole,
|
||||
AnnotationRole,
|
||||
@@ -45,13 +44,6 @@ public:
|
||||
};
|
||||
Q_ENUM(EventRoles)
|
||||
|
||||
enum BubbleShapes {
|
||||
NoShape = 0,
|
||||
BeginShape,
|
||||
MiddleShape,
|
||||
EndShape,
|
||||
};
|
||||
|
||||
explicit MessageEventModel(QObject *parent = nullptr);
|
||||
~MessageEventModel() override;
|
||||
|
||||
@@ -76,6 +68,7 @@ private Q_SLOTS:
|
||||
private:
|
||||
NeoChatRoom *m_currentRoom = nullptr;
|
||||
QString lastReadEventId;
|
||||
QPersistentModelIndex m_lastReadEventIndex;
|
||||
int rowBelowInserted = -1;
|
||||
bool movingEvent = false;
|
||||
|
||||
@@ -86,6 +79,7 @@ private:
|
||||
void refreshLastUserEvents(int baseTimelineRow);
|
||||
void refreshEventRoles(int row, const QVector<int> &roles = {});
|
||||
int refreshEventRoles(const QString &eventId, const QVector<int> &roles = {});
|
||||
void moveReadMarker(const QString &fromEventId, const QString &toEventId);
|
||||
|
||||
Q_SIGNALS:
|
||||
void roomChanged();
|
||||
|
||||
Reference in New Issue
Block a user