From 82fa8ab292dc6e3a5a891f3b17c53236911374d1 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Thu, 29 Apr 2021 19:09:16 +0200 Subject: [PATCH] Highlight navigated to message --- resources/qml/ForwardCompleter.qml | 8 ++-- resources/qml/MatrixText.qml | 2 +- resources/qml/MessageView.qml | 49 ++++++++++++++++++++++++ resources/qml/delegates/ImageMessage.qml | 2 +- resources/qml/delegates/Reply.qml | 2 +- src/ChatPage.cpp | 7 +++- src/timeline/TimelineModel.cpp | 38 ++++++++++++++++++ src/timeline/TimelineModel.h | 14 +++++++ src/timeline/TimelineViewManager.cpp | 16 ++++++++ src/timeline/TimelineViewManager.h | 1 + 10 files changed, 131 insertions(+), 8 deletions(-) diff --git a/resources/qml/ForwardCompleter.qml b/resources/qml/ForwardCompleter.qml index c544378b..408cab12 100644 --- a/resources/qml/ForwardCompleter.qml +++ b/resources/qml/ForwardCompleter.qml @@ -25,10 +25,6 @@ Popup { height: implicitHeight + completerPopup.height + padding * 2 leftPadding: 10 rightPadding: 10 - background: Rectangle { - color: colors.window - } - onOpened: { completerPopup.open(); roomTextInput.forceActiveFocus(); @@ -110,6 +106,10 @@ Popup { target: completerPopup } + background: Rectangle { + color: colors.window + } + Overlay.modal: Rectangle { color: Qt.rgba(colors.window.r, colors.window.g, colors.window.b, 0.7) } diff --git a/resources/qml/MatrixText.qml b/resources/qml/MatrixText.qml index 0876e610..7cfa6735 100644 --- a/resources/qml/MatrixText.qml +++ b/resources/qml/MatrixText.qml @@ -14,7 +14,7 @@ TextEdit { selectByMouse: !Settings.mobileMode enabled: selectByMouse color: colors.text - onLinkActivated: TimelineManager.openLink(link); + onLinkActivated: TimelineManager.openLink(link) ToolTip.visible: hoveredLink ToolTip.text: hoveredLink diff --git a/resources/qml/MessageView.qml b/resources/qml/MessageView.qml index bcbc72f2..59e01599 100644 --- a/resources/qml/MessageView.qml +++ b/resources/qml/MessageView.qml @@ -258,6 +258,7 @@ ScrollView { onRoomAvatarUrlChanged: { messageUserAvatar.url = modelData ? chat.model.avatarUrl(modelData.userId).replace("mxc://", "image://MxcImage/") : ""; } + onScrollToIndex: chat.positionViewAtIndex(index, ListView.Visible) } Label { @@ -302,10 +303,58 @@ ScrollView { delegate: Item { id: wrapper + property bool scrolledToThis: model.id === chat.model.scrollTarget && (y + height > chat.y + chat.contentY && y < chat.y + chat.height + chat.contentY) + anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined width: chat.delegateMaxWidth height: section ? section.height + timelinerow.height : timelinerow.height + Rectangle { + id: scrollHighlight + + opacity: 0 + visible: true + anchors.fill: timelinerow + color: colors.highlight + + states: State { + name: "revealed" + when: wrapper.scrolledToThis + } + + transitions: Transition { + from: "" + to: "revealed" + + SequentialAnimation { + PropertyAnimation { + target: scrollHighlight + properties: "opacity" + easing.type: Easing.InOutQuad + from: 0 + to: 1 + duration: 500 + } + + PropertyAnimation { + target: scrollHighlight + properties: "opacity" + easing.type: Easing.InOutQuad + from: 1 + to: 0 + duration: 500 + } + + ScriptAction { + script: chat.model.eventShown() + } + + } + + } + + } + Loader { id: section diff --git a/resources/qml/delegates/ImageMessage.qml b/resources/qml/delegates/ImageMessage.qml index 0c863c86..8fcf3f82 100644 --- a/resources/qml/delegates/ImageMessage.qml +++ b/resources/qml/delegates/ImageMessage.qml @@ -8,7 +8,7 @@ import im.nheko 1.0 Item { property double tempWidth: Math.min(parent ? parent.width : undefined, model.data.width < 1 ? parent.width : model.data.width) property double tempHeight: tempWidth * model.data.proportionalHeight - property double divisor: model.isReply ? 4 : 2 + property double divisor: model.isReply ? 5 : 3 property bool tooHigh: tempHeight > timelineRoot.height / divisor height: Math.round(tooHigh ? timelineRoot.height / divisor : tempHeight) diff --git a/resources/qml/delegates/Reply.qml b/resources/qml/delegates/Reply.qml index dc2124d5..603d0cf6 100644 --- a/resources/qml/delegates/Reply.qml +++ b/resources/qml/delegates/Reply.qml @@ -18,7 +18,7 @@ Item { height: replyContainer.height TapHandler { - onSingleTapped: chat.positionViewAtIndex(chat.model.idToIndex(modelData.id), ListView.Contain) + onSingleTapped: chat.model.showEvent(modelData.id) gesturePolicy: TapHandler.ReleaseWithinBounds } diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp index e1f631d7..2d7c75d1 100644 --- a/src/ChatPage.cpp +++ b/src/ChatPage.cpp @@ -1376,7 +1376,7 @@ ChatPage::handleMatrixUri(const QByteArray &uri) return; QString mxid2; - if (segments.size() == 4 && segments[2] == "event") { + if (segments.size() == 4 && segments[2] == "e") { if (segments[3].isEmpty()) return; else @@ -1410,6 +1410,8 @@ ChatPage::handleMatrixUri(const QByteArray &uri) for (auto roomid : joined_rooms) { if (roomid == targetRoomId) { room_list_->highlightSelectedRoom(mxid1); + if (!mxid2.isEmpty()) + view_manager_->showEvent(mxid1, mxid2); return; } } @@ -1427,6 +1429,9 @@ ChatPage::handleMatrixUri(const QByteArray &uri) if (aliases->alias == targetRoomAlias) { room_list_->highlightSelectedRoom( QString::fromStdString(roomid)); + if (!mxid2.isEmpty()) + view_manager_->showEvent( + QString::fromStdString(roomid), mxid2); return; } } diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index 30ce176e..5a0f9bad 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -265,6 +265,8 @@ TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObj connect(&events, &EventStore::updateFlowEventId, this, [this](std::string event_id) { this->updateFlowEventId(event_id); }); + + showEventTimer.callOnTimeout(this, &TimelineModel::scrollTimerEvent); } QHash @@ -1298,6 +1300,42 @@ TimelineModel::cacheMedia(QString eventId) cacheMedia(eventId, NULL); } +void +TimelineModel::showEvent(QString eventId) +{ + using namespace std::chrono_literals; + if (idToIndex(eventId) != -1) { + eventIdToShow = eventId; + emit scrollTargetChanged(); + showEventTimer.start(50ms); + } +} + +void +TimelineModel::eventShown() +{ + eventIdToShow.clear(); + emit scrollTargetChanged(); +} + +QString +TimelineModel::scrollTarget() const +{ + return eventIdToShow; +} + +void +TimelineModel::scrollTimerEvent() +{ + if (eventIdToShow.isEmpty() || showEventTimerCounter > 3) { + showEventTimer.stop(); + showEventTimerCounter = 0; + } else { + emit scrollToIndex(idToIndex(eventIdToShow)); + showEventTimerCounter++; + } +} + QString TimelineModel::formatTypingUsers(const std::vector &users, QColor bg) { diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h index fbe963d2..f46409b3 100644 --- a/src/timeline/TimelineModel.h +++ b/src/timeline/TimelineModel.h @@ -9,6 +9,7 @@ #include #include #include +#include #include @@ -149,6 +150,7 @@ class TimelineModel : public QAbstractListModel int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged) Q_PROPERTY(std::vector typingUsers READ typingUsers WRITE updateTypingUsers NOTIFY typingUsersChanged) + Q_PROPERTY(QString scrollTarget READ scrollTarget NOTIFY scrollTargetChanged) Q_PROPERTY(QString reply READ reply WRITE setReply NOTIFY replyChanged RESET resetReply) Q_PROPERTY(QString edit READ edit WRITE setEdit NOTIFY editChanged RESET resetEdit) Q_PROPERTY( @@ -232,6 +234,7 @@ public: Q_INVOKABLE void openMedia(QString eventId); Q_INVOKABLE void cacheMedia(QString eventId); Q_INVOKABLE bool saveMedia(QString eventId) const; + Q_INVOKABLE void showEvent(QString eventId); void cacheMedia(QString eventId, std::function callback); std::vector<::Reaction> reactions(const std::string &event_id) @@ -253,6 +256,7 @@ public: public slots: void setCurrentIndex(int index); int currentIndex() const { return idToIndex(currentId); } + void eventShown(); void markEventsAsRead(const std::vector &event_ids); QVariantMap getDump(QString eventId, QString relatedTo) const; void updateTypingUsers(const std::vector &users) @@ -298,8 +302,11 @@ public slots: QString roomAvatarUrl() const; QString roomId() const { return room_id_; } + QString scrollTarget() const; + private slots: void addPendingMessage(mtx::events::collections::TimelineEvents event); + void scrollTimerEvent(); signals: void currentIndexChanged(int index); @@ -312,6 +319,7 @@ signals: void editChanged(QString reply); void paginationInProgressChanged(const bool); void newCallEvent(const mtx::events::collections::TimelineEvents &event); + void scrollToIndex(int index); void openProfile(UserProfile *profile); void openRoomSettingsDialog(RoomSettings *settings); @@ -325,6 +333,8 @@ signals: void roomAvatarUrlChanged(); void forwardToRoom(mtx::events::collections::TimelineEvents *e, QString roomId); + void scrollTargetChanged(); + private: template void sendEncryptedMessage(mtx::events::RoomEvent msg, mtx::events::EventType eventType); @@ -350,6 +360,10 @@ private: InputBar input_{this}; + QTimer showEventTimer{this}; + QString eventIdToShow; + int showEventTimerCounter = 0; + friend struct SendMessageVisitor; }; diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index 648b499d..99f0b86e 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -404,6 +404,22 @@ TimelineViewManager::highlightRoom(const QString &room_id) ChatPage::instance()->highlightRoom(room_id); } +void +TimelineViewManager::showEvent(const QString &room_id, const QString &event_id) +{ + auto room = models.find(room_id); + if (room != models.end()) { + if (timeline_ != room.value().data()) { + timeline_ = room.value().data(); + emit activeTimelineChanged(timeline_); + container->setFocus(); + nhlog::ui()->info("Activated room {}", room_id.toStdString()); + } + + timeline_->showEvent(event_id); + } +} + QString TimelineViewManager::escapeEmoji(QString str) const { diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h index 9703ee56..aa24b220 100644 --- a/src/timeline/TimelineViewManager.h +++ b/src/timeline/TimelineViewManager.h @@ -106,6 +106,7 @@ public slots: void setHistoryView(const QString &room_id); void highlightRoom(const QString &room_id); + void showEvent(const QString &room_id, const QString &event_id); void focusTimeline(); TimelineModel *getHistoryView(const QString &room_id) {