Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F114837
D188.1732583007.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
21 KB
Referenced Files
None
Subscribers
None
D188.1732583007.diff
View Options
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -173,6 +173,7 @@
ConfirmUploadPopup.qml
StickerPicker.qml
AddStickerPopup.qml
+ StickerPackNameProvider.qml
ConfirmationOverlay.qml
event-types/Simple.qml
event-types/Text.qml
diff --git a/src/contents/ui/AddStickerPopup.qml b/src/contents/ui/AddStickerPopup.qml
--- a/src/contents/ui/AddStickerPopup.qml
+++ b/src/contents/ui/AddStickerPopup.qml
@@ -19,7 +19,12 @@
property var stickerPackList
property var event
- property var currentPack: stickerPackList.at(0)
+ readonly property var availablePacks: stickerPackList.packs
+ readonly property var packNameProvider: Kazv.StickerPackNameProvider {
+ }
+ readonly property var availablePackNames: availablePacks.map(k => packNameProvider.getName(k))
+ readonly property alias packIndex: packChooser.currentIndex
+ property var currentPack: stickerPackList.at(packIndex)
property var stickerSize: Kirigami.Units.iconSizes.enormous
property var addingSticker: false
@@ -37,6 +42,15 @@
source: matrixSdk.mxcUriToHttp(event.content.url)
}
+ ComboBox {
+ id: packChooser
+ objectName: 'packChooser'
+ Layout.fillWidth: true
+ model: addStickerPopup.availablePackNames
+ currentIndex: 0
+ Kirigami.FormData.label: l10n.get('add-sticker-popup-pack-prompt')
+ }
+
TextField {
Layout.fillWidth: true
id: shortCodeInput
@@ -73,7 +87,7 @@
trigger: () => {
addStickerPopup.addingSticker = true;
return matrixSdk.updateStickerPack(
- currentPack.addSticker(shortCodeInput.text, addStickerPopup.event)
+ addStickerPopup.currentPack.addSticker(shortCodeInput.text, addStickerPopup.event)
);
}
onResolved: (success, data) => {
diff --git a/src/contents/ui/StickerPackNameProvider.qml b/src/contents/ui/StickerPackNameProvider.qml
new file mode 100644
--- /dev/null
+++ b/src/contents/ui/StickerPackNameProvider.qml
@@ -0,0 +1,43 @@
+/*
+ * This file is part of kazv.
+ * SPDX-FileCopyrightText: 2024 tusooa <tusooa@kazv.moe>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+import QtQuick
+
+import '.' as Kazv
+
+QtObject {
+ id: provider
+ property var pack
+ readonly property var name: getName(pack)
+ readonly property var roomNameProvider: Kazv.RoomNameProvider {
+ room: sdkVars.roomList.room(pack.roomId)
+ }
+
+ function getName(pack)
+ {
+ if (pack.packName) {
+ return pack.packName;
+ }
+
+ if (pack.isAccountData) {
+ return l10n.get('sticker-picker-user-stickers');
+ } else if (pack.isState) {
+ const roomName = pack === provider.pack
+ ? roomNameProvider.name
+ : roomNameProvider.getName(sdkVars.roomList.room(pack.roomId));
+ if (pack.stateKey) {
+ return l10n.get('sticker-picker-room-sticker-pack-name', {
+ stateKey: pack.stateKey,
+ room: roomName,
+ });
+ } else {
+ return l10n.get('sticker-picker-room-default-sticker-pack-name', {
+ room: roomName,
+ });
+ }
+ }
+ }
+}
diff --git a/src/contents/ui/StickerPicker.qml b/src/contents/ui/StickerPicker.qml
--- a/src/contents/ui/StickerPicker.qml
+++ b/src/contents/ui/StickerPicker.qml
@@ -29,10 +29,14 @@
Repeater {
model: stickerPackList
delegate: TabButton {
+ id: packDelegate
objectName: `stickerPack${index}`
property var pack: stickerPackList.at(index)
+ property var packNameProvider: Kazv.StickerPackNameProvider {
+ pack: packDelegate.pack
+ }
icon.name: 'smiley'
- text: pack.packName ? pack.packName : l10n.get('sticker-picker-user-stickers')
+ text: packNameProvider.name
checked: stickerPicker.currentIndex === index
onClicked: stickerPicker.currentIndex = index
}
diff --git a/src/l10n/cmn-Hans/100-ui.ftl b/src/l10n/cmn-Hans/100-ui.ftl
--- a/src/l10n/cmn-Hans/100-ui.ftl
+++ b/src/l10n/cmn-Hans/100-ui.ftl
@@ -152,6 +152,8 @@
send-message-box-stickers-popup-title = 发送贴纸
sticker-picker-user-stickers = 我的贴纸
+sticker-picker-room-sticker-pack-name = {$room} 中的 {$stateKey}
+sticker-picker-room-default-sticker-pack-name = {$room} 中的默认包
room-timeline-load-more-action = 加载更多
@@ -246,6 +248,7 @@
media-file-menu-add-sticker-action = 添加到贴纸...
add-sticker-popup-title = 添加到贴纸
+add-sticker-popup-pack-prompt = 添加到:
add-sticker-popup-short-code-prompt = 短代码:
add-sticker-popup-short-code-exists-warning = 贴纸包里已经有这个短代码了。上面的贴纸会覆盖已有的。
add-sticker-popup-add-sticker-button = 添加
diff --git a/src/l10n/en/100-ui.ftl b/src/l10n/en/100-ui.ftl
--- a/src/l10n/en/100-ui.ftl
+++ b/src/l10n/en/100-ui.ftl
@@ -156,6 +156,8 @@
send-message-box-stickers-popup-title = Send a sticker
sticker-picker-user-stickers = My stickers
+sticker-picker-room-sticker-pack-name = {$stateKey} in {$room}
+sticker-picker-room-default-sticker-pack-name = Default pack in {$room}
room-timeline-load-more-action = Load more
@@ -268,6 +270,7 @@
media-file-menu-add-sticker-action = Add to sticker...
add-sticker-popup-title = Add to sticker
+add-sticker-popup-pack-prompt = Add to pack:
add-sticker-popup-short-code-prompt = Short code:
add-sticker-popup-short-code-exists-warning = The short code already exists in this pack. The sticker above will override the existing one.
add-sticker-popup-add-sticker-button = Add
diff --git a/src/matrix-sdk.cpp b/src/matrix-sdk.cpp
--- a/src/matrix-sdk.cpp
+++ b/src/matrix-sdk.cpp
@@ -709,6 +709,14 @@
auto eventJson = std::move(source.event).raw().get();
eventJson["type"] = source.eventType;
return sendAccountDataImpl(Event(std::move(eventJson)));
+ } else if (source.source == MatrixStickerPackSource::RoomState) {
+ auto eventJson = std::move(source.event).raw().get();
+ eventJson["type"] = source.eventType;
+ eventJson["state_key"] = source.stateKey;
+ return new MatrixPromise(
+ m_d->clientOnSecondaryRoot
+ .room(source.roomId)
+ .sendStateEvent(Event(std::move(eventJson))));
} else {
return 0;
}
diff --git a/src/matrix-sticker-pack-list.hpp b/src/matrix-sticker-pack-list.hpp
--- a/src/matrix-sticker-pack-list.hpp
+++ b/src/matrix-sticker-pack-list.hpp
@@ -9,7 +9,7 @@
#include <QObject>
#include <QQmlEngine>
-
+#include <QJsonValue>
#include <immer/flex_vector.hpp>
#include <lager/reader.hpp>
@@ -39,4 +39,8 @@
~MatrixStickerPackList() override;
Q_INVOKABLE MatrixStickerPack *at(int index) const;
+
+ LAGER_QT_READER(QJsonValue, packs);
+
+ Q_INVOKABLE MatrixStickerPack *packFor(const QJsonValue &desc) const;
};
diff --git a/src/matrix-sticker-pack-list.cpp b/src/matrix-sticker-pack-list.cpp
--- a/src/matrix-sticker-pack-list.cpp
+++ b/src/matrix-sticker-pack-list.cpp
@@ -5,7 +5,9 @@
*/
#include <kazv-defs.hpp>
-
+#include <QJsonObject>
+#include <QJsonValue>
+#include <QJsonArray>
#include <lager/lenses/optional.hpp>
#include <lager/lenses/at.hpp>
@@ -14,6 +16,7 @@
#include "matrix-sticker-pack-list-p.hpp"
using namespace Kazv;
+using namespace Qt::Literals::StringLiterals;
using ImagePackRoomMap = immer::map<std::string/* room id */, immer::map<std::string/* */, json>>;
@@ -74,9 +77,48 @@
});
}
+static QString getPackDisplayName(const MatrixStickerPackSource &source)
+{
+ auto content = source.event.content().get();
+ if (content.contains("/pack/display_name"_json_pointer)
+ && content["/pack/display_name"_json_pointer].is_string()) {
+ return QString::fromStdString(
+ content["/pack/display_name"_json_pointer]
+ .template get<std::string>());
+ }
+ return u""_s;
+}
+
+static QJsonValue sourceToPacks(const immer::flex_vector<MatrixStickerPackSource> &sources)
+{
+ QJsonArray res;
+ for (const auto &source : sources) {
+ QJsonObject obj{
+ {u"source"_s, source.source},
+ {u"isAccountData"_s, source.source == MatrixStickerPackSource::AccountData},
+ {u"isState"_s, source.source == MatrixStickerPackSource::RoomState},
+ {u"eventType"_s, QString::fromStdString(source.eventType)},
+ {u"roomId"_s, QString::fromStdString(source.roomId)},
+ {u"stateKey"_s, QString::fromStdString(source.stateKey)},
+ {u"packName"_s, getPackDisplayName(source)},
+ };
+ res.append(obj);
+ }
+ return res;
+}
+
+static auto defaultSource = MatrixStickerPackSource{
+ MatrixStickerPackSource::AccountData,
+ accountDataEventType,
+ Event(),
+ "",
+ "",
+};
+
MatrixStickerPackList::MatrixStickerPackList(Client client, QObject *parent)
: KazvAbstractListModel(parent)
, m_events(getEventsFromClient(client))
+ , LAGER_QT(packs)(m_events.map(sourceToPacks))
{
initCountCursor(m_events.map([](const auto &events) {
return static_cast<int>(events.size());
@@ -86,6 +128,7 @@
MatrixStickerPackList::MatrixStickerPackList(Room room, QObject *parent)
: KazvAbstractListModel(parent)
, m_events(getEventsFromRoom(room))
+ , LAGER_QT(packs)(m_events.map(sourceToPacks))
{
initCountCursor(m_events.map([](const auto &events) {
return static_cast<int>(events.size());
@@ -100,13 +143,29 @@
if (events.size() > std::size_t(index)) {
return events[index];
} else {
- return MatrixStickerPackSource{
- MatrixStickerPackSource::AccountData,
- accountDataEventType,
- Event(),
- "",
- "",
- };
+ return defaultSource;
+ }
+ }));
+}
+
+MatrixStickerPack *MatrixStickerPackList::packFor(const QJsonValue &desc) const
+{
+ auto source = MatrixStickerPackSource::Source(desc.toObject()[u"source"_s].toInt());
+ auto eventType = desc.toObject()[u"eventType"_s].toString().toStdString();
+ auto roomId = desc.toObject()[u"roomId"_s].toString().toStdString();
+ auto stateKey = desc.toObject()[u"stateKey"_s].toString().toStdString();
+
+ return new MatrixStickerPack(m_events.map([=](const auto &events) {
+ auto it = std::find_if(events.begin(), events.end(), [=](const auto &s) {
+ return s.source == source
+ && s.eventType == eventType
+ && s.roomId == roomId
+ && s.stateKey == stateKey;
+ });
+ if (it == events.end()) {
+ return defaultSource;
+ } else {
+ return *it;
}
}));
}
diff --git a/src/tests/matrix-sticker-pack-test.cpp b/src/tests/matrix-sticker-pack-test.cpp
--- a/src/tests/matrix-sticker-pack-test.cpp
+++ b/src/tests/matrix-sticker-pack-test.cpp
@@ -9,7 +9,8 @@
#include <memory>
#include <QtTest>
-
+#include <QSet>
+#include <QJsonObject>
#include <lager/state.hpp>
#include <base/event.hpp>
@@ -195,6 +196,64 @@
QVERIFY(hasPack(packs, u"Pack 1"_s));
QVERIFY(hasPack(packs, u"Pack 2"_s));
QVERIFY(hasPack(packs, u"Pack 3"_s));
+
+ auto expectedPacks = QSet<QJsonValue>{
+ QJsonObject{
+ {u"source"_s, MatrixStickerPackSource::AccountData},
+ {u"isAccountData"_s, true},
+ {u"isState"_s, false},
+ {u"eventType"_s, u"im.ponies.user_emotes"_s},
+ {u"roomId"_s, u""_s},
+ {u"stateKey"_s, u""_s},
+ {u"packName"_s, u"Awesome Pack"_s},
+ },
+ QJsonObject{
+ {u"source"_s, MatrixStickerPackSource::RoomState},
+ {u"isAccountData"_s, false},
+ {u"isState"_s, true},
+ {u"eventType"_s, u"im.ponies.room_emotes"_s},
+ {u"roomId"_s, u"!someroom:example.org"_s},
+ {u"stateKey"_s, u""_s},
+ {u"packName"_s, u"Pack 1"_s},
+ },
+ QJsonObject{
+ {u"source"_s, MatrixStickerPackSource::RoomState},
+ {u"isAccountData"_s, false},
+ {u"isState"_s, true},
+ {u"eventType"_s, u"im.ponies.room_emotes"_s},
+ {u"roomId"_s, u"!someroom:example.org"_s},
+ {u"stateKey"_s, u"de.sorunome.mx-puppet-bridge.discord"_s},
+ {u"packName"_s, u"Pack 2"_s},
+ },
+ QJsonObject{
+ {u"source"_s, MatrixStickerPackSource::RoomState},
+ {u"isAccountData"_s, false},
+ {u"isState"_s, true},
+ {u"eventType"_s, u"im.ponies.room_emotes"_s},
+ {u"roomId"_s, u"!someotherroom:example.org"_s},
+ {u"stateKey"_s, u""_s},
+ {u"packName"_s, u"Pack 3"_s},
+ },
+ };
+
+ auto packsArr = stickerPackList->packs().toArray();
+ QCOMPARE(QSet<QJsonValue>(packsArr.begin(), packsArr.end()), expectedPacks);
+
+ auto p1 = toUniquePtr(stickerPackList->packFor(QJsonObject{
+ {u"source"_s, MatrixStickerPackSource::RoomState},
+ {u"eventType"_s, u"im.ponies.room_emotes"_s},
+ {u"roomId"_s, u"!someroom:example.org"_s},
+ {u"stateKey"_s, u""_s},
+ }));
+ QCOMPARE(p1->packName(), u"Pack 1"_s);
+
+ auto p2 = toUniquePtr(stickerPackList->packFor(QJsonObject{
+ {u"source"_s, MatrixStickerPackSource::RoomState},
+ {u"eventType"_s, u"im.ponies.room_emotes"_s},
+ {u"roomId"_s, u"!someotherroom:example.org"_s},
+ {u"stateKey"_s, u"somestatekey"_s},
+ }));
+ QVERIFY(p2->isAccountData());
}
void MatrixStickerPackTest::testStickerPackListInRoom()
@@ -221,6 +280,30 @@
QVERIFY(hasPack(packs, u"Pack 1"_s));
QVERIFY(hasPack(packs, u"Pack 2"_s));
+
+ auto expectedPacks = QSet<QJsonValue>{
+ QJsonObject{
+ {u"source"_s, MatrixStickerPackSource::RoomState},
+ {u"isAccountData"_s, false},
+ {u"isState"_s, true},
+ {u"eventType"_s, u"im.ponies.room_emotes"_s},
+ {u"roomId"_s, u"!someroom:example.org"_s},
+ {u"stateKey"_s, u""_s},
+ {u"packName"_s, u"Pack 1"_s},
+ },
+ QJsonObject{
+ {u"source"_s, MatrixStickerPackSource::RoomState},
+ {u"isAccountData"_s, false},
+ {u"isState"_s, true},
+ {u"eventType"_s, u"im.ponies.room_emotes"_s},
+ {u"roomId"_s, u"!someroom:example.org"_s},
+ {u"stateKey"_s, u"de.sorunome.mx-puppet-bridge.discord"_s},
+ {u"packName"_s, u"Pack 2"_s},
+ },
+ };
+
+ auto packsArr = stickerPackList->packs().toArray();
+ QCOMPARE(QSet<QJsonValue>(packsArr.begin(), packsArr.end()), expectedPacks);
}
void MatrixStickerPackTest::testAddToPack()
diff --git a/src/tests/quick-tests/tst_AddStickerPopup.qml b/src/tests/quick-tests/tst_AddStickerPopup.qml
--- a/src/tests/quick-tests/tst_AddStickerPopup.qml
+++ b/src/tests/quick-tests/tst_AddStickerPopup.qml
@@ -36,11 +36,18 @@
},
})
stickerPackList: ({
- at() {
+ at(index) {
return {
addSticker(shortCode, event) {
return {
- [shortCode]: event.content
+ _packIndex: index,
+ event: {
+ content: {
+ images: {
+ [shortCode]: event.content,
+ },
+ },
+ },
};
},
hasShortCode(shortCode) {
@@ -48,6 +55,10 @@
},
};
},
+ packs: [
+ { packName: 'pack0' },
+ { packName: 'pack1' },
+ ],
})
property var close: mockHelper.noop()
}
@@ -59,6 +70,8 @@
function init() {
addStickerPopup.contentItem.parent = item;
+ findChild(addStickerPopup, 'packChooser').currentIndex = 0;
+ addStickerPopup.addingSticker = false;
}
function cleanup() {
@@ -70,14 +83,13 @@
verify(!findChild(addStickerPopup, 'shortCodeExistsWarning').visible);
const button = findChild(addStickerPopup, 'addStickerButton');
verify(button.enabled);
- // tryVerify(() => false, 50000);
mouseClick(button);
tryVerify(() => matrixSdk.updateStickerPack.calledTimes() === 1);
- verify(Helpers.deepEqual(matrixSdk.updateStickerPack.lastArgs(), [{
+ verify(Helpers.deepEqual(matrixSdk.updateStickerPack.lastArgs()[0].event.content.images, {
'bar': {
url: 'mxc://example.org/something',
}
- }]));
+ }));
tryVerify(() => !button.enabled);
tryVerify(() => findChild(addStickerPopup, 'shortCodeInput').readOnly);
@@ -86,19 +98,30 @@
tryVerify(() => addStickerPopup.close.calledTimes() === 1);
}
+ function test_addStickerOtherPack() {
+ verify(Helpers.deepEqual(addStickerPopup.availablePackNames, ['pack0', 'pack1']));
+ findChild(addStickerPopup, 'packChooser').currentIndex = 1;
+ findChild(addStickerPopup, 'shortCodeInput').text = 'bar';
+ verify(!findChild(addStickerPopup, 'shortCodeExistsWarning').visible);
+ const button = findChild(addStickerPopup, 'addStickerButton');
+ verify(button.enabled);
+ mouseClick(button);
+ tryVerify(() => matrixSdk.updateStickerPack.calledTimes() === 1);
+ compare(matrixSdk.updateStickerPack.lastArgs()[0]._packIndex, 1);
+ }
+
function test_addStickerFailed() {
findChild(addStickerPopup, 'shortCodeInput').text = 'bar';
verify(!findChild(addStickerPopup, 'shortCodeExistsWarning').visible);
const button = findChild(addStickerPopup, 'addStickerButton');
verify(button.enabled);
- // tryVerify(() => false, 50000);
mouseClick(button);
tryVerify(() => matrixSdk.updateStickerPack.calledTimes() === 1);
- verify(Helpers.deepEqual(matrixSdk.updateStickerPack.lastArgs(), [{
+ verify(Helpers.deepEqual(matrixSdk.updateStickerPack.lastArgs()[0].event.content.images, {
'bar': {
url: 'mxc://example.org/something',
}
- }]));
+ }));
matrixSdk.updateStickerPack.lastRetVal().resolve(false, {});
tryVerify(() => button.enabled);
verify(addStickerPopup.close.calledTimes() === 0);
@@ -109,14 +132,13 @@
tryVerify(() => findChild(addStickerPopup, 'shortCodeExistsWarning').visible);
const button = findChild(addStickerPopup, 'addStickerButton');
verify(button.enabled);
- // tryVerify(() => false, 50000);
mouseClick(button);
tryVerify(() => matrixSdk.updateStickerPack.calledTimes() === 1);
- verify(Helpers.deepEqual(matrixSdk.updateStickerPack.lastArgs(), [{
+ verify(Helpers.deepEqual(matrixSdk.updateStickerPack.lastArgs()[0].event.content.images, {
'foo': {
url: 'mxc://example.org/something',
}
- }]));
+ }));
tryVerify(() => !button.enabled);
tryVerify(() => findChild(addStickerPopup, 'shortCodeInput').readOnly);
diff --git a/src/tests/quick-tests/tst_StickerPackNameProvider.qml b/src/tests/quick-tests/tst_StickerPackNameProvider.qml
new file mode 100644
--- /dev/null
+++ b/src/tests/quick-tests/tst_StickerPackNameProvider.qml
@@ -0,0 +1,108 @@
+/*
+ * This file is part of kazv.
+ * SPDX-FileCopyrightText: 2023 tusooa <tusooa@kazv.moe>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+import QtQuick 2.15
+import QtQuick.Layouts 1.15
+import QtTest 1.0
+
+import '../../contents/ui' as Kazv
+import './test-helpers' as TestHelpers
+import 'test-helpers.js' as Helpers
+
+TestCase {
+ id: stickerPackNameProviderTest
+ name: 'StickerPackNameProviderTest'
+ when: true
+
+ property var sdkVars: QtObject {
+ property var userGivenNicknameMap: QtObject {
+ property var map: ({})
+ }
+ property var roomList: QtObject {
+ function room(roomId) {
+ return stickerPackNameProviderTest.roomComp.createObject(stickerPackNameProviderTest, {
+ roomId,
+ name: `Some room ${roomId}`,
+ heroEvents: [],
+ });
+ }
+ }
+ }
+ property var l10n: Helpers.fluentMock
+
+ property var providerFunctional: Kazv.StickerPackNameProvider {}
+ property var providerReactive: Kazv.StickerPackNameProvider {}
+ property var roomComp: Component {
+ QtObject {
+ property var name
+ property var heroEvents
+ property var roomId
+ }
+ }
+ property var packComp: Component {
+ QtObject {
+ property var packName
+ property var isAccountData
+ property var isState
+ property var stateKey
+ property var roomId
+ }
+ }
+
+ function init() {
+ }
+
+ function test_functional() {
+ compare(
+ providerFunctional.getName({ packName: 'some name' }),
+ 'some name');
+
+ compare(providerFunctional.getName({
+ packName: '',
+ isAccountData: true,
+ }), l10n.get('sticker-picker-user-stickers'));
+
+ verify(
+ providerFunctional.getName({
+ packName: '',
+ isState: true,
+ stateKey: '',
+ }).includes('sticker-picker-room-default-sticker-pack-name'));
+ }
+
+ function test_reactiveAgainstPack() {
+ providerReactive.pack = stickerPackNameProviderTest.packComp.createObject(stickerPackNameProviderTest, {
+ packName: 'some name',
+ roomId: '!example:example.com',
+ isState: true,
+ isAccountData: false,
+ });
+
+ compare(providerReactive.name, 'some name');
+
+ providerReactive.pack.packName = 'some other name';
+ compare(providerReactive.name, 'some other name');
+
+ providerReactive.pack.packName = '';
+ verify(providerReactive.name.includes('Some room !example:example.com'));
+
+ providerReactive.pack.isAccountData = true;
+ providerReactive.pack.isState = false;
+ compare(providerReactive.name, l10n.get('sticker-picker-user-stickers'));
+ }
+
+ function test_reactiveAgainstRoom() {
+ providerReactive.pack = packComp.createObject(stickerPackNameProviderTest, {
+ packName: '',
+ roomId: '!example:example.com',
+ isState: true,
+ isAccountData: false,
+ });
+
+ providerReactive.roomNameProvider.room.name = 'Some other room';
+ verify(providerReactive.name.includes('Some other room'));
+ }
+}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Mon, Nov 25, 5:03 PM (9 h, 59 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
39905
Default Alt Text
D188.1732583007.diff (21 KB)
Attached To
Mode
D188: Support adding sticker to packs in room state
Attached
Detach File
Event Timeline
Log In to Comment