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

@@ -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' ]

View File

@@ -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

View File

@@ -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;
}

View File

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

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

View File

@@ -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")

View 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
}
}
}

View 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 {}
}
}

View File

@@ -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>