Create account sticker editor

This commit is contained in:
Tobias Fella
2023-05-14 20:33:51 +00:00
parent 571d9780b1
commit b629961a70
17 changed files with 733 additions and 15 deletions

View File

@@ -0,0 +1,178 @@
// SPDX-FileCopyrightText: 2021-2023 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: LGPL-2.0-or-later
#include "accountstickermodel.h"
#include <csapi/content-repo.h>
#include <qcoro/qcorosignal.h>
using namespace Quotient;
AccountStickerModel::AccountStickerModel(QObject *parent)
: QAbstractListModel(parent)
{
}
int AccountStickerModel::rowCount(const QModelIndex &index) const
{
Q_UNUSED(index);
if (!m_images) {
return 0;
}
return m_images->images.size();
}
QVariant AccountStickerModel::data(const QModelIndex &index, int role) const
{
const auto &row = index.row();
const auto &image = m_images->images[row];
if (role == UrlRole) {
#ifdef QUOTIENT_07
return m_connection->makeMediaUrl(image.url);
#else
return QUrl();
#endif
}
if (role == BodyRole) {
if (image.body) {
return *image.body;
}
}
if (role == ShortCodeRole) {
return image.shortcode;
}
if (role == IsStickerRole) {
if (image.usage) {
return image.usage->isEmpty() || image.usage->contains("sticker"_ls);
}
if (m_images->pack && m_images->pack->usage) {
return m_images->pack->usage->isEmpty() || m_images->pack->usage->contains("sticker"_ls);
}
return true;
}
if (role == IsEmojiRole) {
if (image.usage) {
return image.usage->isEmpty() || image.usage->contains("emoticon"_ls);
}
if (m_images->pack && m_images->pack->usage) {
return m_images->pack->usage->isEmpty() || m_images->pack->usage->contains("emoticon"_ls);
}
return true;
}
return {};
}
QHash<int, QByteArray> AccountStickerModel::roleNames() const
{
return {
{AccountStickerModel::UrlRole, "url"},
{AccountStickerModel::BodyRole, "body"},
{AccountStickerModel::ShortCodeRole, "shortcode"},
{AccountStickerModel::IsStickerRole, "isSticker"},
{AccountStickerModel::IsEmojiRole, "isEmoji"},
};
}
Connection *AccountStickerModel::connection() const
{
return m_connection;
}
void AccountStickerModel::setConnection(Connection *connection)
{
if (m_connection) {
disconnect(m_connection, nullptr, this, nullptr);
}
m_connection = connection;
Q_EMIT connectionChanged();
connect(m_connection, &Connection::accountDataChanged, this, [this](QString type) {
if (type == QStringLiteral("im.ponies.user_emotes")) {
reloadStickers();
}
});
reloadStickers();
}
void AccountStickerModel::reloadStickers()
{
if (!m_connection->hasAccountData("im.ponies.user_emotes"_ls)) {
return;
}
auto json = m_connection->accountData("im.ponies.user_emotes"_ls)->contentJson();
const auto &content = ImagePackEventContent(json);
beginResetModel();
m_images = content;
endResetModel();
}
void AccountStickerModel::deleteSticker(int index)
{
QJsonObject data;
m_images->images.removeAt(index);
m_images->fillJson(&data);
m_connection->setAccountData("im.ponies.user_emotes"_ls, data);
}
void AccountStickerModel::setStickerBody(int index, const QString &text)
{
m_images->images[index].body = text;
QJsonObject data;
m_images->fillJson(&data);
m_connection->setAccountData("im.ponies.user_emotes"_ls, data);
}
void AccountStickerModel::setStickerShortcode(int index, const QString &shortcode)
{
m_images->images[index].shortcode = shortcode;
QJsonObject data;
m_images->fillJson(&data);
m_connection->setAccountData("im.ponies.user_emotes"_ls, data);
}
void AccountStickerModel::setStickerImage(int index, const QUrl &source)
{
doSetStickerImage(index, source);
}
QCoro::Task<void> AccountStickerModel::doSetStickerImage(int index, QUrl source)
{
auto job = m_connection->uploadFile(source.isLocalFile() ? source.toLocalFile() : source.toString());
co_await qCoro(job, &BaseJob::finished);
if (job->error() != BaseJob::NoError) {
co_return;
}
#ifdef QUOTIENT_07
m_images->images[index].url = job->contentUri().toString();
#else
m_images->images[index].url = job->contentUri();
#endif
m_images->images[index].info = none;
QJsonObject data;
m_images->fillJson(&data);
m_connection->setAccountData("im.ponies.user_emotes"_ls, data);
}
QCoro::Task<void> AccountStickerModel::doAddSticker(QUrl source, QString shortcode, QString description)
{
auto job = m_connection->uploadFile(source.isLocalFile() ? source.toLocalFile() : source.toString());
co_await qCoro(job, &BaseJob::finished);
if (job->error() != BaseJob::NoError) {
co_return;
}
m_images->images.append(ImagePackEventContent::ImagePackImage{
shortcode,
job->contentUri(),
description,
none,
QStringList{"sticker"_ls},
});
QJsonObject data;
m_images->fillJson(&data);
m_connection->setAccountData("im.ponies.user_emotes"_ls, data);
}
void AccountStickerModel::addSticker(const QUrl &source, const QString &shortcode, const QString &description)
{
doAddSticker(source, shortcode, description);
}

