diff --git a/CMakeLists.txt b/CMakeLists.txt
index 9f824048..e8bc855d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -381,7 +381,7 @@ if(USE_BUNDLED_MTXCLIENT)
FetchContent_Declare(
MatrixClient
GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git
- GIT_TAG 316a4040785ee2eabac7ef5ce7b4acb71c48f6eb
+ GIT_TAG e5688a2c5987a614b5055595f991f18568127bd2
)
set(BUILD_LIB_EXAMPLES OFF CACHE INTERNAL "")
set(BUILD_LIB_TESTS OFF CACHE INTERNAL "")
diff --git a/io.github.NhekoReborn.Nheko.yaml b/io.github.NhekoReborn.Nheko.yaml
index 0fa450b3..2c0c5ebf 100644
--- a/io.github.NhekoReborn.Nheko.yaml
+++ b/io.github.NhekoReborn.Nheko.yaml
@@ -161,7 +161,7 @@ modules:
buildsystem: cmake-ninja
name: mtxclient
sources:
- - commit: 316a4040785ee2eabac7ef5ce7b4acb71c48f6eb
+ - commit: e5688a2c5987a614b5055595f991f18568127bd2
type: git
url: https://github.com/Nheko-Reborn/mtxclient.git
- config-opts:
diff --git a/resources/qml/Avatar.qml b/resources/qml/Avatar.qml
index 6c12952a..9685dde1 100644
--- a/resources/qml/Avatar.qml
+++ b/resources/qml/Avatar.qml
@@ -11,10 +11,11 @@ import im.nheko 1.0
Rectangle {
id: avatar
- property alias url: img.source
+ property string url
property string userid
property string displayName
property alias textColor: label.color
+ property bool crop: true
signal clicked(var mouse)
@@ -44,12 +45,13 @@ Rectangle {
anchors.fill: parent
asynchronous: true
- fillMode: Image.PreserveAspectCrop
+ fillMode: avatar.crop ? Image.PreserveAspectCrop : Image.PreserveAspectFit
mipmap: true
smooth: true
sourceSize.width: avatar.width
sourceSize.height: avatar.height
layer.enabled: true
+ source: avatar.url + ((avatar.crop || !avatar.url) ? "" : "?scale")
MouseArea {
id: mouseArea
diff --git a/resources/qml/RoomSettings.qml b/resources/qml/RoomSettings.qml
index 6ba080c4..69cf427c 100644
--- a/resources/qml/RoomSettings.qml
+++ b/resources/qml/RoomSettings.qml
@@ -154,7 +154,7 @@ ApplicationWindow {
GridLayout {
columns: 2
- rowSpacing: 10
+ rowSpacing: Nheko.paddingLarge
MatrixText {
text: qsTr("SETTINGS")
@@ -180,7 +180,7 @@ ApplicationWindow {
}
MatrixText {
- text: "Room access"
+ text: qsTr("Room access")
Layout.fillWidth: true
}
diff --git a/resources/qml/ScrollHelper.qml b/resources/qml/ScrollHelper.qml
index 2dd56f27..e584ae3d 100644
--- a/resources/qml/ScrollHelper.qml
+++ b/resources/qml/ScrollHelper.qml
@@ -30,6 +30,10 @@ MouseArea {
property alias enabled: root.enabled
function calculateNewPosition(flickableItem, wheel) {
+ // breaks ListView's with headers...
+ //if (typeof (flickableItem.headerItem) !== "undefined" && flickableItem.headerItem)
+ // minYExtent += flickableItem.headerItem.height;
+
//Nothing to scroll
if (flickableItem.contentHeight < flickableItem.height)
return flickableItem.contentY;
@@ -55,9 +59,6 @@ MouseArea {
var minYExtent = flickableItem.originY + flickableItem.topMargin;
var maxYExtent = (flickableItem.contentHeight + flickableItem.bottomMargin + flickableItem.originY) - flickableItem.height;
- if (typeof (flickableItem.headerItem) !== "undefined" && flickableItem.headerItem)
- minYExtent += flickableItem.headerItem.height;
-
//Avoid overscrolling
return Math.max(minYExtent, Math.min(maxYExtent, flickableItem.contentY - pixelDelta));
}
diff --git a/resources/qml/components/AvatarListTile.qml b/resources/qml/components/AvatarListTile.qml
new file mode 100644
index 00000000..36c26a97
--- /dev/null
+++ b/resources/qml/components/AvatarListTile.qml
@@ -0,0 +1,133 @@
+// SPDX-FileCopyrightText: 2021 Nheko Contributors
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+import ".."
+import QtQuick 2.15
+import QtQuick.Controls 2.15
+import QtQuick.Layouts 1.15
+import im.nheko 1.0
+
+Rectangle {
+ id: tile
+
+ property color background: Nheko.colors.window
+ property color importantText: Nheko.colors.text
+ property color unimportantText: Nheko.colors.buttonText
+ property color bubbleBackground: Nheko.colors.highlight
+ property color bubbleText: Nheko.colors.highlightedText
+ property int avatarSize: Math.ceil(fontMetrics.lineSpacing * 2.3)
+ required property string avatarUrl
+ required property string title
+ required property string subtitle
+ required property int index
+ required property int selectedIndex
+ property bool crop: true
+
+ color: background
+ height: avatarSize + 2 * Nheko.paddingMedium
+ width: ListView.view.width
+ state: "normal"
+ states: [
+ State {
+ name: "highlight"
+ when: hovered.hovered && !(index == selectedIndex)
+
+ PropertyChanges {
+ target: tile
+ background: Nheko.colors.dark
+ importantText: Nheko.colors.brightText
+ unimportantText: Nheko.colors.brightText
+ bubbleBackground: Nheko.colors.highlight
+ bubbleText: Nheko.colors.highlightedText
+ }
+
+ },
+ State {
+ name: "selected"
+ when: index == selectedIndex
+
+ PropertyChanges {
+ target: tile
+ background: Nheko.colors.highlight
+ importantText: Nheko.colors.highlightedText
+ unimportantText: Nheko.colors.highlightedText
+ bubbleBackground: Nheko.colors.highlightedText
+ bubbleText: Nheko.colors.highlight
+ }
+
+ }
+ ]
+
+ HoverHandler {
+ id: hovered
+ }
+
+ RowLayout {
+ spacing: Nheko.paddingMedium
+ anchors.fill: parent
+ anchors.margins: Nheko.paddingMedium
+
+ Avatar {
+ id: avatar
+
+ enabled: false
+ Layout.alignment: Qt.AlignVCenter
+ height: avatarSize
+ width: avatarSize
+ url: tile.avatarUrl.replace("mxc://", "image://MxcImage/")
+ displayName: title
+ crop: tile.crop
+ }
+
+ ColumnLayout {
+ id: textContent
+
+ Layout.alignment: Qt.AlignLeft
+ Layout.fillWidth: true
+ Layout.minimumWidth: 100
+ width: parent.width - avatar.width
+ Layout.preferredWidth: parent.width - avatar.width
+ spacing: Nheko.paddingSmall
+
+ RowLayout {
+ Layout.fillWidth: true
+ spacing: 0
+
+ ElidedLabel {
+ Layout.alignment: Qt.AlignBottom
+ color: tile.importantText
+ elideWidth: textContent.width - Nheko.paddingMedium
+ fullText: title
+ textFormat: Text.PlainText
+ }
+
+ Item {
+ Layout.fillWidth: true
+ }
+
+ }
+
+ RowLayout {
+ Layout.fillWidth: true
+ spacing: 0
+
+ ElidedLabel {
+ color: tile.unimportantText
+ font.pixelSize: fontMetrics.font.pixelSize * 0.9
+ elideWidth: textContent.width - Nheko.paddingSmall
+ fullText: subtitle
+ textFormat: Text.PlainText
+ }
+
+ Item {
+ Layout.fillWidth: true
+ }
+
+ }
+
+ }
+
+ }
+
+}
diff --git a/resources/qml/dialogs/ImagePackEditorDialog.qml b/resources/qml/dialogs/ImagePackEditorDialog.qml
new file mode 100644
index 00000000..0049d3b4
--- /dev/null
+++ b/resources/qml/dialogs/ImagePackEditorDialog.qml
@@ -0,0 +1,283 @@
+// SPDX-FileCopyrightText: 2021 Nheko Contributors
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+import ".."
+import "../components"
+import QtQuick 2.12
+import QtQuick.Controls 2.12
+import QtQuick.Layouts 1.12
+import im.nheko 1.0
+
+ApplicationWindow {
+ //Component.onCompleted: Nheko.reparent(win)
+
+ id: win
+
+ property int avatarSize: Math.ceil(fontMetrics.lineSpacing * 2.3)
+ property SingleImagePackModel imagePack
+ property int currentImageIndex: -1
+ readonly property int stickerDim: 128
+ readonly property int stickerDimPad: 128 + Nheko.paddingSmall
+
+ title: qsTr("Editing image pack")
+ height: 600
+ width: 600
+ palette: Nheko.colors
+ color: Nheko.colors.base
+ modality: Qt.WindowModal
+ flags: Qt.Dialog | Qt.WindowCloseButtonHint
+
+ AdaptiveLayout {
+ id: adaptiveView
+
+ anchors.fill: parent
+ singlePageMode: false
+ pageIndex: 0
+
+ AdaptiveLayoutElement {
+ id: packlistC
+
+ visible: Settings.groupView
+ minimumWidth: 200
+ collapsedWidth: 200
+ preferredWidth: 300
+ maximumWidth: 300
+ clip: true
+
+ ListView {
+ //required property bool isEmote
+ //required property bool isSticker
+
+ model: imagePack
+
+ ScrollHelper {
+ flickable: parent
+ anchors.fill: parent
+ enabled: !Settings.mobileMode
+ }
+
+ header: AvatarListTile {
+ title: imagePack.packname
+ avatarUrl: imagePack.avatarUrl
+ subtitle: imagePack.statekey
+ index: -1
+ selectedIndex: currentImageIndex
+
+ TapHandler {
+ onSingleTapped: currentImageIndex = -1
+ }
+
+ Rectangle {
+ anchors.left: parent.left
+ anchors.verticalCenter: parent.verticalCenter
+ height: parent.height - Nheko.paddingSmall * 2
+ width: 3
+ color: Nheko.colors.highlight
+ }
+
+ }
+
+ delegate: AvatarListTile {
+ id: packItem
+
+ property color background: Nheko.colors.window
+ property color importantText: Nheko.colors.text
+ property color unimportantText: Nheko.colors.buttonText
+ property color bubbleBackground: Nheko.colors.highlight
+ property color bubbleText: Nheko.colors.highlightedText
+ required property string shortCode
+ required property string url
+ required property string body
+
+ title: shortCode
+ subtitle: body
+ avatarUrl: url
+ selectedIndex: currentImageIndex
+ crop: false
+
+ TapHandler {
+ onSingleTapped: currentImageIndex = index
+ }
+
+ }
+
+ }
+
+ }
+
+ AdaptiveLayoutElement {
+ id: packinfoC
+
+ Rectangle {
+ color: Nheko.colors.window
+
+ GridLayout {
+ anchors.fill: parent
+ anchors.margins: Nheko.paddingMedium
+ visible: currentImageIndex == -1
+ enabled: visible
+ columns: 2
+ rowSpacing: Nheko.paddingLarge
+
+ Avatar {
+ Layout.columnSpan: 2
+ url: imagePack.avatarUrl.replace("mxc://", "image://MxcImage/")
+ displayName: imagePack.packname
+ height: 130
+ width: 130
+ crop: false
+ Layout.alignment: Qt.AlignHCenter
+ }
+
+ MatrixText {
+ visible: imagePack.roomid
+ text: qsTr("State key")
+ }
+
+ MatrixTextField {
+ visible: imagePack.roomid
+ Layout.fillWidth: true
+ text: imagePack.statekey
+ onTextEdited: imagePack.statekey = text
+ }
+
+ MatrixText {
+ text: qsTr("Packname")
+ }
+
+ MatrixTextField {
+ Layout.fillWidth: true
+ text: imagePack.packname
+ onTextEdited: imagePack.packname = text
+ }
+
+ MatrixText {
+ text: qsTr("Attrbution")
+ }
+
+ MatrixTextField {
+ Layout.fillWidth: true
+ text: imagePack.attribution
+ onTextEdited: imagePack.attribution = text
+ }
+
+ MatrixText {
+ text: qsTr("Use as Emoji")
+ }
+
+ ToggleButton {
+ checked: imagePack.isEmotePack
+ onToggled: imagePack.isEmotePack = checked
+ Layout.alignment: Qt.AlignRight
+ }
+
+ MatrixText {
+ text: qsTr("Use as Sticker")
+ }
+
+ ToggleButton {
+ checked: imagePack.isStickerPack
+ onToggled: imagePack.isStickerPack = checked
+ Layout.alignment: Qt.AlignRight
+ }
+
+ Item {
+ Layout.columnSpan: 2
+ Layout.fillHeight: true
+ }
+
+ }
+
+ GridLayout {
+ anchors.fill: parent
+ anchors.margins: Nheko.paddingMedium
+ visible: currentImageIndex >= 0
+ enabled: visible
+ columns: 2
+ rowSpacing: Nheko.paddingLarge
+
+ Avatar {
+ Layout.columnSpan: 2
+ url: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.Url).replace("mxc://", "image://MxcImage/")
+ displayName: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.ShortCode)
+ height: 130
+ width: 130
+ crop: false
+ Layout.alignment: Qt.AlignHCenter
+ }
+
+ MatrixText {
+ text: qsTr("Shortcode")
+ }
+
+ MatrixTextField {
+ Layout.fillWidth: true
+ text: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.ShortCode)
+ onTextEdited: imagePack.setData(imagePack.index(currentImageIndex, 0), text, SingleImagePackModel.ShortCode)
+ }
+
+ MatrixText {
+ text: qsTr("Body")
+ }
+
+ MatrixTextField {
+ Layout.fillWidth: true
+ text: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.Body)
+ onTextEdited: imagePack.setData(imagePack.index(currentImageIndex, 0), text, SingleImagePackModel.Body)
+ }
+
+ MatrixText {
+ text: qsTr("Use as Emoji")
+ }
+
+ ToggleButton {
+ checked: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.IsEmote)
+ onToggled: imagePack.setData(imagePack.index(currentImageIndex, 0), text, SingleImagePackModel.IsEmote)
+ Layout.alignment: Qt.AlignRight
+ }
+
+ MatrixText {
+ text: qsTr("Use as Sticker")
+ }
+
+ ToggleButton {
+ checked: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.IsSticker)
+ onToggled: imagePack.setData(imagePack.index(currentImageIndex, 0), text, SingleImagePackModel.IsSticker)
+ Layout.alignment: Qt.AlignRight
+ }
+
+ Item {
+ Layout.columnSpan: 2
+ Layout.fillHeight: true
+ }
+
+ }
+
+ }
+
+ }
+
+ }
+
+ footer: DialogButtonBox {
+ id: buttons
+
+ Button {
+ text: qsTr("Cancel")
+ DialogButtonBox.buttonRole: DialogButtonBox.DestructiveRole
+ onClicked: win.close()
+ }
+
+ Button {
+ text: qsTr("Save")
+ DialogButtonBox.buttonRole: DialogButtonBox.ApplyRole
+ onClicked: {
+ imagePack.save();
+ win.close();
+ }
+ }
+
+ }
+
+}
diff --git a/resources/qml/dialogs/ImagePackSettingsDialog.qml b/resources/qml/dialogs/ImagePackSettingsDialog.qml
index 3d830bf7..c57867fd 100644
--- a/resources/qml/dialogs/ImagePackSettingsDialog.qml
+++ b/resources/qml/dialogs/ImagePackSettingsDialog.qml
@@ -20,14 +20,22 @@ ApplicationWindow {
readonly property int stickerDimPad: 128 + Nheko.paddingSmall
title: qsTr("Image pack settings")
- height: 400
- width: 600
+ height: 600
+ width: 800
palette: Nheko.colors
color: Nheko.colors.base
modality: Qt.NonModal
flags: Qt.Dialog | Qt.WindowCloseButtonHint
Component.onCompleted: Nheko.reparent(win)
+ Component {
+ id: packEditor
+
+ ImagePackEditorDialog {
+ }
+
+ }
+
AdaptiveLayout {
id: adaptiveView
@@ -54,7 +62,7 @@ ApplicationWindow {
enabled: !Settings.mobileMode
}
- delegate: Rectangle {
+ delegate: AvatarListTile {
id: packItem
property color background: Nheko.colors.window
@@ -63,131 +71,24 @@ ApplicationWindow {
property color bubbleBackground: Nheko.colors.highlight
property color bubbleText: Nheko.colors.highlightedText
required property string displayName
- required property string avatarUrl
required property bool fromAccountData
required property bool fromCurrentRoom
- required property int index
- color: background
- height: avatarSize + 2 * Nheko.paddingMedium
- width: ListView.view.width
- state: "normal"
- states: [
- State {
- name: "highlight"
- when: hovered.hovered && !(index == currentPackIndex)
-
- PropertyChanges {
- target: packItem
- background: Nheko.colors.dark
- importantText: Nheko.colors.brightText
- unimportantText: Nheko.colors.brightText
- bubbleBackground: Nheko.colors.highlight
- bubbleText: Nheko.colors.highlightedText
- }
-
- },
- State {
- name: "selected"
- when: index == currentPackIndex
-
- PropertyChanges {
- target: packItem
- background: Nheko.colors.highlight
- importantText: Nheko.colors.highlightedText
- unimportantText: Nheko.colors.highlightedText
- bubbleBackground: Nheko.colors.highlightedText
- bubbleText: Nheko.colors.highlight
- }
-
- }
- ]
+ title: displayName
+ subtitle: {
+ if (fromAccountData)
+ return qsTr("Private pack");
+ else if (fromCurrentRoom)
+ return qsTr("Pack from this room");
+ else
+ return qsTr("Globally enabled pack");
+ }
+ selectedIndex: currentPackIndex
TapHandler {
- margin: -Nheko.paddingSmall
onSingleTapped: currentPackIndex = index
}
- HoverHandler {
- id: hovered
- }
-
- RowLayout {
- spacing: Nheko.paddingMedium
- anchors.fill: parent
- anchors.margins: Nheko.paddingMedium
-
- Avatar {
- // In the future we could show an online indicator by setting the userid for the avatar
- //userid: Nheko.currentUser.userid
-
- id: avatar
-
- enabled: false
- Layout.alignment: Qt.AlignVCenter
- height: avatarSize
- width: avatarSize
- url: avatarUrl.replace("mxc://", "image://MxcImage/")
- displayName: packItem.displayName
- }
-
- ColumnLayout {
- id: textContent
-
- Layout.alignment: Qt.AlignLeft
- Layout.fillWidth: true
- Layout.minimumWidth: 100
- width: parent.width - avatar.width
- Layout.preferredWidth: parent.width - avatar.width
- spacing: Nheko.paddingSmall
-
- RowLayout {
- Layout.fillWidth: true
- spacing: 0
-
- ElidedLabel {
- Layout.alignment: Qt.AlignBottom
- color: packItem.importantText
- elideWidth: textContent.width - Nheko.paddingMedium
- fullText: displayName
- textFormat: Text.PlainText
- }
-
- Item {
- Layout.fillWidth: true
- }
-
- }
-
- RowLayout {
- Layout.fillWidth: true
- spacing: 0
-
- ElidedLabel {
- color: packItem.unimportantText
- font.pixelSize: fontMetrics.font.pixelSize * 0.9
- elideWidth: textContent.width - Nheko.paddingSmall
- fullText: {
- if (fromAccountData)
- return qsTr("Private pack");
- else if (fromCurrentRoom)
- return qsTr("Pack from this room");
- else
- return qsTr("Globally enabled pack");
- }
- textFormat: Text.PlainText
- }
-
- Item {
- Layout.fillWidth: true
- }
-
- }
-
- }
-
- }
-
}
}
@@ -201,15 +102,10 @@ ApplicationWindow {
color: Nheko.colors.window
ColumnLayout {
- //Button {
- // Layout.alignment: Qt.AlignHCenter
- // text: qsTr("Edit")
- // enabled: currentPack.canEdit
- //}
-
id: packinfo
property string packName: currentPack ? currentPack.packname : ""
+ property string attribution: currentPack ? currentPack.attribution : ""
property string avatarUrl: currentPack ? currentPack.avatarUrl : ""
anchors.fill: parent
@@ -227,8 +123,18 @@ ApplicationWindow {
MatrixText {
text: packinfo.packName
- font.pixelSize: 24
+ font.pixelSize: Math.ceil(fontMetrics.pixelSize * 1.1)
+ horizontalAlignment: TextEdit.AlignHCenter
Layout.alignment: Qt.AlignHCenter
+ Layout.preferredWidth: packinfoC.width - Nheko.paddingLarge * 2
+ }
+
+ MatrixText {
+ text: packinfo.attribution
+ wrapMode: TextEdit.Wrap
+ horizontalAlignment: TextEdit.AlignHCenter
+ Layout.alignment: Qt.AlignHCenter
+ Layout.preferredWidth: packinfoC.width - Nheko.paddingLarge * 2
}
GridLayout {
@@ -250,6 +156,18 @@ ApplicationWindow {
}
+ Button {
+ Layout.alignment: Qt.AlignHCenter
+ text: qsTr("Edit")
+ enabled: currentPack.canEdit
+ onClicked: {
+ var dialog = packEditor.createObject(timelineRoot, {
+ "imagePack": currentPack
+ });
+ dialog.show();
+ }
+ }
+
GridView {
Layout.fillHeight: true
Layout.fillWidth: true
@@ -272,7 +190,7 @@ ApplicationWindow {
width: stickerDim
height: stickerDim
hoverEnabled: true
- ToolTip.text: ":" + model.shortcode + ": - " + model.body
+ ToolTip.text: ":" + model.shortCode + ": - " + model.body
ToolTip.visible: hovered
contentItem: Image {
diff --git a/resources/res.qrc b/resources/res.qrc
index c911653c..d7187f42 100644
--- a/resources/res.qrc
+++ b/resources/res.qrc
@@ -160,6 +160,7 @@
qml/device-verification/Success.qml
qml/dialogs/InputDialog.qml
qml/dialogs/ImagePackSettingsDialog.qml
+ qml/dialogs/ImagePackEditorDialog.qml
qml/ui/Ripple.qml
qml/ui/Spinner.qml
qml/ui/animations/BlinkAnimation.qml
@@ -173,6 +174,7 @@
qml/voip/VideoCall.qml
qml/components/AdaptiveLayout.qml
qml/components/AdaptiveLayoutElement.qml
+ qml/components/AvatarListTile.qml
qml/components/FlatButton.qml
qml/RoomMembers.qml
qml/InviteDialog.qml
diff --git a/src/Cache.cpp b/src/Cache.cpp
index 291df053..f3f3dbb6 100644
--- a/src/Cache.cpp
+++ b/src/Cache.cpp
@@ -125,7 +125,7 @@ template
bool
containsStateUpdates(const T &e)
{
- return std::visit([](const auto &ev) { return Cache::isStateEvent(ev); }, e);
+ return std::visit([](const auto &ev) { return Cache::isStateEvent_; }, e);
}
bool
diff --git a/src/Cache_p.h b/src/Cache_p.h
index 5d700658..30c365a6 100644
--- a/src/Cache_p.h
+++ b/src/Cache_p.h
@@ -291,15 +291,9 @@ public:
std::optional secret(const std::string name);
template
- static constexpr bool isStateEvent(const mtx::events::StateEvent &)
- {
- return true;
- }
- template
- static constexpr bool isStateEvent(const mtx::events::Event &)
- {
- return false;
- }
+ constexpr static bool isStateEvent_ =
+ std::is_same_v>,
+ mtx::events::StateEvent().content)>>;
static int compare_state_key(const MDB_val *a, const MDB_val *b)
{
@@ -416,11 +410,27 @@ private:
}
std::visit(
- [&txn, &statesdb, &stateskeydb, &eventsDb](auto e) {
- if constexpr (isStateEvent(e)) {
+ [&txn, &statesdb, &stateskeydb, &eventsDb, &membersdb](const auto &e) {
+ if constexpr (isStateEvent_) {
eventsDb.put(txn, e.event_id, json(e).dump());
- if (e.type != EventType::Unsupported) {
+ if (std::is_same_v<
+ std::remove_cv_t>,
+ StateEvent>) {
+ if (e.type == EventType::RoomMember)
+ membersdb.del(txn, e.state_key, "");
+ else if (e.state_key.empty())
+ statesdb.del(txn, to_string(e.type));
+ else
+ stateskeydb.del(
+ txn,
+ to_string(e.type),
+ json::object({
+ {"key", e.state_key},
+ {"id", e.event_id},
+ })
+ .dump());
+ } else if (e.type != EventType::Unsupported) {
if (e.state_key.empty())
statesdb.put(
txn, to_string(e.type), json(e).dump());
diff --git a/src/MxcImageProvider.cpp b/src/MxcImageProvider.cpp
index ab0f8152..b8648269 100644
--- a/src/MxcImageProvider.cpp
+++ b/src/MxcImageProvider.cpp
@@ -22,7 +22,14 @@ QHash infos;
QQuickImageResponse *
MxcImageProvider::requestImageResponse(const QString &id, const QSize &requestedSize)
{
- MxcImageResponse *response = new MxcImageResponse(id, requestedSize);
+ auto id_ = id;
+ bool crop = true;
+ if (id.endsWith("?scale")) {
+ crop = false;
+ id_.remove("?scale");
+ }
+
+ MxcImageResponse *response = new MxcImageResponse(id_, crop, requestedSize);
pool.start(response);
return response;
}
@@ -36,20 +43,24 @@ void
MxcImageResponse::run()
{
MxcImageProvider::download(
- m_id, m_requestedSize, [this](QString, QSize, QImage image, QString) {
+ m_id,
+ m_requestedSize,
+ [this](QString, QSize, QImage image, QString) {
if (image.isNull()) {
m_error = "Failed to download image.";
} else {
m_image = image;
}
emit finished();
- });
+ },
+ m_crop);
}
void
MxcImageProvider::download(const QString &id,
const QSize &requestedSize,
- std::function then)
+ std::function then,
+ bool crop)
{
std::optional encryptionInfo;
auto temp = infos.find("mxc://" + id);
@@ -58,11 +69,12 @@ MxcImageProvider::download(const QString &id,
if (requestedSize.isValid() && !encryptionInfo) {
QString fileName =
- QString("%1_%2x%3_crop")
+ QString("%1_%2x%3_%4")
.arg(QString::fromUtf8(id.toUtf8().toBase64(QByteArray::Base64UrlEncoding |
QByteArray::OmitTrailingEquals)))
.arg(requestedSize.width())
- .arg(requestedSize.height());
+ .arg(requestedSize.height())
+ .arg(crop ? "crop" : "scale");
QFileInfo fileInfo(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) +
"/media_cache",
fileName);
@@ -85,7 +97,7 @@ MxcImageProvider::download(const QString &id,
opts.mxc_url = "mxc://" + id.toStdString();
opts.width = requestedSize.width() > 0 ? requestedSize.width() : -1;
opts.height = requestedSize.height() > 0 ? requestedSize.height() : -1;
- opts.method = "crop";
+ opts.method = crop ? "crop" : "scale";
http::client()->get_thumbnail(
opts,
[fileInfo, requestedSize, then, id](const std::string &res,
diff --git a/src/MxcImageProvider.h b/src/MxcImageProvider.h
index 7b960836..61d82852 100644
--- a/src/MxcImageProvider.h
+++ b/src/MxcImageProvider.h
@@ -19,9 +19,10 @@ class MxcImageResponse
, public QRunnable
{
public:
- MxcImageResponse(const QString &id, const QSize &requestedSize)
+ MxcImageResponse(const QString &id, bool crop, const QSize &requestedSize)
: m_id(id)
, m_requestedSize(requestedSize)
+ , m_crop(crop)
{
setAutoDelete(false);
}
@@ -37,6 +38,7 @@ public:
QString m_id, m_error;
QSize m_requestedSize;
QImage m_image;
+ bool m_crop;
};
class MxcImageProvider
@@ -51,7 +53,8 @@ public slots:
static void addEncryptionInfo(mtx::crypto::EncryptedFile info);
static void download(const QString &id,
const QSize &requestedSize,
- std::function then);
+ std::function then,
+ bool crop = true);
private:
QThreadPool pool;
diff --git a/src/SingleImagePackModel.cpp b/src/SingleImagePackModel.cpp
index 6c508da0..d3cc8014 100644
--- a/src/SingleImagePackModel.cpp
+++ b/src/SingleImagePackModel.cpp
@@ -5,12 +5,18 @@
#include "SingleImagePackModel.h"
#include "Cache_p.h"
+#include "ChatPage.h"
#include "MatrixClient.h"
+#include "timeline/Permissions.h"
+#include "timeline/TimelineModel.h"
+
+#include "Logging.h"
SingleImagePackModel::SingleImagePackModel(ImagePackInfo pack_, QObject *parent)
: QAbstractListModel(parent)
, roomid_(std::move(pack_.source_room))
, statekey_(std::move(pack_.state_key))
+ , old_statekey_(statekey_)
, pack(std::move(pack_.pack))
{
if (!pack.pack)
@@ -61,6 +67,73 @@ SingleImagePackModel::data(const QModelIndex &index, int role) const
return {};
}
+bool
+SingleImagePackModel::setData(const QModelIndex &index, const QVariant &value, int role)
+{
+ using mtx::events::msc2545::PackUsage;
+
+ if (hasIndex(index.row(), index.column(), index.parent())) {
+ auto &img = pack.images.at(shortcodes.at(index.row()));
+ switch (role) {
+ case ShortCode: {
+ auto newCode = value.toString().toStdString();
+
+ // otherwise we delete this by accident
+ if (pack.images.count(newCode))
+ return false;
+
+ auto tmp = img;
+ auto oldCode = shortcodes.at(index.row());
+ pack.images.erase(oldCode);
+ shortcodes[index.row()] = newCode;
+ pack.images.insert({newCode, tmp});
+
+ emit dataChanged(
+ this->index(index.row()), this->index(index.row()), {Roles::ShortCode});
+ return true;
+ }
+ case Body:
+ img.body = value.toString().toStdString();
+ emit dataChanged(
+ this->index(index.row()), this->index(index.row()), {Roles::Body});
+ return true;
+ case IsEmote: {
+ bool isEmote = value.toBool();
+ bool isSticker =
+ img.overrides_usage() ? img.is_sticker() : pack.pack->is_sticker();
+
+ img.usage.set(PackUsage::Emoji, isEmote);
+ img.usage.set(PackUsage::Sticker, isSticker);
+
+ if (img.usage == pack.pack->usage)
+ img.usage.reset();
+
+ emit dataChanged(
+ this->index(index.row()), this->index(index.row()), {Roles::IsEmote});
+
+ return true;
+ }
+ case IsSticker: {
+ bool isEmote =
+ img.overrides_usage() ? img.is_emoji() : pack.pack->is_emoji();
+ bool isSticker = value.toBool();
+
+ img.usage.set(PackUsage::Emoji, isEmote);
+ img.usage.set(PackUsage::Sticker, isSticker);
+
+ if (img.usage == pack.pack->usage)
+ img.usage.reset();
+
+ emit dataChanged(
+ this->index(index.row()), this->index(index.row()), {Roles::IsSticker});
+
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
bool
SingleImagePackModel::isGloballyEnabled() const
{
@@ -98,3 +171,111 @@ SingleImagePackModel::setGloballyEnabled(bool enabled)
// emit this->globallyEnabledChanged();
});
}
+
+bool
+SingleImagePackModel::canEdit() const
+{
+ if (roomid_.empty())
+ return true;
+ else
+ return Permissions(QString::fromStdString(roomid_))
+ .canChange(qml_mtx_events::ImagePackInRoom);
+}
+
+void
+SingleImagePackModel::setPackname(QString val)
+{
+ auto val_ = val.toStdString();
+ if (val_ != this->pack.pack->display_name) {
+ this->pack.pack->display_name = val_;
+ emit packnameChanged();
+ }
+}
+
+void
+SingleImagePackModel::setAttribution(QString val)
+{
+ auto val_ = val.toStdString();
+ if (val_ != this->pack.pack->attribution) {
+ this->pack.pack->attribution = val_;
+ emit attributionChanged();
+ }
+}
+
+void
+SingleImagePackModel::setAvatarUrl(QString val)
+{
+ auto val_ = val.toStdString();
+ if (val_ != this->pack.pack->avatar_url) {
+ this->pack.pack->avatar_url = val_;
+ emit avatarUrlChanged();
+ }
+}
+
+void
+SingleImagePackModel::setStatekey(QString val)
+{
+ auto val_ = val.toStdString();
+ if (val_ != statekey_) {
+ statekey_ = val_;
+ emit statekeyChanged();
+ }
+}
+
+void
+SingleImagePackModel::setIsStickerPack(bool val)
+{
+ using mtx::events::msc2545::PackUsage;
+ if (val != pack.pack->is_sticker()) {
+ pack.pack->usage.set(PackUsage::Sticker, val);
+ emit isStickerPackChanged();
+ }
+}
+
+void
+SingleImagePackModel::setIsEmotePack(bool val)
+{
+ using mtx::events::msc2545::PackUsage;
+ if (val != pack.pack->is_emoji()) {
+ pack.pack->usage.set(PackUsage::Emoji, val);
+ emit isEmotePackChanged();
+ }
+}
+
+void
+SingleImagePackModel::save()
+{
+ if (roomid_.empty()) {
+ http::client()->put_account_data(pack, [this](mtx::http::RequestErr e) {
+ if (e)
+ ChatPage::instance()->showNotification(
+ tr("Failed to update image pack: {}")
+ .arg(QString::fromStdString(e->matrix_error.error)));
+ });
+ } else {
+ if (old_statekey_ != statekey_) {
+ http::client()->send_state_event(
+ roomid_,
+ to_string(mtx::events::EventType::ImagePackInRoom),
+ old_statekey_,
+ nlohmann::json::object(),
+ [this](const mtx::responses::EventId &, mtx::http::RequestErr e) {
+ if (e)
+ ChatPage::instance()->showNotification(
+ tr("Failed to delete old image pack: {}")
+ .arg(QString::fromStdString(e->matrix_error.error)));
+ });
+ }
+
+ http::client()->send_state_event(
+ roomid_,
+ statekey_,
+ pack,
+ [this](const mtx::responses::EventId &, mtx::http::RequestErr e) {
+ if (e)
+ ChatPage::instance()->showNotification(
+ tr("Failed to update image pack: {}")
+ .arg(QString::fromStdString(e->matrix_error.error)));
+ });
+ }
+}
diff --git a/src/SingleImagePackModel.h b/src/SingleImagePackModel.h
index e0c791ba..44f413c6 100644
--- a/src/SingleImagePackModel.h
+++ b/src/SingleImagePackModel.h
@@ -15,14 +15,18 @@ class SingleImagePackModel : public QAbstractListModel
Q_OBJECT
Q_PROPERTY(QString roomid READ roomid CONSTANT)
- Q_PROPERTY(QString statekey READ statekey CONSTANT)
- Q_PROPERTY(QString attribution READ statekey CONSTANT)
- Q_PROPERTY(QString packname READ packname CONSTANT)
- Q_PROPERTY(QString avatarUrl READ avatarUrl CONSTANT)
- Q_PROPERTY(bool isStickerPack READ isStickerPack CONSTANT)
- Q_PROPERTY(bool isEmotePack READ isEmotePack CONSTANT)
+ Q_PROPERTY(QString statekey READ statekey WRITE setStatekey NOTIFY statekeyChanged)
+ Q_PROPERTY(
+ QString attribution READ attribution WRITE setAttribution NOTIFY attributionChanged)
+ Q_PROPERTY(QString packname READ packname WRITE setPackname NOTIFY packnameChanged)
+ Q_PROPERTY(QString avatarUrl READ avatarUrl WRITE setAvatarUrl NOTIFY avatarUrlChanged)
+ Q_PROPERTY(
+ bool isStickerPack READ isStickerPack WRITE setIsStickerPack NOTIFY isStickerPackChanged)
+ Q_PROPERTY(bool isEmotePack READ isEmotePack WRITE setIsEmotePack NOTIFY isEmotePackChanged)
Q_PROPERTY(bool isGloballyEnabled READ isGloballyEnabled WRITE setGloballyEnabled NOTIFY
globallyEnabledChanged)
+ Q_PROPERTY(bool canEdit READ canEdit CONSTANT)
+
public:
enum Roles
{
@@ -32,11 +36,15 @@ public:
IsEmote,
IsSticker,
};
+ Q_ENUM(Roles);
SingleImagePackModel(ImagePackInfo pack_, QObject *parent = nullptr);
QHash roleNames() const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role) const override;
+ bool setData(const QModelIndex &index,
+ const QVariant &value,
+ int role = Qt::EditRole) override;
QString roomid() const { return QString::fromStdString(roomid_); }
QString statekey() const { return QString::fromStdString(statekey_); }
@@ -47,14 +55,30 @@ public:
bool isEmotePack() const { return pack.pack->is_emoji(); }
bool isGloballyEnabled() const;
+ bool canEdit() const;
void setGloballyEnabled(bool enabled);
+ void setPackname(QString val);
+ void setAttribution(QString val);
+ void setAvatarUrl(QString val);
+ void setStatekey(QString val);
+ void setIsStickerPack(bool val);
+ void setIsEmotePack(bool val);
+
+ Q_INVOKABLE void save();
+
signals:
void globallyEnabledChanged();
+ void statekeyChanged();
+ void attributionChanged();
+ void packnameChanged();
+ void avatarUrlChanged();
+ void isEmotePackChanged();
+ void isStickerPackChanged();
private:
std::string roomid_;
- std::string statekey_;
+ std::string statekey_, old_statekey_;
mtx::events::msc2545::ImagePack pack;
std::vector shortcodes;
diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp
index a8adf05b..10d9788d 100644
--- a/src/timeline/TimelineModel.cpp
+++ b/src/timeline/TimelineModel.cpp
@@ -308,6 +308,15 @@ qml_mtx_events::fromRoomEventType(qml_mtx_events::EventType t)
case qml_mtx_events::KeyVerificationDone:
case qml_mtx_events::KeyVerificationReady:
return mtx::events::EventType::RoomMessage;
+ //! m.image_pack, currently im.ponies.room_emotes
+ case qml_mtx_events::ImagePackInRoom:
+ return mtx::events::EventType::ImagePackRooms;
+ //! m.image_pack, currently im.ponies.user_emotes
+ case qml_mtx_events::ImagePackInAccountData:
+ return mtx::events::EventType::ImagePackInAccountData;
+ //! m.image_pack.rooms, currently im.ponies.emote_rooms
+ case qml_mtx_events::ImagePackRooms:
+ return mtx::events::EventType::ImagePackRooms;
default:
return mtx::events::EventType::Unsupported;
};
diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h
index f62c5360..b5c8ca37 100644
--- a/src/timeline/TimelineModel.h
+++ b/src/timeline/TimelineModel.h
@@ -107,7 +107,13 @@ enum EventType
KeyVerificationCancel,
KeyVerificationKey,
KeyVerificationDone,
- KeyVerificationReady
+ KeyVerificationReady,
+ //! m.image_pack, currently im.ponies.room_emotes
+ ImagePackInRoom,
+ //! m.image_pack, currently im.ponies.user_emotes
+ ImagePackInAccountData,
+ //! m.image_pack.rooms, currently im.ponies.emote_rooms
+ ImagePackRooms,
};
Q_ENUM_NS(EventType)
mtx::events::EventType fromRoomEventType(qml_mtx_events::EventType);