diff --git a/resources/qml/MessageView.qml b/resources/qml/MessageView.qml
index 375b4017..62f7735b 100644
--- a/resources/qml/MessageView.qml
+++ b/resources/qml/MessageView.qml
@@ -2,6 +2,7 @@
//
// SPDX-License-Identifier: GPL-3.0-or-later
+import "./components"
import "./delegates"
import "./emoji"
import "./ui"
@@ -49,7 +50,7 @@ ScrollView {
property alias model: row.model
// use comma to update on scroll
property var attachedPos: chat.contentY, attached ? chat.mapFromItem(attached, attached ? attached.width - width : 0, -height) : null
- readonly property int padding: 4
+ readonly property int padding: Nheko.paddingSmall
visible: Settings.buttonsInTimeline && !!attached && (attached.hovered || messageActionHover.hovered)
x: attached ? attachedPos.x : 0
@@ -76,6 +77,25 @@ ScrollView {
anchors.centerIn: parent
spacing: messageActions.padding
+ Repeater {
+ model: Settings.recentReactions
+
+ delegate: TextButton {
+ required property string modelData
+
+ visible: chat.model ? chat.model.permissions.canSend(MtxEvent.Reaction) : false
+
+ height: fontMetrics.height
+ font.family: Settings.emojiFont
+
+ text: modelData
+ onClicked: {
+ room.input.reaction(row.model.eventId, modelData);
+ TimelineManager.focusMessageInput();
+ }
+ }
+ }
+
ImageButton {
id: editButton
diff --git a/resources/qml/components/TextButton.qml b/resources/qml/components/TextButton.qml
new file mode 100644
index 00000000..5dc946e7
--- /dev/null
+++ b/resources/qml/components/TextButton.qml
@@ -0,0 +1,48 @@
+// SPDX-FileCopyrightText: 2021 Nheko Contributors
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+import "../ui"
+import QtQuick 2.15
+import QtQuick.Controls 2.15
+import im.nheko 1.0 // for cursor shape
+
+AbstractButton {
+ id: button
+
+ property alias cursor: mouseArea.cursorShape
+ property color highlightColor: Nheko.colors.highlight
+ property color buttonTextColor: Nheko.colors.buttonText
+
+ focusPolicy: Qt.NoFocus
+ width: buttonText.implicitWidth
+ height: buttonText.implicitHeight
+ implicitWidth: buttonText.implicitWidth
+ implicitHeight: buttonText.implicitHeight
+
+ Label {
+ id: buttonText
+
+ anchors.centerIn: parent
+ padding: 0
+ text: button.text
+ color: button.hovered ? highlightColor : buttonTextColor
+ font: button.font
+ verticalAlignment: Text.AlignVCenter
+ horizontalAlignment: Text.AlignHCenter
+ }
+
+ CursorShape {
+ id: mouseArea
+
+ anchors.fill: parent
+ cursorShape: Qt.PointingHandCursor
+ }
+
+ Ripple {
+ color: Qt.rgba(buttonTextColor.r, buttonTextColor.g, buttonTextColor.b, 0.5)
+ clip: false
+ rippleTarget: button
+ }
+
+}
diff --git a/resources/res.qrc b/resources/res.qrc
index 67c35351..bc3a8bd2 100644
--- a/resources/res.qrc
+++ b/resources/res.qrc
@@ -114,6 +114,7 @@
qml/components/AvatarListTile.qml
qml/components/FlatButton.qml
qml/components/MainWindowDialog.qml
+ qml/components/TextButton.qml
qml/delegates/Encrypted.qml
qml/delegates/FileMessage.qml
qml/delegates/ImageMessage.qml
diff --git a/src/UserSettingsPage.cpp b/src/UserSettingsPage.cpp
index eae31b71..f8c05e38 100644
--- a/src/UserSettingsPage.cpp
+++ b/src/UserSettingsPage.cpp
@@ -118,6 +118,8 @@ UserSettings::load(std::optional profile)
deviceId_ = settings.value(prefix + "auth/device_id", "").toString();
hiddenTags_ = settings.value(prefix + "user/hidden_tags", QStringList{}).toStringList();
hiddenPins_ = settings.value(prefix + "user/hidden_pins", QStringList{}).toStringList();
+ recentReactions_ =
+ settings.value(prefix + "user/recent_reactions", QStringList{}).toStringList();
collapsedSpaces_.clear();
for (const auto &e :
@@ -209,6 +211,14 @@ UserSettings::setHiddenPins(QStringList hiddenTags)
emit hiddenPinsChanged();
}
+void
+UserSettings::setRecentReactions(QStringList recent)
+{
+ recentReactions_ = recent;
+ save();
+ emit recentReactionsChanged();
+}
+
void
UserSettings::setCollapsedSpaces(QList spaces)
{
@@ -717,6 +727,7 @@ UserSettings::save()
settings.setValue(prefix + "user/online_key_backup", useOnlineKeyBackup_);
settings.setValue(prefix + "user/hidden_tags", hiddenTags_);
settings.setValue(prefix + "user/hidden_pins", hiddenPins_);
+ settings.setValue(prefix + "user/recent_reactions", recentReactions_);
QVariantList v;
for (const auto &e : collapsedSpaces_)
diff --git a/src/UserSettingsPage.h b/src/UserSettingsPage.h
index ab73414e..f338c55b 100644
--- a/src/UserSettingsPage.h
+++ b/src/UserSettingsPage.h
@@ -106,6 +106,8 @@ class UserSettings : public QObject
Q_PROPERTY(bool useIdenticon READ useIdenticon WRITE setUseIdenticon NOTIFY useIdenticonChanged)
Q_PROPERTY(QStringList hiddenPins READ hiddenPins WRITE setHiddenPins NOTIFY hiddenPinsChanged)
+ Q_PROPERTY(QStringList recentReactions READ recentReactions WRITE setRecentReactions NOTIFY
+ recentReactionsChanged)
UserSettings();
@@ -174,6 +176,7 @@ public:
void setDisableCertificateValidation(bool disabled);
void setHiddenTags(QStringList hiddenTags);
void setHiddenPins(QStringList hiddenTags);
+ void setRecentReactions(QStringList recent);
void setUseIdenticon(bool state);
void setCollapsedSpaces(QList spaces);
@@ -232,6 +235,7 @@ public:
bool disableCertificateValidation() const { return disableCertificateValidation_; }
QStringList hiddenTags() const { return hiddenTags_; }
QStringList hiddenPins() const { return hiddenPins_; }
+ QStringList recentReactions() const { return recentReactions_; }
bool useIdenticon() const { return useIdenticon_ && JdenticonProvider::isAvailable(); }
QList collapsedSpaces() const { return collapsedSpaces_; }
@@ -283,6 +287,7 @@ signals:
void disableCertificateValidationChanged(bool disabled);
void useIdenticonChanged(bool state);
void hiddenPinsChanged();
+ void recentReactionsChanged();
private:
// Default to system theme if QT_QPA_PLATFORMTHEME var is set.
@@ -337,6 +342,7 @@ private:
QString homeserver_;
QStringList hiddenTags_;
QStringList hiddenPins_;
+ QStringList recentReactions_;
QList collapsedSpaces_;
bool useIdenticon_;
diff --git a/src/timeline/InputBar.cpp b/src/timeline/InputBar.cpp
index 78416135..191160ea 100644
--- a/src/timeline/InputBar.cpp
+++ b/src/timeline/InputBar.cpp
@@ -829,6 +829,14 @@ InputBar::reaction(const QString &reactedEvent, const QString &reactionKey)
reaction.relations.relations.push_back(rel);
room->sendMessageEvent(reaction, mtx::events::EventType::Reaction);
+
+ auto recents = UserSettings::instance()->recentReactions();
+ if (recents.contains(reactionKey))
+ recents.removeOne(reactionKey);
+ else if (recents.size() >= 6)
+ recents.removeLast();
+ recents.push_front(reactionKey);
+ UserSettings::instance()->setRecentReactions(recents);
// Otherwise, we have previously reacted and the reaction should be redacted
} else {
room->redactEvent(selfReactedEvent);