Add rows to stickerpicker

This commit is contained in:
Nicolas Werner 2023-05-19 03:15:55 +02:00
parent 4a060b59b2
commit 0dfdba4316
No known key found for this signature in database
GPG key ID: C8D75E610773F2D9
9 changed files with 238 additions and 28 deletions

View file

@ -456,6 +456,8 @@ set(SRC_FILES
src/ColorImageProvider.h src/ColorImageProvider.h
src/CombinedImagePackModel.cpp src/CombinedImagePackModel.cpp
src/CombinedImagePackModel.h src/CombinedImagePackModel.h
src/GridImagePackModel.cpp
src/GridImagePackModel.h
src/CommandCompleter.cpp src/CommandCompleter.cpp
src/CommandCompleter.h src/CommandCompleter.h
src/CompletionModelRoles.h src/CompletionModelRoles.h

View file

@ -433,7 +433,7 @@ Rectangle {
ToolTip.visible: hovered ToolTip.visible: hovered
ToolTip.text: qsTr("Stickers") ToolTip.text: qsTr("Stickers")
onClicked: stickerPopup.visible ? stickerPopup.close() : stickerPopup.show(stickerButton, room.roomId, function(row) { onClicked: stickerPopup.visible ? stickerPopup.close() : stickerPopup.show(stickerButton, room.roomId, function(row) {
room.input.sticker(stickerPopup.model.sourceModel, row); room.input.sticker(row);
TimelineManager.focusMessageInput(); TimelineManager.focusMessageInput();
}) })

View file

@ -102,19 +102,41 @@ Menu {
} }
} }
// emoji grid Component {
GridView { id: sectionHeading
Rectangle {
width: gridView.width
height: childrenRect.height
color: Nheko.colors.alternateBase
required property string section
Text {
anchors.left: parent.left
anchors.right: parent.right
text: parent.section
color: Nheko.colors.text
font.bold: true
}
}
}
// sticker grid
ListView {
id: gridView id: gridView
model: roomid ? TimelineManager.completerFor("stickers", roomid) : null model: roomid ? TimelineManager.completerFor("stickergrid", roomid) : null
Layout.preferredHeight: cellHeight * 3.5 Layout.preferredHeight: cellHeight * 3.5
Layout.preferredWidth: stickersPerRow * stickerDimPad + 20 - Nheko.paddingSmall Layout.preferredWidth: stickersPerRow * stickerDimPad + 20 - Nheko.paddingSmall
cellWidth: stickerDimPad property int cellHeight: stickerDimPad
cellHeight: stickerDimPad
boundsBehavior: Flickable.StopAtBounds boundsBehavior: Flickable.StopAtBounds
clip: true clip: true
currentIndex: -1 // prevent sorting from stealing focus currentIndex: -1 // prevent sorting from stealing focus
cacheBuffer: 500
section.property: "packname"
section.criteria: ViewSection.FullString
section.delegate: sectionHeading
section.labelPositioning: ViewSection.InlineLabels | ViewSection.CurrentLabelAtStart
ScrollHelper { ScrollHelper {
flickable: parent flickable: parent
@ -123,23 +145,29 @@ Menu {
} }
// Individual emoji // Individual emoji
delegate: Row {
required property var row;
Repeater {
model: row
delegate: AbstractButton { delegate: AbstractButton {
width: stickerDim width: stickerDim
height: stickerDim height: stickerDim
hoverEnabled: true hoverEnabled: true
ToolTip.text: ":" + model.shortcode + ": - " + model.body ToolTip.text: ":" + modelData.shortcode + ": - " + modelData.body
ToolTip.visible: hovered ToolTip.visible: hovered
// TODO: maybe add favorites at some point? // TODO: maybe add favorites at some point?
onClicked: { onClicked: {
console.debug("Picked " + model.shortcode); console.debug("Picked " + modelData.descriptor);
stickerPopup.close(); stickerPopup.close();
callback(model.originalRow); callback(modelData.descriptor);
} }
contentItem: Image { contentItem: Image {
height: stickerDim height: stickerDim
width: stickerDim width: stickerDim
source: model.url.replace("mxc://", "image://MxcImage/") + "?scale" source: modelData.url.replace("mxc://", "image://MxcImage/") + "?scale"
fillMode: Image.PreserveAspectFit fillMode: Image.PreserveAspectFit
} }
@ -149,6 +177,8 @@ Menu {
radius: 5 radius: 5
} }
}
}
} }
ScrollBar.vertical: ScrollBar { ScrollBar.vertical: ScrollBar {

View file

@ -0,0 +1,88 @@
// SPDX-FileCopyrightText: Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
#include "GridImagePackModel.h"
#include "Cache_p.h"
#include "CompletionModelRoles.h"
#include <algorithm>
Q_DECLARE_METATYPE(StickerImage)
GridImagePackModel::GridImagePackModel(const std::string &roomId, bool stickers, QObject *parent)
: QAbstractListModel(parent)
, room_id(roomId)
{
[[maybe_unused]] static auto id = qRegisterMetaType<StickerImage>();
auto originalPacks = cache::client()->getImagePacks(room_id, stickers);
for (auto &pack : originalPacks) {
PackDesc newPack{};
newPack.packname =
pack.pack.pack ? QString::fromStdString(pack.pack.pack->display_name) : QString();
newPack.room_id = pack.source_room;
newPack.state_key = pack.state_key;
newPack.images.resize(pack.pack.images.size());
std::ranges::transform(std::move(pack.pack.images), newPack.images.begin(), [](auto &&img) {
return std::pair(std::move(img.second), QString::fromStdString(img.first));
});
size_t packRowCount =
(newPack.images.size() / columns) + (newPack.images.size() % columns ? 1 : 0);
newPack.firstRow = rowToPack.size();
for (size_t i = 0; i < packRowCount; i++)
rowToPack.push_back(packs.size());
packs.push_back(std::move(newPack));
}
}
int
GridImagePackModel::rowCount(const QModelIndex &) const
{
return (int)rowToPack.size();
}
QHash<int, QByteArray>
GridImagePackModel::roleNames() const
{
return {
{Roles::PackName, "packname"},
{Roles::Row, "row"},
};
}
QVariant
GridImagePackModel::data(const QModelIndex &index, int role) const
{
if (index.row() < rowCount() && index.row() >= 0) {
const auto &pack = packs[rowToPack[index.row()]];
switch (role) {
case Roles::PackName:
return pack.packname;
case Roles::Row: {
std::size_t offset = static_cast<std::size_t>(index.row()) - pack.firstRow;
QList<StickerImage> imgs;
auto endOffset = std::min((offset + 1) * 3, pack.images.size());
for (std::size_t img = offset * 3; img < endOffset; img++) {
const auto &data = pack.images.at(img);
imgs.push_back({.url = QString::fromStdString(data.first.url),
.shortcode = data.second,
.body = QString::fromStdString(data.first.body),
.descriptor_ = std::vector{
pack.room_id,
pack.state_key,
data.second.toStdString(),
}});
}
return QVariant::fromValue(imgs);
}
default:
return {};
}
}
return {};
}

72
src/GridImagePackModel.h Normal file
View file

@ -0,0 +1,72 @@
// SPDX-FileCopyrightText: Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <QAbstractListModel>
#include <QObject>
#include <QString>
#include <mtx/events/mscs/image_packs.hpp>
struct StickerImage
{
Q_GADGET
Q_PROPERTY(QString url MEMBER url CONSTANT)
Q_PROPERTY(QString shortcode MEMBER shortcode CONSTANT)
Q_PROPERTY(QString body MEMBER body CONSTANT)
Q_PROPERTY(QStringList descriptor READ descriptor CONSTANT)
public:
QStringList descriptor() const
{
if (descriptor_.size() == 3)
return QStringList{
QString::fromStdString(descriptor_[0]),
QString::fromStdString(descriptor_[1]),
QString::fromStdString(descriptor_[2]),
};
else
return {};
}
QString url;
QString shortcode;
QString body;
std::vector<std::string> descriptor_; // roomid, statekey, shortcode
};
class GridImagePackModel final : public QAbstractListModel
{
Q_OBJECT
public:
enum Roles
{
PackName = Qt::UserRole,
Row,
};
GridImagePackModel(const std::string &roomId, bool stickers, QObject *parent = nullptr);
QHash<int, QByteArray> roleNames() const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role) const override;
private:
std::string room_id;
struct PackDesc
{
QString packname;
QString packavatar;
std::string room_id, state_key;
std::vector<std::pair<mtx::events::msc2545::PackImage, QString>> images;
std::size_t firstRow;
};
std::vector<PackDesc> packs;
std::vector<size_t> rowToPack;
int columns = 3;
};

View file

@ -19,6 +19,7 @@
#include "CompletionProxyModel.h" #include "CompletionProxyModel.h"
#include "Config.h" #include "Config.h"
#include "EventAccessors.h" #include "EventAccessors.h"
#include "GridImagePackModel.h"
#include "ImagePackListModel.h" #include "ImagePackListModel.h"
#include "InviteesModel.h" #include "InviteesModel.h"
#include "JdenticonProvider.h" #include "JdenticonProvider.h"
@ -150,6 +151,7 @@ MainWindow::registerQmlTypes()
qRegisterMetaType<mtx::responses::User>(); qRegisterMetaType<mtx::responses::User>();
qRegisterMetaType<mtx::responses::Profile>(); qRegisterMetaType<mtx::responses::Profile>();
qRegisterMetaType<CombinedImagePackModel *>(); qRegisterMetaType<CombinedImagePackModel *>();
qRegisterMetaType<GridImagePackModel *>();
qRegisterMetaType<RoomSettingsAllowedRoomsModel *>(); qRegisterMetaType<RoomSettingsAllowedRoomsModel *>();
qRegisterMetaType<mtx::events::collections::TimelineEvents>(); qRegisterMetaType<mtx::events::collections::TimelineEvents>();
qRegisterMetaType<std::vector<DeviceInfo>>(); qRegisterMetaType<std::vector<DeviceInfo>>();

View file

@ -836,17 +836,26 @@ InputBar::getCommandAndArgs(const QString &currentText) const
} }
void void
InputBar::sticker(CombinedImagePackModel *model, int row) InputBar::sticker(QStringList descriptor)
{ {
if (!model || row < 0) if (descriptor.size() != 3)
return; return;
auto img = model->imageAt(row); auto originalPacks = cache::client()->getImagePacks(room->roomId().toStdString(), true);
auto source_room = descriptor[0].toStdString();
auto state_key = descriptor[1].toStdString();
auto short_code = descriptor[2].toStdString();
for (auto &pack : originalPacks) {
if (pack.source_room == source_room && pack.state_key == state_key &&
pack.pack.images.contains(short_code)) {
auto img = pack.pack.images.at(short_code);
mtx::events::msg::StickerImage sticker{}; mtx::events::msg::StickerImage sticker{};
sticker.info = img.info.value_or(mtx::common::ImageInfo{}); sticker.info = img.info.value_or(mtx::common::ImageInfo{});
sticker.url = img.url; sticker.url = img.url;
sticker.body = img.body.empty() ? model->shortcodeAt(row).toStdString() : img.body; sticker.body = img.body.empty() ? short_code : img.body;
// workaround for https://github.com/vector-im/element-ios/issues/2353 // workaround for https://github.com/vector-im/element-ios/issues/2353
sticker.info.thumbnail_url = sticker.url; sticker.info.thumbnail_url = sticker.url;
@ -858,6 +867,9 @@ InputBar::sticker(CombinedImagePackModel *model, int row)
sticker.relations = generateRelations(); sticker.relations = generateRelations();
room->sendMessageEvent(sticker, mtx::events::EventType::Sticker); room->sendMessageEvent(sticker, mtx::events::EventType::Sticker);
break;
}
}
} }
bool bool

