Add a button to cycle through unread highlights

BUG: 465095
This commit is contained in:
Azhar Momin
2025-12-28 23:02:15 +05:30
committed by James Graham
parent fd44ff972a
commit 58ea229b67
3 changed files with 121 additions and 20 deletions

View File

@@ -169,6 +169,7 @@ NeoChatRoom::NeoChatRoom(Connection *connection, QString roomId, JoinState joinS
const auto neochatconnection = static_cast<NeoChatConnection *>(connection); const auto neochatconnection = static_cast<NeoChatConnection *>(connection);
Q_ASSERT(neochatconnection); Q_ASSERT(neochatconnection);
connect(neochatconnection, &NeoChatConnection::globalUrlPreviewEnabledChanged, this, &NeoChatRoom::urlPreviewEnabledChanged); connect(neochatconnection, &NeoChatConnection::globalUrlPreviewEnabledChanged, this, &NeoChatRoom::urlPreviewEnabledChanged);
connect(this, &Room::fullyReadMarkerMoved, this, &NeoChatRoom::invalidateLastUnreadHighlightId);
} }
bool NeoChatRoom::visible() const bool NeoChatRoom::visible() const
@@ -1843,4 +1844,52 @@ void NeoChatRoom::report(const QString &reason)
connection()->callApi<NeochatReportRoomJob>(id(), reason); connection()->callApi<NeochatReportRoomJob>(id(), reason);
} }
QString NeoChatRoom::findNextUnreadHighlightId()
{
const QString startEventId = !m_lastUnreadHighlightId.isEmpty() ? m_lastUnreadHighlightId : lastFullyReadEventId();
const auto startIt = findInTimeline(startEventId);
if (startIt == historyEdge()) {
return {};
}
for (auto it = startIt.base(); it != messageEvents().cend(); ++it) {
const RoomEvent *ev = it->event();
if (highlights.contains(ev)) {
m_lastUnreadHighlightId = ev->id();
Q_EMIT highlightCycleStartedChanged();
return m_lastUnreadHighlightId;
}
}
if (!m_lastUnreadHighlightId.isEmpty()) {
m_lastUnreadHighlightId.clear();
Q_EMIT highlightCycleStartedChanged();
return findNextUnreadHighlightId();
}
return {};
}
bool NeoChatRoom::highlightCycleStarted() const
{
return !m_lastUnreadHighlightId.isEmpty();
}
void NeoChatRoom::invalidateLastUnreadHighlightId(const QString &fromEventId, const QString &toEventId)
{
Q_UNUSED(fromEventId);
if (m_lastUnreadHighlightId.isEmpty()) {
return;
}
const auto lastIt = findInTimeline(m_lastUnreadHighlightId);
const auto newReadIt = findInTimeline(toEventId);
// opposite comparision because both are reverse iterators :p
if (newReadIt <= lastIt) {
m_lastUnreadHighlightId.clear();
Q_EMIT highlightCycleStartedChanged();
}
}
#include "moc_neochatroom.cpp" #include "moc_neochatroom.cpp"

View File

