Create account sticker editor
This commit is contained in:
@@ -59,4 +59,4 @@ Dependencies:
|
||||
'frameworks/kdbusaddons': '@latest-kf6'
|
||||
|
||||
Options:
|
||||
require-passing-tests-on: [ 'Linux/Qt5', 'FreeBSD', 'Windows/Qt5' ]
|
||||
require-passing-tests-on: [ 'Linux/Qt5', 'FreeBSD' ]
|
||||
|
||||
@@ -15,6 +15,7 @@ add_library(neochat STATIC
|
||||
models/messagefiltermodel.cpp
|
||||
models/roomlistmodel.cpp
|
||||
models/sortfilterspacelistmodel.cpp
|
||||
models/accountstickermodel.cpp
|
||||
spacehierarchycache.cpp
|
||||
roommanager.cpp
|
||||
neochatroom.cpp
|
||||
@@ -24,6 +25,7 @@ add_library(neochat STATIC
|
||||
models/publicroomlistmodel.cpp
|
||||
models/userdirectorylistmodel.cpp
|
||||
models/keywordnotificationrulemodel.cpp
|
||||
models/emoticonfiltermodel.cpp
|
||||
notificationsmanager.cpp
|
||||
models/sortfilterroomlistmodel.cpp
|
||||
chatdocumenthandler.cpp
|
||||
|
||||
@@ -51,5 +51,42 @@ ImagePackEventContent::ImagePackEventContent(const QJsonObject &json)
|
||||
|
||||
void ImagePackEventContent::fillJson(QJsonObject *o) const
|
||||
{
|
||||
// TODO
|
||||
if (pack) {
|
||||
QJsonObject packJson;
|
||||
if (pack->displayName) {
|
||||
packJson["display_name"] = *pack->displayName;
|
||||
}
|
||||
if (pack->usage) {
|
||||
QJsonArray usageJson;
|
||||
for (const auto &usage : *pack->usage) {
|
||||
usageJson += usage;
|
||||
}
|
||||
packJson["usage"] = usageJson;
|
||||
}
|
||||
if (pack->avatarUrl) {
|
||||
packJson["avatar_url"] = pack->avatarUrl->toString();
|
||||
}
|
||||
if (pack->attribution) {
|
||||
packJson["attribution"] = *pack->attribution;
|
||||
}
|
||||
(*o)["pack"_ls] = packJson;
|
||||
}
|
||||
|
||||
QJsonObject imagesJson;
|
||||
for (const auto &image : images) {
|
||||
QJsonObject imageJson;
|
||||
imageJson["url"] = image.url.toString();
|
||||
if (image.body) {
|
||||
imageJson["body"] = *image.body;
|
||||
}
|
||||
if (image.usage) {
|
||||
QJsonArray usageJson;
|
||||
for (const auto &usage : *image.usage) {
|
||||
usageJson += usage;
|
||||
}
|
||||
imageJson["usage"] = usageJson;
|
||||
}
|
||||
imagesJson[image.shortcode] = imageJson;
|
||||
}
|
||||
(*o)["images"_ls] = imagesJson;
|
||||
}
|
||||
|
||||
@@ -48,10 +48,12 @@
|
||||
#include "logger.h"
|
||||
#include "login.h"
|
||||
#include "matriximageprovider.h"
|
||||
#include "models/accountstickermodel.h"
|
||||
#include "models/collapsestateproxymodel.h"
|
||||
#include "models/customemojimodel.h"
|
||||
#include "models/devicesmodel.h"
|
||||
#include "models/emojimodel.h"
|
||||
#include "models/emoticonfiltermodel.h"
|
||||
#include "models/imagepacksmodel.h"
|
||||
#include "models/keywordnotificationrulemodel.h"
|
||||
#include "models/messageeventmodel.h"
|
||||
@@ -248,6 +250,8 @@ int main(int argc, char *argv[])
|
||||
qmlRegisterType<KeywordNotificationRuleModel>("org.kde.neochat", 1, 0, "KeywordNotificationRuleModel");
|
||||
qmlRegisterType<StickerModel>("org.kde.neochat", 1, 0, "StickerModel");
|
||||
qmlRegisterType<ImagePacksModel>("org.kde.neochat", 1, 0, "ImagePacksModel");
|
||||
qmlRegisterType<AccountStickerModel>("org.kde.neochat", 1, 0, "AccountStickerModel");
|
||||
qmlRegisterType<EmoticonFilterModel>("org.kde.neochat", 1, 0, "EmoticonFilterModel");
|
||||
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");
|
||||
|
||||
178
src/models/accountstickermodel.cpp
Normal file
178
src/models/accountstickermodel.cpp
Normal 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);
|
||||
}
|
||||
101
src/models/accountstickermodel.h
Normal file
101
src/models/accountstickermodel.h
Normal 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();
|
||||
};
|
||||
@@ -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;
|
||||
|
||||
40
src/models/emoticonfiltermodel.cpp
Normal file
40
src/models/emoticonfiltermodel.cpp
Normal 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();
|
||||
}
|
||||
49
src/models/emoticonfiltermodel.h
Normal file
49
src/models/emoticonfiltermodel.h
Normal 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;
|
||||
};
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -98,4 +98,5 @@ private:
|
||||
int m_index = 0;
|
||||
QVector<Quotient::ImagePackEventContent::ImagePackImage> m_images;
|
||||
NeoChatRoom *m_room;
|
||||
void reloadImages();
|
||||
};
|
||||
|
||||
@@ -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("StickersPage.qml")
|
||||
},
|
||||
Kirigami.SettingAction {
|
||||
actionName: "spellChecking"
|
||||
text: i18n("Spell Checking")
|
||||
|
||||
148
src/qml/Settings/StickerEditorPage.qml
Normal file
148
src/qml/Settings/StickerEditorPage.qml
Normal file
@@ -0,0 +1,148 @@
|
||||
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15 as QQC2
|
||||
import QtQuick.Layouts 1.15
|
||||
import Qt.labs.platform 1.1
|
||||
import QtQuick.Window 2.15
|
||||
|
||||
import org.kde.kirigami 2.19 as Kirigami
|
||||
import org.kde.kirigamiaddons.labs.mobileform 0.1 as MobileForm
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
Kirigami.ScrollablePage {
|
||||
id: root
|
||||
|
||||
required property string description
|
||||
required property string index
|
||||
required property string url
|
||||
required property string shortcode
|
||||
required property var model
|
||||
required property var proxyModel
|
||||
property bool newSticker: false
|
||||
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
|
||||
title: newSticker ? i18nc("@title", "Add Sticker") : i18nc("@title", "Edit Sticker")
|
||||
|
||||
ColumnLayout {
|
||||
MobileForm.FormCard {
|
||||
Layout.topMargin: Kirigami.Units.largeSpacing
|
||||
Layout.fillWidth: true
|
||||
contentItem: ColumnLayout {
|
||||
spacing: 0
|
||||
MobileForm.FormCardHeader {
|
||||
title: i18n("Sticker")
|
||||
}
|
||||
MobileForm.AbstractFormDelegate {
|
||||
Layout.fillWidth: true
|
||||
background: Item {}
|
||||
contentItem: RowLayout {
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
Image {
|
||||
id: image
|
||||
Layout.alignment: Qt.AlignRight
|
||||
source: root.url
|
||||
sourceSize.width: Kirigami.Units.gridUnit * 4
|
||||
sourceSize.height: Kirigami.Units.gridUnit * 4
|
||||
width: Kirigami.Units.gridUnit * 4
|
||||
height: Kirigami.Units.gridUnit * 4
|
||||
|
||||
Kirigami.Icon {
|
||||
source: "stickers"
|
||||
anchors.fill: parent
|
||||
visible: parent.status !== Image.Ready
|
||||
}
|
||||
|
||||
QQC2.Button {
|
||||
icon.name: "edit-entry"
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
onClicked: mouseArea.clicked()
|
||||
text: image.source != "" ? i18n("Change Image") : i18n("Set Image")
|
||||
QQC2.ToolTip.text: text
|
||||
QQC2.ToolTip.visible: hovered
|
||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
display: QQC2.Button.IconOnly
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
property var fileDialog: null;
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
if (fileDialog != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
fileDialog = openFileDialog.createObject(QQC2.ApplicationWindow.Overlay)
|
||||
fileDialog.chosen.connect(function(receivedSource) {
|
||||
mouseArea.fileDialog = null;
|
||||
if (!receivedSource) {
|
||||
return;
|
||||
}
|
||||
parent.source = receivedSource;
|
||||
});
|
||||
fileDialog.onRejected.connect(function() {
|
||||
mouseArea.fileDialog = null;
|
||||
});
|
||||
fileDialog.open();
|
||||
}
|
||||
}
|
||||
}
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
}
|
||||
MobileForm.FormTextFieldDelegate {
|
||||
id: shortcode
|
||||
label: i18n("Shortcode:")
|
||||
text: root.shortcode
|
||||
}
|
||||
MobileForm.FormTextFieldDelegate {
|
||||
id: description
|
||||
label: i18n("Description:")
|
||||
text: root.description
|
||||
}
|
||||
MobileForm.FormButtonDelegate {
|
||||
id: save
|
||||
text: i18n("Save")
|
||||
icon.name: "document-save"
|
||||
enabled: !root.newSticker || (image.source && shortcode.text && description.text)
|
||||
onClicked: {
|
||||
if (root.newSticker) {
|
||||
model.addSticker(image.source, shortcode.text, description.text)
|
||||
} else {
|
||||
if (description.text !== root.description) {
|
||||
root.model.setStickerBody(proxyModel.mapToSource(proxyModel.index(model.index, 0)).row, description.text)
|
||||
}
|
||||
if (shortcode.text !== root.shortcode) {
|
||||
root.model.setStickerShortcode(proxyModel.mapToSource(proxyModel.index(model.index, 0)).row, shortcode.text)
|
||||
}
|
||||
if (image.source + "" !== root.url) {
|
||||
root.model.setStickerImage(proxyModel.mapToSource(proxyModel.index(model.index, 0)).row, image.source)
|
||||
}
|
||||
}
|
||||
root.closeDialog()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Component {
|
||||
id: openFileDialog
|
||||
|
||||
OpenFileDialog {
|
||||
folder: StandardPaths.writableLocation(StandardPaths.PicturesLocation)
|
||||
parentWindow: root.Window.window
|
||||
}
|
||||
}
|
||||
}
|
||||
128
src/qml/Settings/StickersPage.qml
Normal file
128
src/qml/Settings/StickersPage.qml
Normal file
@@ -0,0 +1,128 @@
|
||||
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15 as QQC2
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import org.kde.kirigami 2.19 as Kirigami
|
||||
import org.kde.kirigamiaddons.labs.mobileform 0.1 as MobileForm
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
Kirigami.ScrollablePage {
|
||||
title: i18n("Stickers")
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
|
||||
ColumnLayout {
|
||||
MobileForm.FormCard {
|
||||
Layout.topMargin: Kirigami.Units.largeSpacing
|
||||
Layout.fillWidth: true
|
||||
contentItem: ColumnLayout {
|
||||
spacing: 0
|
||||
MobileForm.FormCardHeader {
|
||||
title: i18n("Stickers")
|
||||
}
|
||||
Flow {
|
||||
id: stickerFlow
|
||||
Layout.fillWidth: true
|
||||
Repeater {
|
||||
model: EmoticonFilterModel {
|
||||
id: emoticonFilterModel
|
||||
sourceModel: AccountStickerModel {
|
||||
id: stickerModel
|
||||
connection: Controller.activeConnection
|
||||
}
|
||||
showStickers: true
|
||||
showEmojis: false
|
||||
}
|
||||
|
||||
delegate: MobileForm.AbstractFormDelegate {
|
||||
id: stickerDelegate
|
||||
|
||||
width: stickerFlow.width / 4
|
||||
height: width
|
||||
|
||||
onClicked: pageSettingStack.pushDialogLayer(stickerEditorPage, {
|
||||
description: model.body ?? "",
|
||||
index: model.index,
|
||||
url: model.url,
|
||||
shortcode: model.shortcode,
|
||||
model: stickerModel,
|
||||
proxyModel: emoticonFilterModel
|
||||
}, {
|
||||
title: i18nc("@title", "Edit Sticker")
|
||||
});
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
Image {
|
||||
source: model.url
|
||||
Layout.fillWidth: true
|
||||
sourceSize.height: parent.width * 0.8
|
||||
fillMode: Image.PreserveAspectFit
|
||||
autoTransform: true
|
||||
Kirigami.Icon {
|
||||
source: "stickers"
|
||||
anchors.fill: parent
|
||||
visible: parent.status !== Image.Ready
|
||||
}
|
||||
}
|
||||
QQC2.Label {
|
||||
id: descriptionLabel
|
||||
text: model.body ?? i18nc("As in 'This sticker has no description'", "No Description")
|
||||
horizontalAlignment: Qt.AlignHCenter
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.Wrap
|
||||
maximumLineCount: 2
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
QQC2.Button {
|
||||
icon.name: "edit-delete"
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
anchors.margins: Kirigami.Units.smallSpacing
|
||||
z: 2
|
||||
onClicked: stickerModel.deleteSticker(emoticonFilterModel.mapToSource(emoticonFilterModel.index(model.index, 0)).row)
|
||||
}
|
||||
}
|
||||
}
|
||||
MobileForm.AbstractFormDelegate {
|
||||
width: stickerFlow.width / 4
|
||||
height: width
|
||||
|
||||
onClicked: pageSettingStack.pushDialogLayer(stickerEditorPage, {
|
||||
description: "",
|
||||
index: -1,
|
||||
url: "",
|
||||
shortcode: "",
|
||||
model: stickerModel,
|
||||
proxyModel: emoticonFilterModel,
|
||||
newSticker: true
|
||||
}, {
|
||||
title: i18nc("@title", "Add Sticker")
|
||||
});
|
||||
contentItem: ColumnLayout {
|
||||
spacing: 0
|
||||
Kirigami.Icon {
|
||||
source: "list-add"
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
QQC2.Label {
|
||||
text: i18n("Add Sticker")
|
||||
horizontalAlignment: Qt.AlignHCenter
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: stickerEditorPage
|
||||
StickerEditorPage {}
|
||||
}
|
||||
}
|
||||
@@ -98,6 +98,8 @@
|
||||
<file alias="ColorScheme.qml">qml/Settings/ColorScheme.qml</file>
|
||||
<file alias="GeneralSettingsPage.qml">qml/Settings/GeneralSettingsPage.qml</file>
|
||||
<file alias="Emoticons.qml">qml/Settings/Emoticons.qml</file>
|
||||
<file alias="StickersPage.qml">qml/Settings/StickersPage.qml</file>
|
||||
<file alias="StickerEditorPage.qml">qml/Settings/StickerEditorPage.qml</file>
|
||||
<file alias="GlobalNotificationsPage.qml">qml/Settings/GlobalNotificationsPage.qml</file>
|
||||
<file alias="NotificationRuleItem.qml">qml/Settings/NotificationRuleItem.qml</file>
|
||||
<file alias="AppearanceSettingsPage.qml">qml/Settings/AppearanceSettingsPage.qml</file>
|
||||
|
||||
Reference in New Issue
Block a user