View File

@@ -0,0 +1,101 @@
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: LGPL-2.0-or-later
#pragma once
#include "events/imagepackevent.h"
#include <QAbstractListModel>
#include <QCoroTask>
#include <QObject>
#include <QPointer>
#include <QVector>
#include <connection.h>
class ImagePacksModel;
/**
* @class AccountStickerModel
*
* This class defines the model for visualising the account stickers.
*
* This is based upon the im.ponies.user_emotes spec (MSC2545).
*/
class AccountStickerModel : public QAbstractListModel
{
Q_OBJECT
/**
* @brief The connection to get stickers from.
*/
Q_PROPERTY(Quotient::Connection *connection READ connection WRITE setConnection NOTIFY connectionChanged)
public:
enum Roles {
UrlRole = Qt::UserRole + 1, /**< The URL for the sticker. */
ShortCodeRole, /**< The shortcode for the sticker. */
BodyRole, //**< A textual description of the sticker */
IsStickerRole, //**< Whether this emoticon is a sticker */
IsEmojiRole, //**< Whether this emoticon is an emoji */
};
explicit AccountStickerModel(QObject *parent = nullptr);
/**
* @brief Number of rows in the model.
*
* @sa QAbstractItemModel::rowCount
*/
[[nodiscard]] int rowCount(const QModelIndex &index) const override;
/**
* @brief Get the given role value at the given index.
*
* @sa QAbstractItemModel::data
*/
[[nodiscard]] QVariant data(const QModelIndex &index, int role) const override;
/**
* @brief Returns a mapping from Role enum values to role names.
*
* @sa Roles, QAbstractItemModel::roleNames()
*/
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
[[nodiscard]] Quotient::Connection *connection() const;
void setConnection(Quotient::Connection *connection);
/**
* @brief Deletes the sticker at the given index.
*/
Q_INVOKABLE void deleteSticker(int index);
/**
* @brief Changes the description for the sticker at the given index.
*/
Q_INVOKABLE void setStickerBody(int index, const QString &text);
/**
* @brief Changes the shortcode for the sticker at the given index.
*/
Q_INVOKABLE void setStickerShortcode(int index, const QString &shortCode);
/**
* @brief Changes the image for the sticker at the given index.
*/
Q_INVOKABLE void setStickerImage(int index, const QUrl &source);
/**
* @brief Adds a sticker with the given parameters.
*/
Q_INVOKABLE void addSticker(const QUrl &source, const QString &shortcode, const QString &description);
Q_SIGNALS:
void connectionChanged();
private:
std::optional<Quotient::ImagePackEventContent> m_images;
QPointer<Quotient::Connection> m_connection;
QCoro::Task<void> doSetStickerImage(int index, QUrl source);
QCoro::Task<void> doAddSticker(QUrl source, QString shortcode, QString description);
void reloadStickers();
};

View File

@@ -86,6 +86,7 @@ void CustomEmojiModel::addEmoji(const QString &name, const QUrl &location)
{QStringLiteral("url"), url},
{QStringLiteral("info"), imageInfo},
{QStringLiteral("body"), location.fileName()},
{"usage"_ls, "emoticon"_ls},
});
json["images"] = emojiData;

View File

@@ -0,0 +1,40 @@
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
#include "emoticonfiltermodel.h"
#include "accountstickermodel.h"
EmoticonFilterModel::EmoticonFilterModel(QObject *parent)
: QSortFilterProxyModel(parent)
{
}
bool EmoticonFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
{
auto stickerUsage = sourceModel()->data(sourceModel()->index(sourceRow, 0), AccountStickerModel::IsStickerRole).toBool();
auto emojiUsage = sourceModel()->data(sourceModel()->index(sourceRow, 0), AccountStickerModel::IsEmojiRole).toBool();
return (stickerUsage && m_showStickers) || (emojiUsage && m_showEmojis);
}
bool EmoticonFilterModel::showStickers() const
{
return m_showStickers;
}
void EmoticonFilterModel::setShowStickers(bool showStickers)
{
m_showStickers = showStickers;
Q_EMIT showStickersChanged();
}
bool EmoticonFilterModel::showEmojis() const
{
return m_showEmojis;
}
void EmoticonFilterModel::setShowEmojis(bool showEmojis)
{
m_showEmojis = showEmojis;
Q_EMIT showEmojisChanged();
}

