diff --git a/include/libqmatrixclient b/include/libqmatrixclient
index 9875149..c8dc0c0 160000
--- a/include/libqmatrixclient
+++ b/include/libqmatrixclient
@@ -1 +1 @@
-Subproject commit 98751495f1990dccf285e3b4739f86de7b7f68fd
+Subproject commit c8dc0c075497ca8f174b738ee4253ca282cbec8c
diff --git a/qml/Room.qml b/qml/Room.qml
index b82483b..9a8e3f7 100644
--- a/qml/Room.qml
+++ b/qml/Room.qml
@@ -31,6 +31,8 @@ Page {
Layout.maximumWidth: 360
listModel: roomListModel
+
+ onEnterRoom: roomForm.currentRoom = currentRoom
}
RoomForm {
@@ -38,8 +40,6 @@ Page {
Layout.fillWidth: true
Layout.fillHeight: true
-
- currentRoom: roomListForm.currentRoom
}
}
}
diff --git a/qml/component/MessageDelegate.qml b/qml/component/MessageDelegate.qml
index defc0e5..e0515de 100644
--- a/qml/component/MessageDelegate.qml
+++ b/qml/component/MessageDelegate.qml
@@ -1,6 +1,7 @@
import QtQuick 2.9
import QtQuick.Controls 2.2
import QtQuick.Controls.Material 2.2
+import Matrique 0.1
Item {
id: messageDelegate
@@ -13,7 +14,7 @@ Item {
readonly property bool isMessage: eventType === "message" || eventType === "notice"
readonly property bool isFile: eventType === "video" || eventType === "audio" || eventType === "file" || eventType === "image"
- visible: eventType != "redaction"
+ visible: marks !== EventStatus.Hidden
z: -5
width: delegateLoader.width
diff --git a/qml/form/RoomForm.qml b/qml/form/RoomForm.qml
index 6e041fb..11fdd3c 100644
--- a/qml/form/RoomForm.qml
+++ b/qml/form/RoomForm.qml
@@ -10,7 +10,7 @@ import "qrc:/js/md.js" as Markdown
Item {
id: item
- property var currentRoom
+ property var currentRoom: null
Pane {
anchors.fill: parent
@@ -269,9 +269,9 @@ Item {
text = text.substr(PREFIX_RAINBOW.length)
var parsedText = ""
- var rainbowColor = ["#ee0000", "#ff7700", "#eeee00", "#00bb00", "#0000ee", "#dd00dd", "#880088"]
+ var rainbowColor = ["#ff2b00", "#ff5500", "#ff8000", "#ffaa00", "#ffd500", "#ffff00", "#d4ff00", "#aaff00", "#80ff00", "#55ff00", "#2bff00", "#00ff00", "#00ff2b", "#00ff55", "#00ff80", "#00ffaa", "#00ffd5", "#00ffff", "#00d4ff", "#00aaff", "#007fff", "#0055ff", "#002bff", "#0000ff", "#2a00ff", "#5500ff", "#7f00ff", "#aa00ff", "#d400ff", "#ff00ff", "#ff00d4", "#ff00aa", "#ff0080", "#ff0055", "#ff002b", "#ff0000"]
for (var i = 0; i < text.length; i++) {
- parsedText = parsedText + "" + text.charAt(i) + ""
+ parsedText = parsedText + "" + text.charAt(i) + ""
}
currentRoom.postHtmlMessage(text, parsedText, RoomMessageEvent.Text)
return
diff --git a/qml/form/RoomListForm.qml b/qml/form/RoomListForm.qml
index 7f89764..1cef370 100644
--- a/qml/form/RoomListForm.qml
+++ b/qml/form/RoomListForm.qml
@@ -14,6 +14,7 @@ Item {
readonly property int currentIndex: roomListProxyModel.mapToSource(listView.currentIndex)
readonly property var currentRoom: currentIndex != -1 ? listModel.roomAt(currentIndex) : null
readonly property bool mini: setting.miniMode // Used as an indicator of whether the listform should be displayed as "Mini mode".
+ signal enterRoom()
ColumnLayout {
anchors.fill: parent
@@ -137,11 +138,19 @@ Item {
width: parent.width
height: 80
onPressed: listView.currentIndex = index
+ onClicked: enterRoom()
onPressAndHold: menuComponent.createObject(this)
ToolTip.visible: mini && hovered
ToolTip.text: name
+ Rectangle {
+ width: 4
+ height: parent.height
+ color: Qt.tint(Material.accent, "#20FFFFFF")
+ visible: unreadCount > 0
+ }
+
contentItem: RowLayout {
anchors.fill: parent
anchors.margins: 16
@@ -201,6 +210,10 @@ Item {
onTriggered: currentRoom.isLowPriority ? currentRoom.removeTag("m.lowpriority") : currentRoom.addTag("m.lowpriority", "1")
}
MenuSeparator {}
+ MenuItem {
+ text: "Mark as Read"
+ onTriggered: currentRoom.markAllMessagesAsRead()
+ }
MenuItem {
text: "Leave Room"
onTriggered: matriqueController.forgetRoom(currentRoom.id)
diff --git a/qml/main.qml b/qml/main.qml
index 9ad888b..d28a020 100644
--- a/qml/main.qml
+++ b/qml/main.qml
@@ -91,27 +91,6 @@ ApplicationWindow {
}
}
- Dialog {
- property alias text: errorLabel.text
-
- id: errorDialog
- width: 360
- modal: true
- title: "ERROR"
-
- x: (window.width - width) / 2
- y: (window.height - height) / 2
-
- standardButtons: Dialog.Ok
-
- Label {
- id: errorLabel
- width: parent.width
- text: "Label"
- wrapMode: Text.Wrap
- }
- }
-
Component {
id: loginPage
diff --git a/src/messageeventmodel.cpp b/src/messageeventmodel.cpp
index 22f813c..e97289d 100644
--- a/src/messageeventmodel.cpp
+++ b/src/messageeventmodel.cpp
@@ -1,7 +1,6 @@
#include "messageeventmodel.h"
#include
-#include
#include // for qmlRegisterType()
#include
@@ -24,22 +23,27 @@ QHash MessageEventModel::roleNames() const {
roles[AboveAuthorRole] = "aboveAuthor";
roles[ContentRole] = "content";
roles[ContentTypeRole] = "contentType";
+ roles[HighlightRole] = "highlight";
roles[ReadMarkerRole] = "readMarker";
roles[SpecialMarksRole] = "marks";
roles[LongOperationRole] = "progressInfo";
+ roles[AnnotationRole] = "annotation";
roles[EventResolvedTypeRole] = "eventResolvedType";
roles[PlainTextRole] = "plainText";
return roles;
}
+MessageEventModel::~MessageEventModel() {}
+
MessageEventModel::MessageEventModel(QObject* parent)
: QAbstractListModel(parent), m_currentRoom(nullptr) {
- qmlRegisterType();
- qRegisterMetaType();
+ using namespace QMatrixClient;
+ qmlRegisterType();
+ qRegisterMetaType();
+ qmlRegisterUncreatableType(
+ "Matrique", 0, 1, "EventStatus", "EventStatus is not an creatable type");
}
-MessageEventModel::~MessageEventModel() {}
-
void MessageEventModel::setRoom(QMatrixClient::Room* room) {
if (room == m_currentRoom) return;
@@ -52,58 +56,76 @@ void MessageEventModel::setRoom(QMatrixClient::Room* room) {
m_currentRoom = room;
if (room) {
lastReadEventId = room->readMarkerEventId();
+
using namespace QMatrixClient;
connect(m_currentRoom, &Room::aboutToAddNewMessages, this,
[=](RoomEventsRange events) {
- const auto pos = m_currentRoom->pendingEvents().size();
- beginInsertRows(QModelIndex(), int(pos),
- int(pos + events.size() - 1));
+ beginInsertRows({}, timelineBaseIndex(),
+ timelineBaseIndex() + int(events.size()) - 1);
});
connect(m_currentRoom, &Room::aboutToAddHistoricalMessages, this,
[=](RoomEventsRange events) {
- if (rowCount() > 0) nextNewerRow = rowCount() - 1; // See #312
- beginInsertRows(QModelIndex(), rowCount(),
+ if (rowCount() > 0)
+ rowBelowInserted = rowCount() - 1; // See #312
+ beginInsertRows({}, rowCount(),
rowCount() + int(events.size()) - 1);
});
- connect(m_currentRoom, &Room::addedMessages, this, [=] {
- if (nextNewerRow > -1) {
- const auto idx = index(nextNewerRow);
- emit dataChanged(idx, idx);
- nextNewerRow = -1;
- }
- endInsertRows();
- });
+ connect(m_currentRoom, &Room::addedMessages, this,
+ [=](int lowest, int biggest) {
+ endInsertRows();
+ if (biggest < m_currentRoom->maxTimelineIndex()) {
+ auto rowBelowInserted = m_currentRoom->maxTimelineIndex() -
+ biggest + timelineBaseIndex() - 1;
+ refreshEventRoles(rowBelowInserted,
+ {AboveAuthorRole, AboveSectionRole});
+ }
+ for (auto i = m_currentRoom->maxTimelineIndex() - biggest;
+ i <= m_currentRoom->maxTimelineIndex() - lowest; ++i)
+ refreshLastUserEvents(i);
+ });
connect(m_currentRoom, &Room::pendingEventAboutToAdd, this,
[this] { beginInsertRows({}, 0, 0); });
connect(m_currentRoom, &Room::pendingEventAdded, this,
&MessageEventModel::endInsertRows);
connect(m_currentRoom, &Room::pendingEventAboutToMerge, this,
[this](RoomEvent*, int i) {
- const auto timelineBaseIdx =
- int(m_currentRoom->pendingEvents().size());
- if (i + 1 == timelineBaseIdx) return; // No need to move anything
- mergingEcho = true;
- Q_ASSERT(beginMoveRows({}, i, i, {}, timelineBaseIdx));
+ if (i == 0) return; // No need to move anything, just refresh
+
+ movingEvent = true;
+ // Reverse i because row 0 is bottommost in the model
+ const auto row = timelineBaseIndex() - i - 1;
+ Q_ASSERT(beginMoveRows({}, row, row, {}, timelineBaseIndex()));
});
connect(m_currentRoom, &Room::pendingEventMerged, this, [this] {
- if (mergingEcho) {
+ if (movingEvent) {
endMoveRows();
- mergingEcho = false;
+ movingEvent = false;
}
- refreshEventRoles(int(m_currentRoom->pendingEvents().size()),
- {SpecialMarksRole});
+ 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,
+ {AboveAuthorRole, AboveSectionRole});
});
connect(m_currentRoom, &Room::pendingEventChanged, this,
- [this](int i) { refreshEventRoles(i, {SpecialMarksRole}); });
+ &MessageEventModel::refreshRow);
+ connect(m_currentRoom, &Room::pendingEventAboutToDiscard, this,
+ [this](int i) { 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::replacedEvent, this,
- [this](const RoomEvent* newEvent) { refreshEvent(newEvent->id()); });
+ connect(m_currentRoom, &Room::replacedEvent, this,
+ [this](const RoomEvent* newEvent) {
+ refreshLastUserEvents(refreshEvent(newEvent->id()) -
+ timelineBaseIndex());
+ });
connect(m_currentRoom, &Room::fileTransferProgress, this,
&MessageEventModel::refreshEvent);
connect(m_currentRoom, &Room::fileTransferCompleted, this,
@@ -119,21 +141,32 @@ void MessageEventModel::setRoom(QMatrixClient::Room* room) {
endResetModel();
}
-void MessageEventModel::refreshEvent(const QString& eventId) {
- refreshEventRoles(eventId, {});
+int MessageEventModel::refreshEvent(const QString& eventId) {
+ return refreshEventRoles(eventId);
}
-void MessageEventModel::refreshEventRoles(const int row,
- const QVector& roles) {
+void MessageEventModel::refreshRow(int row) { refreshEventRoles(row); }
+
+int MessageEventModel::timelineBaseIndex() const {
+ return m_currentRoom ? int(m_currentRoom->pendingEvents().size()) : 0;
+}
+
+void MessageEventModel::refreshEventRoles(int row, const QVector& roles) {
const auto idx = index(row);
emit dataChanged(idx, idx, roles);
}
-void MessageEventModel::refreshEventRoles(const QString& eventId,
- const QVector& roles) {
+int MessageEventModel::refreshEventRoles(const QString& eventId,
+ const QVector& roles) {
const auto it = m_currentRoom->findInTimeline(eventId);
- if (it != m_currentRoom->timelineEdge())
- refreshEventRoles(it - m_currentRoom->messageEvents().rbegin(), roles);
+ if (it == m_currentRoom->timelineEdge()) {
+ qWarning() << "Trying to refresh inexistent event:" << eventId;
+ return -1;
+ }
+ const auto row =
+ it - m_currentRoom->messageEvents().rbegin() + timelineBaseIndex();
+ refreshEventRoles(row, roles);
+ return row;
}
inline bool hasValidTimestamp(const QMatrixClient::TimelineItem& ti) {
@@ -161,9 +194,8 @@ QDateTime MessageEventModel::makeMessageTimestamp(
return {};
}
-QString MessageEventModel::makeDateString(
- const QMatrixClient::Room::rev_iter_t& baseIt) const {
- auto date = makeMessageTimestamp(baseIt).toLocalTime().date();
+QString MessageEventModel::renderDate(QDateTime timestamp) const {
+ auto date = timestamp.toLocalTime().date();
if (QMatrixClient::SettingsGroup("UI")
.value("banner_human_friendly_date", true)
.toBool()) {
@@ -176,25 +208,101 @@ QString MessageEventModel::makeDateString(
return date.toString(Qt::DefaultLocaleShortDate);
}
+bool MessageEventModel::isUserActivityNotable(
+ const QMatrixClient::Room::rev_iter_t& baseIt) const {
+ const auto& senderId = (*baseIt)->senderId();
+ // TODO: Go up and down the timeline (limit to 100 events for
+ // the sake of performance) and collect all messages of
+ // this author; find out if there's anything besides joins, leaves
+ // and redactions; if not, double-check whether the current event is
+ // a part of a re-join without following redactions.
+ using namespace QMatrixClient;
+ bool joinFound = false, redactionsFound = false;
+ // Find the nearest join of this user above, or a no-nonsense event.
+ for (auto it = baseIt,
+ limit = baseIt +
+ std::min(int(m_currentRoom->timelineEdge() - baseIt), 100);
+ it != limit; ++it) {
+ const auto& e = **it;
+ if (e.senderId() != senderId) continue;
+ if (e.isRedacted()) {
+ redactionsFound = true;
+ continue;
+ }
+ if (auto* me = it->viewAs()) {
+ if (me->isJoin()) {
+ joinFound = true;
+ break;
+ }
+ continue;
+ }
+ return true; // Consider all other events notable
+ }
+ // Find the nearest leave of this user below, or a no-nonsense event
+ bool leaveFound = false;
+ for (auto it = baseIt.base() - 1,
+ limit = baseIt.base() +
+ std::min(int(m_currentRoom->messageEvents().end() -
+ baseIt.base()),
+ 100);
+ it != limit; ++it) {
+ const auto& e = **it;
+ if (e.senderId() != senderId) continue;
+ if (e.isRedacted()) {
+ redactionsFound = true;
+ continue;
+ }
+ if (auto* me = it->viewAs()) {
+ if (me->isLeave() || me->membership() == MembershipType::Ban) {
+ leaveFound = true;
+ break;
+ }
+ continue;
+ }
+ return true;
+ }
+ // If we are here, it means that no notable events have been found in
+ // the timeline vicinity, and probably redactions are there. Doesn't look
+ // notable but let's give some benefit of doubt.
+ if (redactionsFound) return false; // Join + redactions or redactions + leave
+ return !(joinFound && leaveFound); // Join + (maybe profile changes) + leave
+}
+
+void MessageEventModel::refreshLastUserEvents(int baseTimelineRow) {
+ if (!m_currentRoom || m_currentRoom->timelineSize() <= baseTimelineRow)
+ return;
+ const auto& timelineBottom = m_currentRoom->messageEvents().rbegin();
+ const auto& lastSender = (*(timelineBottom + baseTimelineRow))->senderId();
+ const auto limit = timelineBottom + std::min(baseTimelineRow + 100,
+ m_currentRoom->timelineSize());
+ for (auto it = timelineBottom + std::max(baseTimelineRow - 100, 0);
+ it != limit; ++it) {
+ if ((*it)->senderId() == lastSender) {
+ auto idx = index(it - timelineBottom);
+ emit dataChanged(idx, idx);
+ }
+ }
+}
+
int MessageEventModel::rowCount(const QModelIndex& parent) const {
if (!m_currentRoom || parent.isValid()) return 0;
return m_currentRoom->timelineSize();
}
-QVariant MessageEventModel::data(const QModelIndex& index, int role) const {
- const auto row = index.row();
+QVariant MessageEventModel::data(const QModelIndex& idx, int role) const {
+ const auto row = idx.row();
if (!m_currentRoom || row < 0 ||
row >= int(m_currentRoom->pendingEvents().size()) +
m_currentRoom->timelineSize())
return {};
- const auto timelineBaseIdx = int(m_currentRoom->pendingEvents().size());
+ bool isPending = row < timelineBaseIndex();
const auto timelineIt = m_currentRoom->messageEvents().crbegin() +
- std::max(-1, row - timelineBaseIdx);
- const auto& evt = row < timelineBaseIdx
- ? *m_currentRoom->pendingEvents()[size_t(row)]
- : *timelineIt->event();
+ std::max(0, row - timelineBaseIndex());
+ const auto pendingIt = m_currentRoom->pendingEvents().crbegin() +
+ std::min(row, timelineBaseIndex());
+ const auto& evt = isPending ? **pendingIt : **timelineIt;
using namespace QMatrixClient;
if (role == Qt::DisplayRole) {
@@ -238,14 +346,14 @@ QVariant MessageEventModel::data(const QModelIndex& index, int role) const {
: tr("joined the room");
}
QString text{};
- if (e.displayName() != e.prevContent()->displayName) {
+ if (e.isRename()) {
if (e.displayName().isEmpty())
text = tr("cleared the display name");
else
text =
tr("changed the display name to %1").arg(e.displayName());
}
- if (e.avatarUrl() != e.prevContent()->avatarUrl) {
+ if (e.isAvatarUpdate()) {
if (!text.isEmpty()) text += " and ";
if (e.avatarUrl().isEmpty())
text += tr("cleared the avatar");
@@ -314,8 +422,7 @@ QVariant MessageEventModel::data(const QModelIndex& index, int role) const {
if (e.hasFileContent()) {
auto fileCaption = e.content()->fileInfo()->originalName;
- if (fileCaption.isEmpty())
- fileCaption = e.plainBody();
+ if (fileCaption.isEmpty()) fileCaption = e.plainBody();
if (fileCaption.isEmpty()) return tr("a file");
}
return e.plainBody();
@@ -412,15 +519,10 @@ QVariant MessageEventModel::data(const QModelIndex& index, int role) const {
return "notice";
case MessageEventType::Image:
return "image";
- case MessageEventType::File:
- case MessageEventType::Audio:
- case MessageEventType::Video:
- return "file";
default:
- return "message";
+ return e->hasFileContent() ? "file" : "message";
}
}
- if (is(evt)) return "redaction";
if (evt.isStateEvent()) return "state";
return "other";
@@ -431,9 +533,8 @@ QVariant MessageEventModel::data(const QModelIndex& index, int role) const {
if (role == AuthorRole) {
// FIXME: It shouldn't be User, it should be its state "as of event"
- return QVariant::fromValue(row < timelineBaseIdx
- ? m_currentRoom->localUser()
- : m_currentRoom->user(evt.senderId()));
+ return QVariant::fromValue(isPending ? m_currentRoom->localUser()
+ : m_currentRoom->user(evt.senderId()));
}
if (role == ContentTypeRole) {
@@ -466,16 +567,39 @@ QVariant MessageEventModel::data(const QModelIndex& index, int role) const {
if (role == ReadMarkerRole) return evt.id() == lastReadEventId;
if (role == SpecialMarksRole) {
- if (row < timelineBaseIdx)
- return evt.id().isEmpty() ? "unsent" : "unsynced";
+ if (isPending) return pendingIt->deliveryStatus();
+
+ if (is(evt)) return EventStatus::Hidden;
+ auto* memberEvent = timelineIt->viewAs();
+ if (memberEvent) {
+ if ((memberEvent->isJoin() || memberEvent->isLeave()) &&
+ !Settings().value("UI/show_joinleave", true).toBool())
+ return EventStatus::Hidden;
+ }
+ if (memberEvent || evt.isRedacted()) {
+ if (evt.senderId() == m_currentRoom->localUser()->id() ||
+ Settings().value("UI/show_spammy").toBool()) {
+ // QElapsedTimer et; et.start();
+ auto hide = !isUserActivityNotable(timelineIt);
+ // qDebug() << "Checked user activity for" << evt.id() <<
+ // "in" << et;
+ if (hide) return EventStatus::Hidden;
+ }
+ }
+ if (evt.isRedacted())
+ return Settings().value("UI/show_redacted").toBool()
+ ? EventStatus::Redacted : EventStatus::Hidden;
if (evt.isStateEvent() &&
- static_cast(evt).repeatsState())
- return "noop";
- return evt.isRedacted() ? "redacted" : "";
+ static_cast(evt).repeatsState() &&
+ !Settings().value("UI/show_noop_events").toBool())
+ return EventStatus::Hidden;
+
+ return EventStatus::Normal;
}
- if (role == EventIdRole) return evt.id();
+ if (role == EventIdRole)
+ return !evt.id().isEmpty() ? evt.id() : evt.transactionId();
if (role == LongOperationRole) {
if (auto e = eventCast(&evt))
@@ -483,30 +607,24 @@ QVariant MessageEventModel::data(const QModelIndex& index, int role) const {
return QVariant::fromValue(m_currentRoom->fileTransferInfo(e->id()));
}
- if (row >= timelineBaseIdx - 1) // The timeline and the topmost unsynced
- {
- if (role == TimeRole)
- return row < timelineBaseIdx ? QDateTime::currentDateTimeUtc()
- : makeMessageTimestamp(timelineIt);
+ if (role == AnnotationRole)
+ if (isPending) return pendingIt->annotation();
- if (role == SectionRole)
- return row < timelineBaseIdx
- ? tr("Today")
- : makeDateString(
- timelineIt); // FIXME: move date rendering to QML
-
- // FIXME: shouldn't be here, because #312
- auto aboveEventIt = timelineIt + 1;
- if (aboveEventIt != m_currentRoom->timelineEdge()) {
- if (role == AboveSectionRole) return makeDateString(aboveEventIt);
-
- if (role == AboveAuthorRole)
- return QVariant::fromValue(
- m_currentRoom->user((*aboveEventIt)->senderId()));
-
- if (role == AboveTimeRole) return makeMessageTimestamp(aboveEventIt);
- }
+ if (role == TimeRole || role == SectionRole) {
+ auto ts =
+ isPending ? pendingIt->lastUpdated() : makeMessageTimestamp(timelineIt);
+ return role == TimeRole ? QVariant(ts) : renderDate(ts);
}
- return QVariant();
+ if (role == AboveSectionRole || role == AboveAuthorRole ||
+ role == AboveTimeRole)
+ for (auto r = row + 1; r < rowCount(); ++r) {
+ auto i = index(r);
+ if (data(i, SpecialMarksRole) != EventStatus::Hidden)
+ return data(i, role == AboveSectionRole
+ ? SectionRole
+ : role == AboveAuthorRole ? AuthorRole : TimeRole);
+ }
+
+ return {};
}
diff --git a/src/messageeventmodel.h b/src/messageeventmodel.h
index c79a6c8..500ca8a 100644
--- a/src/messageeventmodel.h
+++ b/src/messageeventmodel.h
@@ -25,6 +25,7 @@ class MessageEventModel : public QAbstractListModel {
ReadMarkerRole,
SpecialMarksRole,
LongOperationRole,
+ AnnotationRole,
PlainTextRole,
// For debugging
EventResolvedTypeRole,
@@ -36,25 +37,31 @@ class MessageEventModel : public QAbstractListModel {
QMatrixClient::Room* getRoom() { return m_currentRoom; }
void setRoom(QMatrixClient::Room* room);
- Q_INVOKABLE int rowCount(
- const QModelIndex& parent = QModelIndex()) const override;
- QVariant data(const QModelIndex& index, int role) const override;
+ int rowCount(const QModelIndex& parent = QModelIndex()) const override;
+ QVariant data(const QModelIndex& index,
+ int role = Qt::DisplayRole) const override;
QHash roleNames() const;
private slots:
- void refreshEvent(const QString& eventId);
+ int refreshEvent(const QString& eventId);
+ void refreshRow(int row);
private:
QMatrixClient::Room* m_currentRoom = nullptr;
QString lastReadEventId;
- bool mergingEcho = 0;
- int nextNewerRow = -1;
+ int rowBelowInserted = -1;
+ bool movingEvent = 0;
+ int timelineBaseIndex() const;
QDateTime makeMessageTimestamp(
const QMatrixClient::Room::rev_iter_t& baseIt) const;
- QString makeDateString(const QMatrixClient::Room::rev_iter_t& baseIt) const;
- void refreshEventRoles(const int row, const QVector& roles);
- void refreshEventRoles(const QString& eventId, const QVector& roles);
+ QString renderDate(QDateTime timestamp) const;
+ bool isUserActivityNotable(
+ const QMatrixClient::Room::rev_iter_t& baseIt) const;
+
+ void refreshLastUserEvents(int baseRow);
+ void refreshEventRoles(int row, const QVector& roles = {});
+ int refreshEventRoles(const QString& eventId, const QVector& roles = {});
signals:
void roomChanged();
diff --git a/src/roomlistmodel.cpp b/src/roomlistmodel.cpp
index b6eeb30..49029f3 100644
--- a/src/roomlistmodel.cpp
+++ b/src/roomlistmodel.cpp
@@ -60,19 +60,20 @@ void RoomListModel::connectRoomSignals(Room* room) {
connect(room, &Room::avatarChanged, this,
[=] { refresh(room, {AvatarRole}); });
- connect(room, &Room::unreadMessagesChanged, this, [=](Room* r) {
- if (r->hasUnreadMessages()) emit newMessage(r);
- });
-// connect(
-// room, &QMatrixClient::Room::aboutToAddNewMessages, this,
-// [=](QMatrixClient::RoomEventsRange eventsRange) {
-// for (QMatrixClient::RoomEvents events : eventsRange.const_iterator) {
-// for (QMatrixClient::RoomEvent event : events) {
-// qDebug() << event.fullJson();
-// }
-// }
-// emit newMessage(room);
-// });
+ connect(room, &Room::unreadMessagesChanged, this, [=](Room* r) {
+ if (r->hasUnreadMessages()) emit newMessage(r);
+ });
+ // connect(
+ // room, &QMatrixClient::Room::aboutToAddNewMessages, this,
+ // [=](QMatrixClient::RoomEventsRange eventsRange) {
+ // for (QMatrixClient::RoomEvents events : eventsRange.const_iterator)
+ // {
+ // for (QMatrixClient::RoomEvent event : events) {
+ // qDebug() << event.fullJson();
+ // }
+ // }
+ // emit newMessage(room);
+ // });
}
void RoomListModel::updateRoom(Room* room, Room* prev) {
@@ -159,6 +160,9 @@ QVariant RoomListModel::data(const QModelIndex& index, int role) const {
if (room->highlightCount() > 0) return QBrush(QColor("orange"));
return QVariant();
}
+ if (role == UnreadCountRole) {
+ return room->unreadCount();
+ }
return QVariant();
}
@@ -189,5 +193,6 @@ QHash RoomListModel::roleNames() const {
roles[TopicRole] = "topic";
roles[CategoryRole] = "category";
roles[HighlightRole] = "highlight";
+ roles[UnreadCountRole] = "unreadCount";
return roles;
}
diff --git a/src/roomlistmodel.h b/src/roomlistmodel.h
index e5ee63b..fb4d02f 100644
--- a/src/roomlistmodel.h
+++ b/src/roomlistmodel.h
@@ -18,6 +18,7 @@ class RoomListModel : public QAbstractListModel {
TopicRole,
CategoryRole,
HighlightRole,
+ UnreadCountRole
};
RoomListModel(QObject* parent = 0);