diff --git a/resources/qml/MessageInput.qml b/resources/qml/MessageInput.qml index 9e9e5b7a..3759477f 100644 --- a/resources/qml/MessageInput.qml +++ b/resources/qml/MessageInput.qml @@ -136,9 +136,9 @@ Rectangle { padding: 0 focus: true onTextChanged: { - if (TimelineManager.timeline) { + if (TimelineManager.timeline) TimelineManager.timeline.input.updateState(selectionStart, selectionEnd, cursorPosition, text); - } + } onCursorRectangleChanged: textInput.ensureVisible(cursorRectangle) onCursorPositionChanged: { diff --git a/resources/qml/MessageView.qml b/resources/qml/MessageView.qml index 0858be83..dae3e5d7 100644 --- a/resources/qml/MessageView.qml +++ b/resources/qml/MessageView.qml @@ -66,33 +66,25 @@ ListView { } } - section { - property: "section" - } - 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 + visible: modelData && (modelData.previousMessageUserId !== modelData.userId || modelData.previousMessageDay !== modelData.day) + width: parentWidth + height: ((modelData && modelData.previousMessageDay !== modelData.day) ? dateBubble.height + 8 + userName.height : userName.height) + 8 Label { id: dateBubble anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined - visible: section.includes(" ") - text: chat.model.formatDateSeparator(modelData.timestamp) + visible: modelData && modelData.previousMessageDay !== modelData.day + text: modelData ? chat.model.formatDateSeparator(modelData.timestamp) : "" color: colors.text - height: fontMetrics.height * 1.4 + height: Math.round(fontMetrics.height * 1.4) width: contentWidth * 1.2 horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter @@ -109,26 +101,19 @@ ListView { spacing: 8 Avatar { - // MouseArea { - // anchors.fill: parent - // onClicked: chat.model.openUserProfile(modelData.userId) - // cursorShape: Qt.PointingHandCursor - // propagateComposedEvents: true - // } - width: avatarSize height: avatarSize - url: chat.model.avatarUrl(modelData.userId).replace("mxc://", "image://MxcImage/") - displayName: modelData.userName - userid: modelData.userId + url: modelData ? chat.model.avatarUrl(modelData.userId).replace("mxc://", "image://MxcImage/") : "" + displayName: modelData ? modelData.userName : "" + userid: modelData ? modelData.userId : "" onClicked: chat.model.openUserProfile(modelData.userId) } Label { id: userName - text: TimelineManager.escapeEmoji(modelData.userName) - color: TimelineManager.userColor(modelData.userId, colors.window) + text: modelData ? TimelineManager.escapeEmoji(modelData.userName) : "" + color: TimelineManager.userColor(modelData ? modelData.userId : "", colors.window) textFormat: Text.RichText MouseArea { @@ -143,7 +128,7 @@ ListView { Label { color: colors.buttonText - text: TimelineManager.userStatus(modelData.userId) + text: modelData ? TimelineManager.userStatus(modelData.userId) : "" textFormat: Text.PlainText elide: Text.ElideRight width: chat.delegateMaxWidth - parent.spacing * 2 - userName.implicitWidth - avatarSize @@ -163,31 +148,26 @@ ListView { delegate: Item { id: wrapper - // 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 - property Item section - anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined width: chat.delegateMaxWidth height: section ? section.height + timelinerow.height : timelinerow.height - onSectionBoundaryChanged: { - if (sectionBoundary) { - var properties = { - "modelData": model.dump, - "section": ListView.section, - "nextSection": ListView.nextSection - }; - section = sectionHeader.createObject(wrapper, properties); - } else { - section.destroy(); - section = null; - } + + Loader { + id: section + + property var modelData: model + property int parentWidth: parent.width + + active: model.previousMessageUserId !== undefined && model.previousMessageUserId !== model.userId || model.previousMessageDay !== model.day + //asynchronous: true + sourceComponent: sectionHeader + visible: status == Loader.Ready } TimelineRow { id: timelinerow - y: section ? section.y + section.height : 0 + y: section.active && section.visible ? section.y + section.height : 0 } Connections { diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index c6c084cc..5db6f0c2 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -265,14 +265,16 @@ QHash TimelineModel::roleNames() const { return { - {Section, "section"}, {Type, "type"}, {TypeString, "typeString"}, {IsOnlyEmoji, "isOnlyEmoji"}, {Body, "body"}, {FormattedBody, "formattedBody"}, + {PreviousMessageUserId, "previousMessageUserId"}, {UserId, "userId"}, {UserName, "userName"}, + {PreviousMessageDay, "previousMessageDay"}, + {Day, "day"}, {Timestamp, "timestamp"}, {Url, "url"}, {ThumbnailUrl, "thumbnailUrl"}, @@ -323,6 +325,11 @@ TimelineModel::data(const mtx::events::collections::TimelineEvents &event, int r case UserName: return QVariant(displayName(QString::fromStdString(acc::sender(event)))); + case Day: { + QDateTime prevDate = origin_server_ts(event); + prevDate.setTime(QTime()); + return QVariant(prevDate.toMSecsSinceEpoch()); + } case Timestamp: return QVariant(origin_server_ts(event)); case Type: @@ -450,7 +457,6 @@ TimelineModel::data(const mtx::events::collections::TimelineEvents &event, int r QVariantMap m; auto names = roleNames(); - // m.insert(names[Section], data(id, static_cast(Section))); m.insert(names[Type], data(event, static_cast(Type))); m.insert(names[TypeString], data(event, static_cast(TypeString))); m.insert(names[IsOnlyEmoji], data(event, static_cast(IsOnlyEmoji))); @@ -458,6 +464,7 @@ TimelineModel::data(const mtx::events::collections::TimelineEvents &event, int r m.insert(names[FormattedBody], data(event, static_cast(FormattedBody))); m.insert(names[UserId], data(event, static_cast(UserId))); m.insert(names[UserName], data(event, static_cast(UserName))); + m.insert(names[Day], data(event, static_cast(Day))); m.insert(names[Timestamp], data(event, static_cast(Timestamp))); m.insert(names[Url], data(event, static_cast(Url))); m.insert(names[ThumbnailUrl], data(event, static_cast(ThumbnailUrl))); @@ -498,30 +505,17 @@ TimelineModel::data(const QModelIndex &index, int role) const if (!event) return ""; - if (role == Section) { - QDateTime date = origin_server_ts(*event); - date.setTime(QTime()); - - std::string userId = acc::sender(*event); - - for (int r = rowCount() - index.row(); r < events.size(); r++) { - auto tempEv = events.get(r); - if (!tempEv) - break; - - QDateTime prevDate = origin_server_ts(*tempEv); - prevDate.setTime(QTime()); - if (prevDate != date) - return QString("%2 %1") - .arg(date.toMSecsSinceEpoch()) - .arg(QString::fromStdString(userId)); - - std::string prevUserId = acc::sender(*tempEv); - if (userId != prevUserId) - break; - } - - return QString("%1").arg(QString::fromStdString(userId)); + if (role == PreviousMessageDay || role == PreviousMessageUserId) { + int prevIdx = rowCount() - index.row() - 2; + if (prevIdx < 0) + return QVariant(); + auto tempEv = events.get(prevIdx); + if (!tempEv) + return QVariant(); + if (role == PreviousMessageUserId) + return data(*tempEv, UserId); + else + return data(*tempEv, Day); } return data(*event, role); diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h index 16a2565e..b6b3b5ae 100644 --- a/src/timeline/TimelineModel.h +++ b/src/timeline/TimelineModel.h @@ -159,14 +159,16 @@ public: enum Roles { - Section, Type, TypeString, IsOnlyEmoji, Body, FormattedBody, + PreviousMessageUserId, UserId, UserName, + PreviousMessageDay, + Day, Timestamp, Url, ThumbnailUrl,