From 946ab4d0f287307c24e310c6d2faef931f094ec5 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Fri, 3 Jan 2020 23:21:33 +0100 Subject: [PATCH] invert timeline --- resources/qml/TimelineView.qml | 56 ++++++++++++++++++++++++++++------ src/timeline/TimelineModel.cpp | 52 +++++++++++++++++++++++-------- src/timeline/TimelineModel.h | 1 + 3 files changed, 88 insertions(+), 21 deletions(-) diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml index 1a1900a..6bc2eb5 100644 --- a/resources/qml/TimelineView.qml +++ b/resources/qml/TimelineView.qml @@ -115,30 +115,68 @@ Item { onMovementEnded: updatePosition() spacing: 4 - delegate: TimelineRow { + verticalLayoutDirection: ListView.BottomToTop + + delegate: Rectangle { + // This would normally be previousSection, but our model's order is inverted. + property bool sectionBoundary: (ListView.nextSection != "" && ListView.nextSection !== ListView.section) || model.index === chat.count - 1 + + id: wrapper + property Item section + width: chat.width + height: section ? section.height + timelinerow.height : timelinerow.height + + TimelineRow { + id: timelinerow + y: section ? section.y + section.height : 0 + } function isFullyVisible() { return height > 1 && (y - chat.contentY - 1) + height < chat.height } function getIndex() { return index; } + + onSectionBoundaryChanged: { + if (sectionBoundary) { + var properties = { + 'modelData': model.dump, + 'section': ListView.section, + 'nextSection': ListView.nextSection + } + section = sectionHeader.createObject(wrapper, properties) + } else { + section.destroy() + section = null + } + } + } section { property: "section" - delegate: Column { + } + Component { + id: sectionHeader + Column { + property var modelData + property string section + property string nextSection + topPadding: 4 bottomPadding: 4 spacing: 8 + visible: !!modelData + width: parent.width height: (section.includes(" ") ? dateBubble.height + 8 + userName.height : userName.height) + 8 Label { id: dateBubble - anchors.horizontalCenter: parent.horizontalCenter + anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined visible: section.includes(" ") - text: chat.model.formatDateSeparator(new Date(Number(section.split(" ")[1]))) + text: chat.model.formatDateSeparator(modelData.timestamp) color: colors.windowText height: contentHeight * 1.2 @@ -155,20 +193,20 @@ Item { Avatar { width: avatarSize height: avatarSize - url: chat.model.avatarUrl(section.split(" ")[0]).replace("mxc://", "image://MxcImage/") - displayName: chat.model.displayName(section.split(" ")[0]) + url: chat.model.avatarUrl(modelData.userId).replace("mxc://", "image://MxcImage/") + displayName: modelData.userName MouseArea { anchors.fill: parent - onClicked: chat.model.openUserProfile(section.split(" ")[0]) + onClicked: chat.model.openUserProfile(modelData.userId) cursorShape: Qt.PointingHandCursor } } Text { id: userName - text: chat.model.escapeEmoji(chat.model.displayName(section.split(" ")[0])) - color: chat.model.userColor(section.split(" ")[0], colors.window) + text: chat.model.escapeEmoji(modelData.userName) + color: chat.model.userColor(modelData.userId, colors.window) textFormat: Text.RichText MouseArea { diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index 593a21d..8746a31 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -202,6 +202,7 @@ TimelineModel::roleNames() const {ReplyTo, "replyTo"}, {RoomName, "roomName"}, {RoomTopic, "roomTopic"}, + {Dump, "dump"}, }; } int @@ -235,7 +236,7 @@ TimelineModel::data(const QModelIndex &index, int role) const std::string userId = acc::sender(event); - for (int r = index.row() - 1; r > 0; r--) { + for (size_t r = index.row() + 1; r < eventOrder.size(); r++) { auto tempEv = events.value(eventOrder[r]); QDateTime prevDate = origin_server_ts(tempEv); prevDate.setTime(QTime()); @@ -314,6 +315,35 @@ TimelineModel::data(const QModelIndex &index, int role) const return QVariant(QString::fromStdString(room_name(event))); case RoomTopic: return QVariant(QString::fromStdString(room_topic(event))); + case Dump: { + QVariantMap m; + auto names = roleNames(); + + // m.insert(names[Section], data(index, static_cast(Section))); + m.insert(names[Type], data(index, static_cast(Type))); + m.insert(names[Body], data(index, static_cast(Body))); + m.insert(names[FormattedBody], data(index, static_cast(FormattedBody))); + m.insert(names[UserId], data(index, static_cast(UserId))); + m.insert(names[UserName], data(index, static_cast(UserName))); + m.insert(names[Timestamp], data(index, static_cast(Timestamp))); + m.insert(names[Url], data(index, static_cast(Url))); + m.insert(names[ThumbnailUrl], data(index, static_cast(ThumbnailUrl))); + m.insert(names[Filename], data(index, static_cast(Filename))); + m.insert(names[Filesize], data(index, static_cast(Filesize))); + m.insert(names[MimeType], data(index, static_cast(MimeType))); + m.insert(names[Height], data(index, static_cast(Height))); + m.insert(names[Width], data(index, static_cast(Width))); + m.insert(names[ProportionalHeight], + data(index, static_cast(ProportionalHeight))); + m.insert(names[Id], data(index, static_cast(Id))); + m.insert(names[State], data(index, static_cast(State))); + m.insert(names[IsEncrypted], data(index, static_cast(IsEncrypted))); + m.insert(names[ReplyTo], data(index, static_cast(ReplyTo))); + m.insert(names[RoomName], data(index, static_cast(RoomName))); + m.insert(names[RoomTopic], data(index, static_cast(RoomTopic))); + + return QVariant(m); + } default: return QVariant(); } @@ -335,10 +365,8 @@ TimelineModel::addEvents(const mtx::responses::Timeline &timeline) if (ids.empty()) return; - beginInsertRows(QModelIndex(), - static_cast(this->eventOrder.size()), - static_cast(this->eventOrder.size() + ids.size() - 1)); - this->eventOrder.insert(this->eventOrder.end(), ids.begin(), ids.end()); + beginInsertRows(QModelIndex(), 0, static_cast(ids.size() - 1)); + this->eventOrder.insert(this->eventOrder.begin(), ids.rbegin(), ids.rend()); endInsertRows(); updateLastMessage(); @@ -362,7 +390,7 @@ isMessage(const mtx::events::Event &) void TimelineModel::updateLastMessage() { - for (auto it = eventOrder.rbegin(); it != eventOrder.rend(); ++it) { + for (auto it = eventOrder.begin(); it != eventOrder.end(); ++it) { auto event = events.value(*it); if (auto e = std::get_if>( &event)) { @@ -499,8 +527,10 @@ TimelineModel::addBackwardsEvents(const mtx::responses::Messages &msgs) std::vector ids = internalAddEvents(msgs.chunk); if (!ids.empty()) { - beginInsertRows(QModelIndex(), 0, static_cast(ids.size() - 1)); - this->eventOrder.insert(this->eventOrder.begin(), ids.rbegin(), ids.rend()); + beginInsertRows(QModelIndex(), + static_cast(this->eventOrder.size()), + static_cast(this->eventOrder.size() + ids.size() - 1)); + this->eventOrder.insert(this->eventOrder.end(), ids.begin(), ids.end()); endInsertRows(); } @@ -1120,11 +1150,9 @@ TimelineModel::addPendingMessage(mtx::events::collections::TimelineEvents event) internalAddEvents({event}); QString txn_id_qstr = QString::fromStdString(mtx::accessors::event_id(event)); - beginInsertRows(QModelIndex(), - static_cast(this->eventOrder.size()), - static_cast(this->eventOrder.size())); + beginInsertRows(QModelIndex(), 0, 0); pending.push_back(txn_id_qstr); - this->eventOrder.insert(this->eventOrder.end(), txn_id_qstr); + this->eventOrder.insert(this->eventOrder.begin(), txn_id_qstr); endInsertRows(); updateLastMessage(); diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h index 7ff80c4..4161a0f 100644 --- a/src/timeline/TimelineModel.h +++ b/src/timeline/TimelineModel.h @@ -147,6 +147,7 @@ public: ReplyTo, RoomName, RoomTopic, + Dump, }; QHash roleNames() const override;