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);