View File

@@ -0,0 +1,49 @@
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
#include <QSortFilterProxyModel>
/**
* @class EmoticonFilterModel
*
* This class creates a custom QSortFilterProxyModel for filtering a emoticon by type
* (Sticker or Emoji).
*/
class EmoticonFilterModel : public QSortFilterProxyModel
{
Q_OBJECT
/**
* @brief Whether stickers should be shown
*/
Q_PROPERTY(bool showStickers READ showStickers WRITE setShowStickers NOTIFY showStickersChanged)
/**
* @brief Whether emojis show be shown
*/
Q_PROPERTY(bool showEmojis READ showEmojis WRITE setShowEmojis NOTIFY showEmojisChanged)
public:
explicit EmoticonFilterModel(QObject *parent = nullptr);
/**
* @brief Custom filter function checking the type of emoticon
*
* @note The filter cannot be modified and will always use the same filter properties.
*/
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
[[nodiscard]] bool showStickers() const;
void setShowStickers(bool showStickers);
[[nodiscard]] bool showEmojis() const;
void setShowEmojis(bool showEmojis);
Q_SIGNALS:
void showStickersChanged();
void showEmojisChanged();
private:
bool m_showStickers = false;
bool m_showEmojis = false;
};

View File

@@ -20,7 +20,11 @@ int ImagePacksModel::rowCount(const QModelIndex &index) const
QVariant ImagePacksModel::data(const QModelIndex &index, int role) const
{
const auto &event = m_events[index.row()];
const auto row = index.row();
if (row < 0 || row >= m_events.size()) {
return {};
}
const auto &event = m_events[row];
if (role == DisplayNameRole) {
if (event.pack->displayName) {
return *event.pack->displayName;
@@ -59,14 +63,24 @@ void ImagePacksModel::setRoom(NeoChatRoom *room)
{
if (m_room) {
disconnect(m_room, nullptr, this, nullptr);
disconnect(m_room->connection(), nullptr, this, nullptr);
}
m_room = room;
connect(m_room->connection(), &Connection::accountDataChanged, this, [this](const QString &type) {
if (type == "im.ponies.user_emotes"_ls) {
reloadImages();
}
});
// TODO listen to packs changing
reloadImages();
Q_EMIT roomChanged();
}
void ImagePacksModel::reloadImages()
{
beginResetModel();
m_events.clear();
// TODO listen to account data changing
// TODO listen to packs changing
if (m_room->connection()->hasAccountData("im.ponies.user_emotes"_ls)) {
auto json = m_room->connection()->accountData("im.ponies.user_emotes"_ls)->contentJson();
json["pack"] = QJsonObject{
@@ -110,8 +124,8 @@ void ImagePacksModel::setRoom(NeoChatRoom *room)
}
}
#endif
Q_EMIT imagesLoaded();
endResetModel();
Q_EMIT roomChanged();
}
bool ImagePacksModel::showStickers() const

View File

@@ -90,10 +90,12 @@ Q_SIGNALS:
void roomChanged();
void showStickersChanged();
void showEmoticonsChanged();
void imagesLoaded();
private:
QPointer<NeoChatRoom> m_room;
QVector<Quotient::ImagePackEventContent> m_events;
bool m_showStickers = true;
bool m_showEmoticons = true;
void reloadImages();
};

View File

@@ -51,14 +51,11 @@ void StickerModel::setModel(ImagePacksModel *model)
disconnect(m_model, nullptr, this, nullptr);
}
connect(model, &ImagePacksModel::roomChanged, this, [this]() {
beginResetModel();
m_images = m_model->images(m_index);
endResetModel();
reloadImages();
});
beginResetModel();
connect(model, &ImagePacksModel::imagesLoaded, this, &StickerModel::reloadImages);
m_model = model;
m_images = m_model->images(m_index);
endResetModel();
reloadImages();
Q_EMIT modelChanged();
}
@@ -68,13 +65,18 @@ int StickerModel::packIndex() const
}
void StickerModel::setPackIndex(int index)
{
beginResetModel();
m_index = index;
Q_EMIT packIndexChanged();
reloadImages();
}
void StickerModel::reloadImages()
{
beginResetModel();
if (m_model) {
m_images = m_model->images(m_index);
}
endResetModel();
Q_EMIT packIndexChanged();
}
NeoChatRoom *StickerModel::room() const
@@ -84,6 +86,9 @@ NeoChatRoom *StickerModel::room() const
void StickerModel::setRoom(NeoChatRoom *room)
{
if (room) {
disconnect(room->connection(), nullptr, this, nullptr);
}
m_room = room;
Q_EMIT roomChanged();
}

View File

@@ -98,4 +98,5 @@ private:
int m_index = 0;
QVector<Quotient::ImagePackEventContent::ImagePackImage> m_images;
NeoChatRoom *m_room;
void reloadImages();
};