Highlight navigated to message

This commit is contained in:
Nicolas Werner 2021-04-29 19:09:16 +02:00
parent 620b6e8838
commit 82fa8ab292
No known key found for this signature in database
GPG Key ID: C8D75E610773F2D9
10 changed files with 131 additions and 8 deletions

View File

@ -25,10 +25,6 @@ Popup {
height: implicitHeight + completerPopup.height + padding * 2 height: implicitHeight + completerPopup.height + padding * 2
leftPadding: 10 leftPadding: 10
rightPadding: 10 rightPadding: 10
background: Rectangle {
color: colors.window
}
onOpened: { onOpened: {
completerPopup.open(); completerPopup.open();
roomTextInput.forceActiveFocus(); roomTextInput.forceActiveFocus();
@ -110,6 +106,10 @@ Popup {
target: completerPopup target: completerPopup
} }
background: Rectangle {
color: colors.window
}
Overlay.modal: Rectangle { Overlay.modal: Rectangle {
color: Qt.rgba(colors.window.r, colors.window.g, colors.window.b, 0.7) color: Qt.rgba(colors.window.r, colors.window.g, colors.window.b, 0.7)
} }

View File

@ -14,7 +14,7 @@ TextEdit {
selectByMouse: !Settings.mobileMode selectByMouse: !Settings.mobileMode
enabled: selectByMouse enabled: selectByMouse
color: colors.text color: colors.text
onLinkActivated: TimelineManager.openLink(link); onLinkActivated: TimelineManager.openLink(link)
ToolTip.visible: hoveredLink ToolTip.visible: hoveredLink
ToolTip.text: hoveredLink ToolTip.text: hoveredLink

View File

@ -258,6 +258,7 @@ ScrollView {
onRoomAvatarUrlChanged: { onRoomAvatarUrlChanged: {
messageUserAvatar.url = modelData ? chat.model.avatarUrl(modelData.userId).replace("mxc://", "image://MxcImage/") : ""; messageUserAvatar.url = modelData ? chat.model.avatarUrl(modelData.userId).replace("mxc://", "image://MxcImage/") : "";
} }
onScrollToIndex: chat.positionViewAtIndex(index, ListView.Visible)
} }
Label { Label {
@ -302,10 +303,58 @@ ScrollView {
delegate: Item { delegate: Item {
id: wrapper 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 anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
width: chat.delegateMaxWidth width: chat.delegateMaxWidth
height: section ? section.height + timelinerow.height : timelinerow.height 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 { Loader {
id: section id: section

View File

@ -8,7 +8,7 @@ import im.nheko 1.0
Item { Item {
property double tempWidth: Math.min(parent ? parent.width : undefined, model.data.width < 1 ? parent.width : model.data.width) 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 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 property bool tooHigh: tempHeight > timelineRoot.height / divisor
height: Math.round(tooHigh ? timelineRoot.height / divisor : tempHeight) height: Math.round(tooHigh ? timelineRoot.height / divisor : tempHeight)

View File

@ -18,7 +18,7 @@ Item {
height: replyContainer.height height: replyContainer.height
TapHandler { TapHandler {
onSingleTapped: chat.positionViewAtIndex(chat.model.idToIndex(modelData.id), ListView.Contain) onSingleTapped: chat.model.showEvent(modelData.id)
gesturePolicy: TapHandler.ReleaseWithinBounds gesturePolicy: TapHandler.ReleaseWithinBounds
} }

View File

@ -1376,7 +1376,7 @@ ChatPage::handleMatrixUri(const QByteArray &uri)
return; return;
QString mxid2; QString mxid2;
if (segments.size() == 4 && segments[2] == "event") { if (segments.size() == 4 && segments[2] == "e") {
if (segments[3].isEmpty()) if (segments[3].isEmpty())
return; return;
else else
@ -1410,6 +1410,8 @@ ChatPage::handleMatrixUri(const QByteArray &uri)
for (auto roomid : joined_rooms) { for (auto roomid : joined_rooms) {
if (roomid == targetRoomId) { if (roomid == targetRoomId) {
room_list_->highlightSelectedRoom(mxid1); room_list_->highlightSelectedRoom(mxid1);
if (!mxid2.isEmpty())
view_manager_->showEvent(mxid1, mxid2);
return; return;
} }
} }
@ -1427,6 +1429,9 @@ ChatPage::handleMatrixUri(const QByteArray &uri)
if (aliases->alias == targetRoomAlias) { if (aliases->alias == targetRoomAlias) {
room_list_->highlightSelectedRoom( room_list_->highlightSelectedRoom(
QString::fromStdString(roomid)); QString::fromStdString(roomid));
if (!mxid2.isEmpty())
view_manager_->showEvent(
QString::fromStdString(roomid), mxid2);
return; return;
} }
} }

View File

@ -265,6 +265,8 @@ TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObj
connect(&events, &EventStore::updateFlowEventId, this, [this](std::string event_id) { connect(&events, &EventStore::updateFlowEventId, this, [this](std::string event_id) {
this->updateFlowEventId(event_id); this->updateFlowEventId(event_id);
}); });
showEventTimer.callOnTimeout(this, &TimelineModel::scrollTimerEvent);
} }
QHash<int, QByteArray> QHash<int, QByteArray>
@ -1298,6 +1300,42 @@ TimelineModel::cacheMedia(QString eventId)
cacheMedia(eventId, NULL); 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 QString
TimelineModel::formatTypingUsers(const std::vector<QString> &users, QColor bg) TimelineModel::formatTypingUsers(const std::vector<QString> &users, QColor bg)
{ {

View File

@ -9,6 +9,7 @@
#include <QDate> #include <QDate>
#include <QHash> #include <QHash>
#include <QSet> #include <QSet>
#include <QTimer>
#include <mtxclient/http/errors.hpp> #include <mtxclient/http/errors.hpp>
@ -149,6 +150,7 @@ class TimelineModel : public QAbstractListModel
int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged) int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged)
Q_PROPERTY(std::vector<QString> typingUsers READ typingUsers WRITE updateTypingUsers NOTIFY Q_PROPERTY(std::vector<QString> typingUsers READ typingUsers WRITE updateTypingUsers NOTIFY
typingUsersChanged) typingUsersChanged)
Q_PROPERTY(QString scrollTarget READ scrollTarget NOTIFY scrollTargetChanged)
Q_PROPERTY(QString reply READ reply WRITE setReply NOTIFY replyChanged RESET resetReply) 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(QString edit READ edit WRITE setEdit NOTIFY editChanged RESET resetEdit)
Q_PROPERTY( Q_PROPERTY(
@ -232,6 +234,7 @@ public:
Q_INVOKABLE void openMedia(QString eventId); Q_INVOKABLE void openMedia(QString eventId);
Q_INVOKABLE void cacheMedia(QString eventId); Q_INVOKABLE void cacheMedia(QString eventId);
Q_INVOKABLE bool saveMedia(QString eventId) const; Q_INVOKABLE bool saveMedia(QString eventId) const;
Q_INVOKABLE void showEvent(QString eventId);
void cacheMedia(QString eventId, std::function<void(const QString filename)> callback); void cacheMedia(QString eventId, std::function<void(const QString filename)> callback);
std::vector<::Reaction> reactions(const std::string &event_id) std::vector<::Reaction> reactions(const std::string &event_id)
@ -253,6 +256,7 @@ public:
public slots: public slots:
void setCurrentIndex(int index); void setCurrentIndex(int index);
int currentIndex() const { return idToIndex(currentId); } int currentIndex() const { return idToIndex(currentId); }
void eventShown();
void markEventsAsRead(const std::vector<QString> &event_ids); void markEventsAsRead(const std::vector<QString> &event_ids);
QVariantMap getDump(QString eventId, QString relatedTo) const; QVariantMap getDump(QString eventId, QString relatedTo) const;
void updateTypingUsers(const std::vector<QString> &users) void updateTypingUsers(const std::vector<QString> &users)
@ -298,8 +302,11 @@ public slots:
QString roomAvatarUrl() const; QString roomAvatarUrl() const;
QString roomId() const { return room_id_; } QString roomId() const { return room_id_; }
QString scrollTarget() const;
private slots: private slots:
void addPendingMessage(mtx::events::collections::TimelineEvents event); void addPendingMessage(mtx::events::collections::TimelineEvents event);
void scrollTimerEvent();
signals: signals:
void currentIndexChanged(int index); void currentIndexChanged(int index);
@ -312,6 +319,7 @@ signals:
void editChanged(QString reply); void editChanged(QString reply);
void paginationInProgressChanged(const bool); void paginationInProgressChanged(const bool);
void newCallEvent(const mtx::events::collections::TimelineEvents &event); void newCallEvent(const mtx::events::collections::TimelineEvents &event);
void scrollToIndex(int index);
void openProfile(UserProfile *profile); void openProfile(UserProfile *profile);
void openRoomSettingsDialog(RoomSettings *settings); void openRoomSettingsDialog(RoomSettings *settings);
@ -325,6 +333,8 @@ signals:
void roomAvatarUrlChanged(); void roomAvatarUrlChanged();
void forwardToRoom(mtx::events::collections::TimelineEvents *e, QString roomId); void forwardToRoom(mtx::events::collections::TimelineEvents *e, QString roomId);
void scrollTargetChanged();
private: private:
template<typename T> template<typename T>
void sendEncryptedMessage(mtx::events::RoomEvent<T> msg, mtx::events::EventType eventType); void sendEncryptedMessage(mtx::events::RoomEvent<T> msg, mtx::events::EventType eventType);
@ -350,6 +360,10 @@ private:
InputBar input_{this}; InputBar input_{this};
QTimer showEventTimer{this};
QString eventIdToShow;
int showEventTimerCounter = 0;
friend struct SendMessageVisitor; friend struct SendMessageVisitor;
}; };

View File

@ -404,6 +404,22 @@ TimelineViewManager::highlightRoom(const QString &room_id)
ChatPage::instance()->highlightRoom(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 QString
TimelineViewManager::escapeEmoji(QString str) const TimelineViewManager::escapeEmoji(QString str) const
{ {

View File

@ -106,6 +106,7 @@ public slots:
void setHistoryView(const QString &room_id); void setHistoryView(const QString &room_id);
void highlightRoom(const QString &room_id); void highlightRoom(const QString &room_id);
void showEvent(const QString &room_id, const QString &event_id);
void focusTimeline(); void focusTimeline();
TimelineModel *getHistoryView(const QString &room_id) TimelineModel *getHistoryView(const QString &room_id)
{ {