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);