Implement sending stickers
MSC2545 image packs are used as source.
This commit is contained in:
@@ -49,6 +49,9 @@ add_library(neochat STATIC
|
||||
models/searchmodel.cpp
|
||||
texthandler.cpp
|
||||
logger.cpp
|
||||
imagepackevent.cpp
|
||||
stickermodel.cpp
|
||||
imagepacksmodel.cpp
|
||||
)
|
||||
|
||||
add_executable(neochat-app
|
||||
|
||||
54
src/imagepackevent.cpp
Normal file
54
src/imagepackevent.cpp
Normal file
@@ -0,0 +1,54 @@
|
||||
// SPDX-FileCopyrightText: 2021-2023 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
#include "imagepackevent.h"
|
||||
#include <QJsonObject>
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
ImagePackEventContent::ImagePackEventContent(const QJsonObject &json)
|
||||
{
|
||||
if(json.contains(QStringLiteral("pack"))) {
|
||||
pack = ImagePackEventContent::Pack{
|
||||
fromJson<Omittable<QString>>(json["pack"].toObject()["display_name"]),
|
||||
#ifdef QUOTIENT_07
|
||||
fromJson<Omittable<QUrl>>(json["pack"].toObject()["avatar_url"]),
|
||||
#else
|
||||
QUrl(),
|
||||
#endif
|
||||
fromJson<Omittable<QStringList>>(json["pack"].toObject()["usage"]),
|
||||
fromJson<Omittable<QString>>(json["pack"].toObject()["attribution"]),
|
||||
};
|
||||
} else {
|
||||
pack = none;
|
||||
}
|
||||
|
||||
const auto &keys = json["images"].toObject().keys();
|
||||
for (const auto &k : keys) {
|
||||
Omittable<EventContent::ImageInfo> info;
|
||||
if (json["images"][k].toObject().contains(QStringLiteral("info"))) {
|
||||
#ifdef QUOTIENT_07
|
||||
info = EventContent::ImageInfo(QUrl(json["images"][k]["url"].toString()), json["images"][k]["info"].toObject(), k);
|
||||
#else
|
||||
info = EventContent::ImageInfo(QUrl(json["images"][k]["url"].toString()), json["images"][k].toObject(), k);
|
||||
#endif
|
||||
} else {
|
||||
info = none;
|
||||
}
|
||||
images += ImagePackImage{
|
||||
k,
|
||||
#ifdef QUOTIENT_07
|
||||
fromJson<QUrl>(json["images"][k]["url"].toString()),
|
||||
#else
|
||||
QUrl(),
|
||||
#endif
|
||||
fromJson<Omittable<QString>>(json["images"][k]["body"]),
|
||||
info,
|
||||
fromJson<Omittable<QStringList>>(json["images"][k]["usage"]),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
void ImagePackEventContent::fillJson(QJsonObject* o) const {
|
||||
// TODO
|
||||
}
|
||||
58
src/imagepackevent.h
Normal file
58
src/imagepackevent.h
Normal file
@@ -0,0 +1,58 @@
|
||||
// SPDX-FileCopyrightText: 2021-2023 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QVector>
|
||||
#include <events/eventcontent.h>
|
||||
#include <events/stateevent.h>
|
||||
|
||||
namespace Quotient
|
||||
{
|
||||
class ImagePackEventContent
|
||||
{
|
||||
public:
|
||||
struct Pack {
|
||||
Quotient::Omittable<QString> displayName;
|
||||
Quotient::Omittable<QUrl> avatarUrl;
|
||||
Quotient::Omittable<QStringList> usage;
|
||||
Quotient::Omittable<QString> attribution;
|
||||
};
|
||||
|
||||
struct ImagePackImage {
|
||||
QString shortcode;
|
||||
QUrl url;
|
||||
Quotient::Omittable<QString> body;
|
||||
Quotient::Omittable<Quotient::EventContent::ImageInfo> info;
|
||||
Quotient::Omittable<QStringList> usage;
|
||||
};
|
||||
|
||||
Quotient::Omittable<Pack> pack;
|
||||
QVector<ImagePackEventContent::ImagePackImage> images;
|
||||
|
||||
explicit ImagePackEventContent(const QJsonObject &o);
|
||||
|
||||
void fillJson(QJsonObject *o) const;
|
||||
};
|
||||
|
||||
#ifdef QUOTIENT_07
|
||||
class ImagePackEvent : public KeyedStateEventBase<ImagePackEvent, ImagePackEventContent>
|
||||
#else
|
||||
class ImagePackEvent : public StateEvent<ImagePackEventContent>
|
||||
#endif
|
||||
{
|
||||
public:
|
||||
#ifdef QUOTIENT_07
|
||||
QUO_EVENT(ImagePackEvent, "im.ponies.room_emotes")
|
||||
using KeyedStateEventBase::KeyedStateEventBase;
|
||||
#else
|
||||
DEFINE_EVENT_TYPEID("im.ponies.room_emotes", ImagePackEvent)
|
||||
explicit ImagePackEvent(const QJsonObject &obj)
|
||||
: StateEvent(typeId(), obj)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
REGISTER_EVENT_TYPE(ImagePackEvent)
|
||||
}
|
||||
135
src/imagepacksmodel.cpp
Normal file
135
src/imagepacksmodel.cpp
Normal file
@@ -0,0 +1,135 @@
|
||||
// SPDX-FileCopyrightText: 2021 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
#include "imagepacksmodel.h"
|
||||
#include "imagepackevent.h"
|
||||
#include "neochatroom.h"
|
||||
|
||||
#include <KLocalizedString>
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
ImagePacksModel::ImagePacksModel(QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
{
|
||||
}
|
||||
|
||||
int ImagePacksModel::rowCount(const QModelIndex &index) const
|
||||
{
|
||||
return m_events.count();
|
||||
}
|
||||
|
||||
QVariant ImagePacksModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
const auto &event = m_events[index.row()];
|
||||
if (role == DisplayNameRole) {
|
||||
if (event.pack->displayName) {
|
||||
return *event.pack->displayName;
|
||||
}
|
||||
}
|
||||
if (role == AvatarUrlRole) {
|
||||
if (event.pack->avatarUrl) {
|
||||
#ifdef QUOTIENT_07
|
||||
return m_room->connection()->makeMediaUrl(*event.pack->avatarUrl);
|
||||
#endif
|
||||
} else if (!event.images.empty()) {
|
||||
#ifdef QUOTIENT_07
|
||||
return m_room->connection()->makeMediaUrl(event.images[0].url);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> ImagePacksModel::roleNames() const
|
||||
{
|
||||
return {{DisplayNameRole, "displayName"}, {AvatarUrlRole, "avatarUrl"}, {AttributionRole, "attribution"}, {IdRole, "id"},};
|
||||
}
|
||||
|
||||
NeoChatRoom *ImagePacksModel::room() const
|
||||
{
|
||||
return m_room;
|
||||
}
|
||||
|
||||
void ImagePacksModel::setRoom(NeoChatRoom *room)
|
||||
{
|
||||
if (m_room) {
|
||||
disconnect(m_room, nullptr, this, nullptr);
|
||||
}
|
||||
m_room = room;
|
||||
|
||||
beginResetModel();
|
||||
m_events.clear();
|
||||
|
||||
// TODO listen to account data changing
|
||||
// TODO listen to packs changing
|
||||
auto json = m_room->connection()->accountData("im.ponies.user_emotes"_ls)->contentJson();
|
||||
json["pack"] = QJsonObject{
|
||||
{"display_name", i18n("Own Stickers")},
|
||||
};
|
||||
const auto &content = ImagePackEventContent(json);
|
||||
if (!content.images.isEmpty()) {
|
||||
m_events += ImagePackEventContent(json);
|
||||
}
|
||||
const auto &accountData = m_room->connection()->accountData("im.ponies.emote_rooms"_ls);
|
||||
if (accountData) {
|
||||
const auto &rooms = accountData->contentJson()["rooms"_ls].toObject();
|
||||
for (const auto &roomId : rooms.keys()) {
|
||||
if (roomId == m_room->id()) {
|
||||
continue;
|
||||
}
|
||||
auto packs = rooms[roomId].toObject();
|
||||
const auto &stickerRoom = m_room->connection()->room(roomId);
|
||||
for (const auto &packKey : packs.keys()) {
|
||||
#ifdef QUOTIENT_07
|
||||
const auto packContent = stickerRoom->currentState().get<ImagePackEvent>(packKey)->content();
|
||||
if (!packContent.pack->usage || (packContent.pack->usage->contains("emoticon") && showEmoticons())
|
||||
|| (packContent.pack->usage->contains("sticker") && showStickers())) {
|
||||
m_events += packContent;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
#ifdef QUOTIENT_07
|
||||
auto events = m_room->currentState().eventsOfType("im.ponies.room_emotes");
|
||||
for (const auto &event : events) {
|
||||
auto packContent = eventCast<const ImagePackEvent>(event)->content();
|
||||
if (!packContent.pack->usage || (packContent.pack->usage->contains("emoticon") && showEmoticons())
|
||||
|| (packContent.pack->usage->contains("sticker") && showStickers())) {
|
||||
m_events += packContent;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
endResetModel();
|
||||
Q_EMIT roomChanged();
|
||||
}
|
||||
|
||||
bool ImagePacksModel::showStickers() const
|
||||
{
|
||||
return m_showStickers;
|
||||
}
|
||||
|
||||
void ImagePacksModel::setShowStickers(bool showStickers)
|
||||
{
|
||||
m_showStickers = showStickers;
|
||||
Q_EMIT showStickersChanged();
|
||||
}
|
||||
|
||||
bool ImagePacksModel::showEmoticons() const
|
||||
{
|
||||
return m_showEmoticons;
|
||||
}
|
||||
|
||||
void ImagePacksModel::setShowEmoticons(bool showEmoticons)
|
||||
{
|
||||
m_showEmoticons = showEmoticons;
|
||||
Q_EMIT showEmoticonsChanged();
|
||||
}
|
||||
QVector<Quotient::ImagePackEventContent::ImagePackImage> ImagePacksModel::images(int index)
|
||||
{
|
||||
if (index < 0 || index >= m_events.size()) {
|
||||
return {};
|
||||
}
|
||||
return m_events[index].images;
|
||||
}
|
||||
57
src/imagepacksmodel.h
Normal file
57
src/imagepacksmodel.h
Normal file
@@ -0,0 +1,57 @@
|
||||
// SPDX-FileCopyrightText: 2021-2023 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "imagepackevent.h"
|
||||
#include <QAbstractListModel>
|
||||
#include <QPointer>
|
||||
#include <QVector>
|
||||
|
||||
class NeoChatRoom;
|
||||
|
||||
class ImagePacksModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(NeoChatRoom *room READ room WRITE setRoom NOTIFY roomChanged)
|
||||
Q_PROPERTY(bool showStickers READ showStickers WRITE setShowStickers NOTIFY showStickersChanged)
|
||||
Q_PROPERTY(bool showEmoticons READ showEmoticons WRITE setShowEmoticons NOTIFY showEmoticonsChanged)
|
||||
|
||||
public:
|
||||
enum Roles {
|
||||
DisplayNameRole = Qt::DisplayRole,
|
||||
AvatarUrlRole,
|
||||
AttributionRole,
|
||||
IdRole,
|
||||
};
|
||||
Q_ENUM(Roles);
|
||||
|
||||
explicit ImagePacksModel(QObject *parent = nullptr);
|
||||
|
||||
[[nodiscard]] int rowCount(const QModelIndex &index) const override;
|
||||
[[nodiscard]] QVariant data(const QModelIndex &index, int role) const override;
|
||||
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
[[nodiscard]] NeoChatRoom *room() const;
|
||||
void setRoom(NeoChatRoom *room);
|
||||
|
||||
[[nodiscard]] bool showStickers() const;
|
||||
void setShowStickers(bool showStickers);
|
||||
|
||||
[[nodiscard]] bool showEmoticons() const;
|
||||
void setShowEmoticons(bool showEmoticons);
|
||||
|
||||
[[nodiscard]] QVector<Quotient::ImagePackEventContent::ImagePackImage> images(int index);
|
||||
|
||||
Q_SIGNALS:
|
||||
void roomChanged();
|
||||
void showStickersChanged();
|
||||
void showEmoticonsChanged();
|
||||
|
||||
private:
|
||||
QPointer<NeoChatRoom> m_room;
|
||||
QVector<Quotient::ImagePackEventContent> m_events;
|
||||
bool m_showStickers = true;
|
||||
bool m_showEmoticons = true;
|
||||
};
|
||||
@@ -44,6 +44,7 @@
|
||||
#include "clipboard.h"
|
||||
#include "controller.h"
|
||||
#include "filetypesingleton.h"
|
||||
#include "imagepacksmodel.h"
|
||||
#include "linkpreviewer.h"
|
||||
#include "logger.h"
|
||||
#include "login.h"
|
||||
@@ -75,6 +76,7 @@
|
||||
#include "models/statefiltermodel.h"
|
||||
#include "roommanager.h"
|
||||
#include "spacehierarchycache.h"
|
||||
#include "stickermodel.h"
|
||||
#include "urlhelper.h"
|
||||
#include "windowcontroller.h"
|
||||
#ifdef QUOTIENT_07
|
||||
@@ -244,6 +246,8 @@ int main(int argc, char *argv[])
|
||||
qmlRegisterType<PollHandler>("org.kde.neochat", 1, 0, "PollHandler");
|
||||
#endif
|
||||
qmlRegisterType<KeywordNotificationRuleModel>("org.kde.neochat", 1, 0, "KeywordNotificationRuleModel");
|
||||
qmlRegisterType<StickerModel>("org.kde.neochat", 1, 0, "StickerModel");
|
||||
qmlRegisterType<ImagePacksModel>("org.kde.neochat", 1, 0, "ImagePacksModel");
|
||||
qmlRegisterUncreatableType<RoomMessageEvent>("org.kde.neochat", 1, 0, "RoomMessageEvent", "ENUM");
|
||||
qmlRegisterUncreatableType<PushNotificationState>("org.kde.neochat", 1, 0, "PushNotificationState", "ENUM");
|
||||
qmlRegisterUncreatableType<PushNotificationAction>("org.kde.neochat", 1, 0, "PushNotificationAction", "ENUM");
|
||||
|
||||
@@ -54,7 +54,7 @@ QQC2.Control {
|
||||
property bool isBusy: false
|
||||
|
||||
icon.name: "smiley"
|
||||
text: i18n("Add an Emoji")
|
||||
text: i18n("Emojis & Stickers")
|
||||
displayHint: Kirigami.DisplayHint.IconOnly
|
||||
checkable: true
|
||||
|
||||
@@ -367,7 +367,7 @@ QQC2.Control {
|
||||
|
||||
EmojiDialog {
|
||||
id: emojiDialog
|
||||
x: parent.width - implicitWidth
|
||||
x: parent.width - width
|
||||
y: -implicitHeight // - Kirigami.Units.smallSpacing
|
||||
|
||||
modal: false
|
||||
|
||||
@@ -11,6 +11,7 @@ QQC2.ItemDelegate {
|
||||
property string name
|
||||
property string emoji
|
||||
property bool showTones: false
|
||||
property bool isImage: false
|
||||
|
||||
QQC2.ToolTip.text: emojiDelegate.name
|
||||
QQC2.ToolTip.visible: hovered && emojiDelegate.name !== ""
|
||||
@@ -23,7 +24,7 @@ QQC2.ItemDelegate {
|
||||
contentItem: Item {
|
||||
Kirigami.Heading {
|
||||
anchors.fill: parent
|
||||
visible: !emojiDelegate.emoji.startsWith("image")
|
||||
visible: !emojiDelegate.emoji.startsWith("image") && !emojiDelegate.isImage
|
||||
text: emojiDelegate.emoji
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
@@ -40,7 +41,7 @@ QQC2.ItemDelegate {
|
||||
}
|
||||
Image {
|
||||
anchors.fill: parent
|
||||
visible: emojiDelegate.emoji.startsWith("image")
|
||||
visible: emojiDelegate.emoji.startsWith("image") || emojiDelegate.isImage
|
||||
source: visible ? emojiDelegate.emoji : ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,8 +16,10 @@ QQC2.ScrollView {
|
||||
required property bool withCustom
|
||||
readonly property var searchCategory: withCustom ? EmojiModel.Search : EmojiModel.SearchNoCustom
|
||||
required property QtObject header
|
||||
property bool stickers: false
|
||||
|
||||
signal chosen(string unicode)
|
||||
signal stickerChosen(int index)
|
||||
|
||||
onActiveFocusChanged: if (activeFocus) {
|
||||
emojis.forceActiveFocus()
|
||||
@@ -48,15 +50,19 @@ QQC2.ScrollView {
|
||||
delegate: EmojiDelegate {
|
||||
id: emojiDelegate
|
||||
checked: emojis.currentIndex === model.index
|
||||
emoji: modelData.unicode
|
||||
name: modelData.shortName
|
||||
emoji: !!modelData ? modelData.unicode : model.url
|
||||
name: !!modelData ? modelData.shortName : model.body
|
||||
|
||||
width: emojis.cellWidth
|
||||
height: emojis.cellHeight
|
||||
|
||||
isImage: emojiGrid.stickers
|
||||
Keys.onEnterPressed: clicked()
|
||||
Keys.onReturnPressed: clicked()
|
||||
onClicked: {
|
||||
if (emojiGrid.stickers) {
|
||||
emojiGrid.stickerChosen(model.index)
|
||||
}
|
||||
emojiGrid.chosen(modelData.isCustom ? modelData.shortName : modelData.unicode)
|
||||
EmojiModel.emojiUsed(modelData)
|
||||
}
|
||||
@@ -69,7 +75,7 @@ QQC2.ScrollView {
|
||||
tones.open()
|
||||
tones.forceActiveFocus()
|
||||
}
|
||||
showTones: EmojiModel.tones(modelData.shortName).length > 0
|
||||
showTones: !!modelData && EmojiModel.tones(modelData.shortName).length > 0
|
||||
}
|
||||
|
||||
Kirigami.PlaceholderMessage {
|
||||
|
||||
@@ -24,15 +24,45 @@ ColumnLayout {
|
||||
readonly property int categoryIconSize: Math.round(Kirigami.Units.gridUnit * 2.5)
|
||||
readonly property var currentCategory: currentEmojiModel[categories.currentIndex].category
|
||||
readonly property alias categoryCount: categories.count
|
||||
property int selectedType: 0
|
||||
|
||||
signal chosen(string emoji)
|
||||
|
||||
onActiveFocusChanged: if (activeFocus) {
|
||||
searchField.forceActiveFocus()
|
||||
searchField.forceActiveFocus();
|
||||
}
|
||||
|
||||
spacing: 0
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: root.categoryIconSize
|
||||
|
||||
Item {
|
||||
Layout.preferredHeight: 1
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
CategoryIcon {
|
||||
id: emojis
|
||||
source: "smiley"
|
||||
text: i18n("Emojis")
|
||||
t: 0
|
||||
}
|
||||
|
||||
CategoryIcon {
|
||||
id: stickers
|
||||
source: "stickers"
|
||||
text: i18n("Stickers")
|
||||
t: 1
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.preferredHeight: 1
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
|
||||
QQC2.ScrollView {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: root.categoryIconSize + QQC2.ScrollBar.horizontal.height
|
||||
@@ -46,6 +76,7 @@ ColumnLayout {
|
||||
|
||||
Keys.onReturnPressed: if (emojiGrid.count > 0) emojiGrid.focus = true
|
||||
Keys.onEnterPressed: if (emojiGrid.count > 0) emojiGrid.focus = true
|
||||
|
||||
KeyNavigation.down: emojiGrid.count > 0 ? emojiGrid : categories
|
||||
KeyNavigation.tab: emojiGrid.count > 0 ? emojiGrid : categories
|
||||
|
||||
@@ -54,22 +85,10 @@ ColumnLayout {
|
||||
Keys.forwardTo: searchField
|
||||
interactive: width !== contentWidth
|
||||
|
||||
model: root.currentEmojiModel
|
||||
model: root.selectedType === 0 ? root.currentEmojiModel : stickerPackModel
|
||||
Component.onCompleted: categories.forceActiveFocus()
|
||||
|
||||
delegate: EmojiDelegate {
|
||||
width: root.categoryIconSize
|
||||
height: width
|
||||
|
||||
checked: categories.currentIndex === model.index
|
||||
emoji: modelData.emoji
|
||||
name: modelData.name
|
||||
|
||||
onClicked: {
|
||||
categories.currentIndex = index
|
||||
categories.focus = true
|
||||
}
|
||||
}
|
||||
delegate: root.selectedType === 0 ? emojiDelegate : stickerDelegate
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,6 +101,7 @@ ColumnLayout {
|
||||
id: searchField
|
||||
Layout.margins: Kirigami.Units.smallSpacing
|
||||
Layout.fillWidth: true
|
||||
visible: selectedType === 0
|
||||
|
||||
/**
|
||||
* The focus is manged by the parent and we don't want to use the standard
|
||||
@@ -93,13 +113,15 @@ ColumnLayout {
|
||||
EmojiGrid {
|
||||
id: emojiGrid
|
||||
targetIconSize: root.currentCategory === EmojiModel.Custom ? Kirigami.Units.gridUnit * 3 : root.categoryIconSize // Custom emojis are bigger
|
||||
model: searchField.text.length === 0 ? EmojiModel.emojis(root.currentCategory) : (root.includeCustom ? EmojiModel.filterModel(searchField.text, false) : EmojiModel.filterModelNoCustom(searchField.text, false))
|
||||
model: root.selectedType === 1 ? stickerModel : searchField.text.length === 0 ? EmojiModel.emojis(root.currentCategory) : (root.includeCustom ? EmojiModel.filterModel(searchField.text, false) : EmojiModel.filterModelNoCustom(searchField.text, false))
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
withCustom: root.includeCustom
|
||||
onChosen: root.chosen(unicode)
|
||||
header: categories
|
||||
Keys.forwardTo: searchField
|
||||
stickers: root.selectedType === 1
|
||||
onStickerChosen: stickerModel.postSticker(index)
|
||||
}
|
||||
|
||||
Kirigami.Separator {
|
||||
@@ -132,4 +154,86 @@ ColumnLayout {
|
||||
orientation: Qt.Horizontal
|
||||
}
|
||||
}
|
||||
|
||||
ImagePacksModel {
|
||||
id: stickerPackModel
|
||||
room: currentRoom
|
||||
showStickers: true
|
||||
showEmoticons: false
|
||||
}
|
||||
|
||||
StickerModel {
|
||||
id: stickerModel
|
||||
model: stickerPackModel
|
||||
packIndex: 0
|
||||
room: currentRoom
|
||||
}
|
||||
|
||||
Component {
|
||||
id: emojiDelegate
|
||||
EmojiDelegate {
|
||||
width: root.categoryIconSize
|
||||
height: width
|
||||
checked: categories.currentIndex === model.index
|
||||
emoji: modelData ? modelData.emoji : ""
|
||||
name: modelData ? modelData.name : ""
|
||||
onClicked: {
|
||||
categories.currentIndex = index;
|
||||
categories.focus = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: stickerDelegate
|
||||
EmojiDelegate {
|
||||
width: root.categoryIconSize
|
||||
height: width
|
||||
emoji: model.avatarUrl ?? ""
|
||||
isImage: true
|
||||
name: model.displayName ?? ""
|
||||
onClicked: stickerModel.packIndex = model.index
|
||||
checked: stickerModel.packIndex === model.index
|
||||
}
|
||||
}
|
||||
|
||||
component CategoryIcon : Kirigami.Icon {
|
||||
id: categoryIcons
|
||||
|
||||
readonly property bool checked: root.selectedType === t
|
||||
required property int t
|
||||
required property string text
|
||||
|
||||
Layout.preferredWidth: root.categoryIconSize
|
||||
Layout.preferredHeight: root.categoryIconSize
|
||||
|
||||
QQC2.ToolTip.text: text
|
||||
QQC2.ToolTip.visible: categoryIconsMouseArea.containsMouse
|
||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
|
||||
MouseArea {
|
||||
id: categoryIconsMouseArea
|
||||
|
||||
hoverEnabled: true
|
||||
anchors.fill: parent
|
||||
onClicked: root.selectedType = t
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
color: categoryIcons.checked ? Kirigami.Theme.highlightColor : "transparent"
|
||||
radius: Kirigami.Units.smallSpacing
|
||||
z: -1
|
||||
anchors {
|
||||
fill: parent
|
||||
margins: Kirigami.Units.smallSpacing
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
radius: Kirigami.Units.smallSpacing
|
||||
anchors.fill: parent
|
||||
color: Kirigami.Theme.highlightColor
|
||||
opacity: categoryIconsMouseArea.containsMouse && !categoryIconsMouseArea.pressed ? 0.2 : 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ QQC2.Popup {
|
||||
padding: 2
|
||||
|
||||
implicitHeight: Kirigami.Units.gridUnit * 20 + 2 * padding
|
||||
width: Math.min(contentItem.categoryIconSize * contentItem.categoryCount + 2 * padding, QQC2.Overlay.overlay.width)
|
||||
width: Math.min(contentItem.categoryIconSize * 11 + 2 * padding, QQC2.Overlay.overlay.width)
|
||||
contentItem: EmojiPicker {
|
||||
id: emojiPicker
|
||||
height: 400
|
||||
|
||||
@@ -49,6 +49,11 @@ Kirigami.CategorizedSettings {
|
||||
text: i18n("Notifications")
|
||||
icon.name: "notifications"
|
||||
page: Qt.resolvedUrl("PushNotification.qml")
|
||||
},
|
||||
Kirigami.SettingAction {
|
||||
text: i18n("Stickers")
|
||||
icon.name: "stickers"
|
||||
page: Qt.resolvedUrl("RoomStickers.qml")
|
||||
initialProperties: {
|
||||
return {
|
||||
room: root.room
|
||||
|
||||
@@ -38,6 +38,12 @@ Kirigami.CategorizedSettings {
|
||||
icon.name: "preferences-desktop-emoticons"
|
||||
page: Qt.resolvedUrl("Emoticons.qml")
|
||||
},
|
||||
Kirigami.SettingAction {
|
||||
actionName: "stickers"
|
||||
text: i18n("Stickers")
|
||||
icon.name: "stickers"
|
||||
page: Qt.resolvedUrl("Emoticons.qml")
|
||||
},
|
||||
Kirigami.SettingAction {
|
||||
actionName: "spellChecking"
|
||||
text: i18n("Spell Checking")
|
||||
|
||||
109
src/stickermodel.cpp
Normal file
109
src/stickermodel.cpp
Normal file
@@ -0,0 +1,109 @@
|
||||
// SPDX-FileCopyrightText: 2021-2023 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
#include "stickermodel.h"
|
||||
|
||||
#include "imagepackevent.h"
|
||||
#include "imagepacksmodel.h"
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
StickerModel::StickerModel(QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
{}
|
||||
|
||||
int StickerModel::rowCount(const QModelIndex &index) const
|
||||
{
|
||||
return m_images.size();
|
||||
}
|
||||
QVariant StickerModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
const auto &row = index.row();
|
||||
const auto &image = m_images[row];
|
||||
if (role == Url) {
|
||||
#ifdef QUOTIENT_07
|
||||
return m_room->connection()->makeMediaUrl(image.url);
|
||||
#endif
|
||||
}
|
||||
if (role == Body) {
|
||||
if (image.body) {
|
||||
return *image.body;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> StickerModel::roleNames() const
|
||||
{
|
||||
return {
|
||||
{StickerModel::Url, "url"},
|
||||
{StickerModel::Body, "body"},
|
||||
};
|
||||
}
|
||||
ImagePacksModel *StickerModel::model() const
|
||||
{
|
||||
return m_model;
|
||||
}
|
||||
|
||||
void StickerModel::setModel(ImagePacksModel *model)
|
||||
{
|
||||
if (m_model) {
|
||||
disconnect(m_model, nullptr, this, nullptr);
|
||||
}
|
||||
connect(model, &ImagePacksModel::roomChanged, this, [this]() {
|
||||
beginResetModel();
|
||||
m_images = m_model->images(m_index);
|
||||
endResetModel();
|
||||
});
|
||||
beginResetModel();
|
||||
m_model = model;
|
||||
m_images = m_model->images(m_index);
|
||||
endResetModel();
|
||||
Q_EMIT modelChanged();
|
||||
}
|
||||
|
||||
int StickerModel::packIndex() const
|
||||
{
|
||||
return m_index;
|
||||
}
|
||||
void StickerModel::setPackIndex(int index)
|
||||
{
|
||||
beginResetModel();
|
||||
m_index = index;
|
||||
if (m_model) {
|
||||
m_images = m_model->images(m_index);
|
||||
}
|
||||
endResetModel();
|
||||
Q_EMIT packIndexChanged();
|
||||
}
|
||||
|
||||
NeoChatRoom *StickerModel::room() const
|
||||
{
|
||||
return m_room;
|
||||
}
|
||||
|
||||
void StickerModel::setRoom(NeoChatRoom *room)
|
||||
{
|
||||
m_room = room;
|
||||
Q_EMIT roomChanged();
|
||||
}
|
||||
|
||||
void StickerModel::postSticker(int index)
|
||||
{
|
||||
const auto &image = m_images[index];
|
||||
const auto &body = image.body ? *image.body : QString();
|
||||
QJsonObject infoJson;
|
||||
if (image.info) {
|
||||
infoJson["w"] = image.info->imageSize.width();
|
||||
infoJson["h"] = image.info->imageSize.height();
|
||||
infoJson["mimetype"] = image.info->mimeType.name();
|
||||
infoJson["size"] = image.info->payloadSize;
|
||||
// TODO thumbnail
|
||||
}
|
||||
QJsonObject content{
|
||||
{"body"_ls, body},
|
||||
{"url"_ls, image.url.toString()},
|
||||
{"info"_ls, infoJson},
|
||||
};
|
||||
m_room->postJson("m.sticker", content);
|
||||
}
|
||||
56
src/stickermodel.h
Normal file
56
src/stickermodel.h
Normal file
@@ -0,0 +1,56 @@
|
||||
// SPDX-FileCopyrightText: 2021 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "imagepackevent.h"
|
||||
#include "neochatroom.h"
|
||||
#include <QAbstractListModel>
|
||||
#include <QObject>
|
||||
#include <QVector>
|
||||
|
||||
class ImagePacksModel;
|
||||
|
||||
class StickerModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(ImagePacksModel *model READ model WRITE setModel NOTIFY modelChanged)
|
||||
Q_PROPERTY(int packIndex READ packIndex WRITE setPackIndex NOTIFY packIndexChanged)
|
||||
Q_PROPERTY(NeoChatRoom *room READ room WRITE setRoom NOTIFY roomChanged)
|
||||
|
||||
public:
|
||||
enum Roles {
|
||||
Url = Qt::UserRole + 1,
|
||||
Body,
|
||||
};
|
||||
|
||||
explicit StickerModel(QObject *parent = nullptr);
|
||||
|
||||
[[nodiscard]] int rowCount(const QModelIndex &index) const override;
|
||||
[[nodiscard]] QVariant data(const QModelIndex &index, int role) const override;
|
||||
|
||||
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
[[nodiscard]] ImagePacksModel *model() const;
|
||||
void setModel(ImagePacksModel *model);
|
||||
|
||||
[[nodiscard]] int packIndex() const;
|
||||
void setPackIndex(int index);
|
||||
|
||||
[[nodiscard]] NeoChatRoom *room() const;
|
||||
void setRoom(NeoChatRoom *room);
|
||||
|
||||
Q_INVOKABLE void postSticker(int index);
|
||||
|
||||
Q_SIGNALS:
|
||||
void roomChanged();
|
||||
void modelChanged();
|
||||
void packIndexChanged();
|
||||
|
||||
private:
|
||||
ImagePacksModel *m_model = nullptr;
|
||||
int m_index = 0;
|
||||
QVector<Quotient::ImagePackEventContent::ImagePackImage> m_images;
|
||||
NeoChatRoom *m_room;
|
||||
};
|
||||
Reference in New Issue
Block a user