@@ -208,6 +208,11 @@ class NeoChatRoom : public Quotient::Room
*/ */
Q_PROPERTY(QString pinnedMessage READ pinnedMessage NOTIFY pinnedMessageChanged) Q_PROPERTY(QString pinnedMessage READ pinnedMessage NOTIFY pinnedMessageChanged)
/**
* @brief Whether the highlight finding cycle has started.
*/
Q_PROPERTY(bool highlightCycleStarted READ highlightCycleStarted NOTIFY highlightCycleStartedChanged)
public: public:
explicit NeoChatRoom(Quotient::Connection *connection, QString roomId, Quotient::JoinState joinState = {}); explicit NeoChatRoom(Quotient::Connection *connection, QString roomId, Quotient::JoinState joinState = {});
@@ -627,6 +632,19 @@ public:
*/ */
Q_INVOKABLE void report(const QString &reason); Q_INVOKABLE void report(const QString &reason);
/**
* @brief Returns the ID of the next unread highlight in the room.
*
* Each call advances the internal highlight cursor. Once the last unread highlight
* is reached, the cycle is reset.
*/
Q_INVOKABLE QString findNextUnreadHighlightId();
/**
* @brief Whether the highlight finding cycle has started.
*/
bool highlightCycleStarted() const;
private: private:
bool m_visible = false; bool m_visible = false;
@@ -662,11 +680,15 @@ private:
QString m_pinnedMessage; QString m_pinnedMessage;
void loadPinnedMessage(); void loadPinnedMessage();
QString m_lastUnreadHighlightId;
private Q_SLOTS: private Q_SLOTS:
void updatePushNotificationState(QString type); void updatePushNotificationState(QString type);
void cacheLastEvent(); void cacheLastEvent();
void invalidateLastUnreadHighlightId(const QString &fromEventId, const QString &toEventId);
Q_SIGNALS: Q_SIGNALS:
void cachedInputChanged(); void cachedInputChanged();
void busyChanged(); void busyChanged();
@@ -692,6 +714,7 @@ Q_SIGNALS:
void extraEventNotFound(const QString &eventId); void extraEventNotFound(const QString &eventId);
void inviteTimestampChanged(); void inviteTimestampChanged();
void pinnedMessageChanged(); void pinnedMessageChanged();
void highlightCycleStartedChanged();
/** /**
* @brief Request a message be shown to the user of the given type. * @brief Request a message be shown to the user of the given type.

View File

@@ -193,9 +193,7 @@ QQC2.ScrollView {
room: _private.room room: _private.room
} }
KirigamiComponents.FloatingButton { ColumnLayout {
id: goReadMarkerFab
anchors { anchors {
right: parent.right right: parent.right
top: parent.top top: parent.top
@@ -203,28 +201,59 @@ QQC2.ScrollView {
rightMargin: Kirigami.Units.largeSpacing rightMargin: Kirigami.Units.largeSpacing
} }
implicitWidth: Kirigami.Settings.hasTransientTouchInput ? Kirigami.Units.gridUnit * 3 : Kirigami.Units.gridUnit * 2 spacing: Kirigami.Units.largeSpacing
implicitHeight: Kirigami.Settings.hasTransientTouchInput ? Kirigami.Units.gridUnit * 3 : Kirigami.Units.gridUnit * 2
padding: Kirigami.Units.largeSpacing KirigamiComponents.FloatingButton {
id: goReadMarkerFab
z: 2 implicitWidth: Kirigami.Settings.hasTransientTouchInput ? Kirigami.Units.gridUnit * 3 : Kirigami.Units.gridUnit * 2
visible: !_private.room?.partiallyReadStats.empty() implicitHeight: Kirigami.Settings.hasTransientTouchInput ? Kirigami.Units.gridUnit * 3 : Kirigami.Units.gridUnit * 2
text: _private.room.readMarkerLoaded ? i18nc("@action:button", "Jump to first unread message") : i18nc("@action:button", "Jump to oldest loaded message") padding: Kirigami.Units.largeSpacing
icon.name: "go-up"
onClicked: { z: 2
goReadMarkerFab.textChanged() visible: !_private.room?.partiallyReadStats.empty()
root.goToEvent(_private.room.lastFullyReadEventId);
} text: _private.room.readMarkerLoaded ? i18nc("@action:button", "Jump to first unread message") : i18nc("@action:button", "Jump to oldest loaded message")
Kirigami.Action { icon.name: "go-up"
shortcut: "Shift+PgUp" onClicked: {
onTriggered: goReadMarkerFab.clicked() goReadMarkerFab.textChanged()
root.goToEvent(_private.room.lastFullyReadEventId);
}
Kirigami.Action {
shortcut: "Shift+PgUp"
onTriggered: goReadMarkerFab.clicked()
}
QQC2.ToolTip.text: goReadMarkerFab.text
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
QQC2.ToolTip.visible: goReadMarkerFab.hovered
} }
KirigamiComponents.FloatingButton {
id: goUnreadHighlightFab
QQC2.ToolTip.text: goReadMarkerFab.text implicitWidth: Kirigami.Settings.hasTransientTouchInput ? Kirigami.Units.gridUnit * 3 : Kirigami.Units.gridUnit * 2
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay implicitHeight: Kirigami.Settings.hasTransientTouchInput ? Kirigami.Units.gridUnit * 3 : Kirigami.Units.gridUnit * 2
QQC2.ToolTip.visible: goReadMarkerFab.hovered
padding: Kirigami.Units.largeSpacing
z: 2
visible: _private.room?.highlightCount > 0
text: _private.room?.highlightCycleStarted ? i18nc("@action:button", "Jump to next unread highlight") : i18nc("@action:button", "Jump to first unread message")
icon.name: "mail-unread-symbolic"
onClicked: {
const eventId = _private.room.findNextUnreadHighlightId();
if (eventId !== "") {
goUnreadHighlightFab.textChanged();
root.goToEvent(eventId);
}
}
QQC2.ToolTip.text: goUnreadHighlightFab.text
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
QQC2.ToolTip.visible: goUnreadHighlightFab.hovered
}
} }
KirigamiComponents.FloatingButton { KirigamiComponents.FloatingButton {
id: goMarkAsReadFab id: goMarkAsReadFab