View file

@ -217,7 +217,7 @@ public slots:
MarkdownOverride useMarkdown = MarkdownOverride::NOT_SPECIFIED, MarkdownOverride useMarkdown = MarkdownOverride::NOT_SPECIFIED,
bool rainbowify = false); bool rainbowify = false);
void reaction(const QString &reactedEvent, const QString &reactionKey); void reaction(const QString &reactedEvent, const QString &reactionKey);
void sticker(CombinedImagePackModel *model, int row); void sticker(QStringList descriptor);
void acceptUploads(); void acceptUploads();
void declineUploads(); void declineUploads();

View file

@ -17,6 +17,7 @@
#include "CommandCompleter.h" #include "CommandCompleter.h"
#include "CompletionProxyModel.h" #include "CompletionProxyModel.h"
#include "EventAccessors.h" #include "EventAccessors.h"
#include "GridImagePackModel.h"
#include "ImagePackListModel.h" #include "ImagePackListModel.h"
#include "InviteesModel.h" #include "InviteesModel.h"
#include "Logging.h" #include "Logging.h"
@ -477,6 +478,9 @@ TimelineViewManager::completerFor(const QString &completerName, const QString &r
auto proxy = new CompletionProxyModel(stickerModel, 1, static_cast<size_t>(-1) / 4); auto proxy = new CompletionProxyModel(stickerModel, 1, static_cast<size_t>(-1) / 4);
stickerModel->setParent(proxy); stickerModel->setParent(proxy);
return proxy; return proxy;
} else if (completerName == QLatin1String("stickergrid")) {
auto stickerModel = new GridImagePackModel(roomId.toStdString(), true);
return stickerModel;
} else if (completerName == QLatin1String("customEmoji")) { } else if (completerName == QLatin1String("customEmoji")) {
auto stickerModel = new CombinedImagePackModel(roomId.toStdString(), false); auto stickerModel = new CombinedImagePackModel(roomId.toStdString(), false);
auto proxy = new CompletionProxyModel(stickerModel); auto proxy = new CompletionProxyModel(stickerModel);