mirror of
https://github.com/Nheko-Reborn/nheko.git
synced 2024-11-25 12:38:48 +03:00
Add rows to stickerpicker
This commit is contained in:
parent
4a060b59b2
commit
0dfdba4316
9 changed files with 238 additions and 28 deletions
|
@ -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
|
||||||
|
|
|
@ -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();
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
88
src/GridImagePackModel.cpp
Normal file
88
src/GridImagePackModel.cpp
Normal 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
72
src/GridImagePackModel.h
Normal 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;
|
||||||
|
};
|
|
@ -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>>();
|
||||||
|
|
|
@ -836,17 +836,26 @@ InputBar::getCommandAndArgs(const QString ¤tText) 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
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Reference in a new issue