summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt10
-rw-r--r--io.github.NhekoReborn.Nheko.yaml2
-rw-r--r--nheko-nightly.flatpakref1
-rw-r--r--nheko-nightly.flatpakrepo1
-rw-r--r--resources/qml/MessageInput.qml2
-rw-r--r--resources/qml/RoomList.qml12
-rw-r--r--resources/qml/RoomSettings.qml13
-rw-r--r--resources/qml/Root.qml15
-rw-r--r--resources/qml/dialogs/ImagePackSettingsDialog.qml309
-rw-r--r--resources/res.qrc1
-rw-r--r--src/Cache.cpp33
-rw-r--r--src/CacheStructs.h5
-rw-r--r--src/Cache_p.h9
-rw-r--r--src/CombinedImagePackModel.cpp (renamed from src/ImagePackModel.cpp)17
-rw-r--r--src/CombinedImagePackModel.h (renamed from src/ImagePackModel.h)4
-rw-r--r--src/ImagePackListModel.cpp76
-rw-r--r--src/ImagePackListModel.h37
-rw-r--r--src/SingleImagePackModel.cpp100
-rw-r--r--src/SingleImagePackModel.h61
-rw-r--r--src/UserSettingsPage.cpp2
-rw-r--r--src/timeline/InputBar.cpp4
-rw-r--r--src/timeline/InputBar.h4
-rw-r--r--src/timeline/TimelineViewManager.cpp26
-rw-r--r--src/timeline/TimelineViewManager.h3
-rw-r--r--src/ui/RoomSettings.h2
25 files changed, 710 insertions, 39 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt

index f77d9978..7d2d5dc3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt
@@ -354,7 +354,9 @@ set(SRC_FILES src/Olm.cpp src/RegisterPage.cpp src/SSOHandler.cpp - src/ImagePackModel.cpp + src/CombinedImagePackModel.cpp + src/SingleImagePackModel.cpp + src/ImagePackListModel.cpp src/TrayIcon.cpp src/UserSettingsPage.cpp src/UsersModel.cpp @@ -380,7 +382,7 @@ if(USE_BUNDLED_MTXCLIENT) FetchContent_Declare( MatrixClient GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git - GIT_TAG 1c277e9ac69aafdaf6888ce595b21dc86e970f28 + GIT_TAG bc203a0e01abcff85ae1972f1ab9de3fabc3dba6 ) set(BUILD_LIB_EXAMPLES OFF CACHE INTERNAL "") set(BUILD_LIB_TESTS OFF CACHE INTERNAL "") @@ -558,7 +560,9 @@ qt5_wrap_cpp(MOC_HEADERS src/MxcImageProvider.h src/RegisterPage.h src/SSOHandler.h - src/ImagePackModel.h + src/CombinedImagePackModel.h + src/SingleImagePackModel.h + src/ImagePackListModel.h src/TrayIcon.h src/UserSettingsPage.h src/UsersModel.h diff --git a/io.github.NhekoReborn.Nheko.yaml b/io.github.NhekoReborn.Nheko.yaml
index b6f468db..a3637fd0 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: 1c277e9ac69aafdaf6888ce595b21dc86e970f28 + - commit: bc203a0e01abcff85ae1972f1ab9de3fabc3dba6 type: git url: https://github.com/Nheko-Reborn/mtxclient.git - config-opts: diff --git a/nheko-nightly.flatpakref b/nheko-nightly.flatpakref
index 7d27bdfe..74e47ecd 100644 --- a/nheko-nightly.flatpakref +++ b/nheko-nightly.flatpakref
@@ -3,6 +3,7 @@ Title=Nheko Nightly Name=io.github.NhekoReborn.Nheko Branch=master Url=https://flatpak.neko.dev/repo/nightly +SuggestRemoteName=nheko-nightlies Homepage=https://nheko-reborn.github.io/ Icon=https://nheko.im/nheko-reborn/nheko/-/raw/master/resources/nheko.svg RuntimeRepo=https://dl.flathub.org/repo/flathub.flatpakrepo diff --git a/nheko-nightly.flatpakrepo b/nheko-nightly.flatpakrepo
index 4fb1bc55..680558af 100644 --- a/nheko-nightly.flatpakrepo +++ b/nheko-nightly.flatpakrepo
@@ -1,6 +1,7 @@ [Flatpak Repo] Title=Nheko Nightly Url=https://flatpak.neko.dev/repo/nightly +SuggestRemoteName=nheko-nightlies Homepage=https://nheko.im/ Comment=Nheko nightly release repository Description=Nheko nightly release repository diff --git a/resources/qml/MessageInput.qml b/resources/qml/MessageInput.qml
index c135aff9..58d71a4e 100644 --- a/resources/qml/MessageInput.qml +++ b/resources/qml/MessageInput.qml
@@ -355,7 +355,7 @@ Rectangle { image: ":/icons/icons/ui/smile.png" ToolTip.visible: hovered ToolTip.text: qsTr("Emoji") - onClicked: emojiPopup.visible ? emojiPopup.close() : emojiPopup.show(function(emoji) { + onClicked: emojiPopup.visible ? emojiPopup.close() : emojiPopup.show(emojiButton, function(emoji) { messageInput.insert(messageInput.cursorPosition, emoji); TimelineManager.focusMessageInput(); }) diff --git a/resources/qml/RoomList.qml b/resources/qml/RoomList.qml
index 9dac5830..2be5fe92 100644 --- a/resources/qml/RoomList.qml +++ b/resources/qml/RoomList.qml
@@ -61,9 +61,19 @@ Page { } } + Platform.MessageDialog { + id: leaveRoomDialog + + title: qsTr("Leave Room") + text: qsTr("Are you sure you want to leave this room?") + modality: Qt.Modal + onAccepted: Rooms.leave(roomContextMenu.roomid) + buttons: Dialog.Ok | Dialog.Cancel + } + Platform.MenuItem { text: qsTr("Leave room") - onTriggered: Rooms.leave(roomContextMenu.roomid) + onTriggered: leaveRoomDialog.open() } Platform.MenuSeparator { diff --git a/resources/qml/RoomSettings.qml b/resources/qml/RoomSettings.qml
index b4936f3e..accb5637 100644 --- a/resources/qml/RoomSettings.qml +++ b/resources/qml/RoomSettings.qml
@@ -219,7 +219,7 @@ ApplicationWindow { title: qsTr("End-to-End Encryption") text: qsTr("Encryption is currently experimental and things might break unexpectedly. <br> Please take note that it can't be disabled afterwards.") - modality: Qt.NonModal + modality: Qt.Modal onAccepted: { if (roomSettings.isEncryptionEnabled) return ; @@ -249,6 +249,17 @@ ApplicationWindow { Layout.alignment: Qt.AlignRight } + MatrixText { + text: qsTr("Sticker & Emote Settings") + } + + Button { + text: qsTr("Change") + ToolTip.text: qsTr("Change what packs are enabled, remove packs or create new ones") + onClicked: TimelineManager.openImagePackSettings(roomSettings.roomId) + Layout.alignment: Qt.AlignRight + } + Item { // for adding extra space between sections Layout.fillWidth: true diff --git a/resources/qml/Root.qml b/resources/qml/Root.qml
index 8e226639..1793d9bc 100644 --- a/resources/qml/Root.qml +++ b/resources/qml/Root.qml
@@ -4,6 +4,7 @@ import "./delegates" import "./device-verification" +import "./dialogs" import "./emoji" import "./voip" import Qt.labs.platform 1.1 as Platform @@ -87,6 +88,14 @@ Page { } + Component { + id: packSettingsComponent + + ImagePackSettingsDialog { + } + + } + Shortcut { sequence: "Ctrl+K" onActivated: { @@ -120,6 +129,12 @@ Page { }); userProfile.show(); } + onShowImagePackSettings: { + var packSet = packSettingsComponent.createObject(timelineRoot, { + "packlist": packlist + }); + packSet.show(); + } } Connections { diff --git a/resources/qml/dialogs/ImagePackSettingsDialog.qml b/resources/qml/dialogs/ImagePackSettingsDialog.qml new file mode 100644
index 00000000..c4b4a885 --- /dev/null +++ b/resources/qml/dialogs/ImagePackSettingsDialog.qml
@@ -0,0 +1,309 @@ +// 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 { + id: win + + property ImagePackListModel packlist + property int avatarSize: Math.ceil(fontMetrics.lineSpacing * 2.3) + property SingleImagePackModel currentPack: packlist.packAt(currentPackIndex) + property int currentPackIndex: 0 + readonly property int stickerDim: 128 + readonly property int stickerDimPad: 128 + Nheko.paddingSmall + + title: qsTr("Image pack settings") + x: MainWindow.x + (MainWindow.width / 2) - (width / 2) + y: MainWindow.y + (MainWindow.height / 2) - (height / 2) + height: 400 + width: 600 + palette: Nheko.colors + color: Nheko.colors.base + modality: Qt.NonModal + flags: Qt.Dialog + + AdaptiveLayout { + id: adaptiveView + + anchors.fill: parent + singlePageMode: false + pageIndex: 0 + + AdaptiveLayoutElement { + id: packlistC + + visible: Settings.groupView + minimumWidth: 200 + collapsedWidth: 200 + preferredWidth: 300 + maximumWidth: 300 + + ListView { + model: packlist + clip: true + + ScrollHelper { + flickable: parent + anchors.fill: parent + enabled: !Settings.mobileMode + } + + delegate: Rectangle { + 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 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 + } + + } + ] + + 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 + } + + } + + } + + } + + } + + } + + } + + AdaptiveLayoutElement { + id: packinfoC + + Rectangle { + color: Nheko.colors.window + + ColumnLayout { + id: packinfo + + property string packName: currentPack ? currentPack.packname : "" + property string avatarUrl: currentPack ? currentPack.avatarUrl : "" + + anchors.fill: parent + anchors.margins: Nheko.paddingLarge + spacing: Nheko.paddingLarge + + Avatar { + url: packinfo.avatarUrl.replace("mxc://", "image://MxcImage/") + displayName: packinfo.packName + height: 100 + width: 100 + Layout.alignment: Qt.AlignHCenter + enabled: false + } + + MatrixText { + text: packinfo.packName + font.pixelSize: 24 + Layout.alignment: Qt.AlignHCenter + } + + GridLayout { + Layout.alignment: Qt.AlignHCenter + visible: currentPack && currentPack.roomid != "" + columns: 2 + rowSpacing: Nheko.paddingMedium + + MatrixText { + text: qsTr("Enable globally") + } + + ToggleButton { + ToolTip.text: qsTr("Enables this pack to be used in all rooms") + checked: currentPack ? currentPack.isGloballyEnabled : false + onClicked: currentPack.isGloballyEnabled = !currentPack.isGloballyEnabled + Layout.alignment: Qt.AlignRight + } + + } + + GridView { + Layout.fillHeight: true + Layout.fillWidth: true + model: currentPack + cellWidth: stickerDimPad + cellHeight: stickerDimPad + boundsBehavior: Flickable.StopAtBounds + clip: true + currentIndex: -1 // prevent sorting from stealing focus + cacheBuffer: 500 + + ScrollHelper { + flickable: parent + anchors.fill: parent + enabled: !Settings.mobileMode + } + + // Individual emoji + delegate: AbstractButton { + width: stickerDim + height: stickerDim + hoverEnabled: true + ToolTip.text: ":" + model.shortcode + ": - " + model.body + ToolTip.visible: hovered + + contentItem: Image { + height: stickerDim + width: stickerDim + source: model.url.replace("mxc://", "image://MxcImage/") + fillMode: Image.PreserveAspectFit + } + + background: Rectangle { + anchors.fill: parent + color: hovered ? Nheko.colors.highlight : 'transparent' + radius: 5 + } + + } + + } + + } + + } + + } + + } + + footer: DialogButtonBox { + id: buttons + + Button { + text: qsTr("Close") + DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole + onClicked: win.close() + } + + } + +} diff --git a/resources/res.qrc b/resources/res.qrc
index f8c040e4..5d37c397 100644 --- a/resources/res.qrc +++ b/resources/res.qrc
@@ -160,6 +160,7 @@ <file>qml/device-verification/Failed.qml</file> <file>qml/device-verification/Success.qml</file> <file>qml/dialogs/InputDialog.qml</file> + <file>qml/dialogs/ImagePackSettingsDialog.qml</file> <file>qml/ui/Ripple.qml</file> <file>qml/ui/Spinner.qml</file> <file>qml/ui/animations/BlinkAnimation.qml</file> diff --git a/src/Cache.cpp b/src/Cache.cpp
index 0bcf9fbf..d651b182 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp
@@ -3383,26 +3383,30 @@ Cache::getChildRoomIds(const std::string &room_id) } std::vector<ImagePackInfo> -Cache::getImagePacks(const std::string &room_id, bool stickers) +Cache::getImagePacks(const std::string &room_id, std::optional<bool> stickers) { auto txn = ro_txn(env_); std::vector<ImagePackInfo> infos; - auto addPack = [&infos, stickers](const mtx::events::msc2545::ImagePack &pack) { - if (!pack.pack || (stickers ? pack.pack->is_sticker() : pack.pack->is_emoji())) { + auto addPack = [&infos, stickers](const mtx::events::msc2545::ImagePack &pack, + const std::string &source_room, + const std::string &state_key) { + if (!pack.pack || !stickers.has_value() || + (stickers.value() ? pack.pack->is_sticker() : pack.pack->is_emoji())) { ImagePackInfo info; - if (pack.pack) - info.packname = pack.pack->display_name; + info.source_room = source_room; + info.state_key = state_key; + info.pack.pack = pack.pack; for (const auto &img : pack.images) { if (img.second.overrides_usage() && (stickers ? !img.second.is_sticker() : !img.second.is_emoji())) continue; - info.images.insert(img); + info.pack.images.insert(img); } - if (!info.images.empty()) + if (!info.pack.images.empty()) infos.push_back(std::move(info)); } }; @@ -3414,7 +3418,7 @@ Cache::getImagePacks(const std::string &room_id, bool stickers) std::get_if<mtx::events::EphemeralEvent<mtx::events::msc2545::ImagePack>>( &*accountpack); if (tmp) - addPack(tmp->content); + addPack(tmp->content, "", ""); } // packs from rooms, that were enabled globally @@ -3433,7 +3437,7 @@ Cache::getImagePacks(const std::string &room_id, bool stickers) if (auto pack = getStateEvent<mtx::events::msc2545::ImagePack>( txn, room_id2, state_id)) - addPack(pack->content); + addPack(pack->content, room_id2, state_id); } } } @@ -3441,17 +3445,24 @@ Cache::getImagePacks(const std::string &room_id, bool stickers) // packs from current room if (auto pack = getStateEvent<mtx::events::msc2545::ImagePack>(txn, room_id)) { - addPack(pack->content); + addPack(pack->content, room_id, ""); } for (const auto &pack : getStateEventsWithType<mtx::events::msc2545::ImagePack>(txn, room_id)) { - addPack(pack.content); + addPack(pack.content, room_id, pack.state_key); } return infos; } std::optional<mtx::events::collections::RoomAccountDataEvents> +Cache::getAccountData(mtx::events::EventType type, const std::string &room_id) +{ + auto txn = ro_txn(env_); + return getAccountData(txn, type, room_id); +} + +std::optional<mtx::events::collections::RoomAccountDataEvents> Cache::getAccountData(lmdb::txn &txn, mtx::events::EventType type, const std::string &room_id) { try { diff --git a/src/CacheStructs.h b/src/CacheStructs.h
index f274d70f..4a5c5c76 100644 --- a/src/CacheStructs.h +++ b/src/CacheStructs.h
@@ -113,6 +113,7 @@ struct RoomSearchResult struct ImagePackInfo { - std::string packname; - std::map<std::string, mtx::events::msc2545::PackImage> images; + mtx::events::msc2545::ImagePack pack; + std::string source_room; + std::string state_key; }; diff --git a/src/Cache_p.h b/src/Cache_p.h
index 13fbc371..c9d42202 100644 --- a/src/Cache_p.h +++ b/src/Cache_p.h
@@ -97,6 +97,12 @@ public: return getStateEvent<T>(txn, room_id, state_key); } + //! retrieve a specific event from account data + //! pass empty room_id for global account data + std::optional<mtx::events::collections::RoomAccountDataEvents> getAccountData( + mtx::events::EventType type, + const std::string &room_id = ""); + //! Retrieve member info from a room. std::vector<RoomMember> getMembers(const std::string &room_id, std::size_t startIndex = 0, @@ -225,7 +231,8 @@ public: std::vector<std::string> getParentRoomIds(const std::string &room_id); std::vector<std::string> getChildRoomIds(const std::string &room_id); - std::vector<ImagePackInfo> getImagePacks(const std::string &room_id, bool stickers); + std::vector<ImagePackInfo> getImagePacks(const std::string &room_id, + std::optional<bool> stickers); //! Mark a room that uses e2e encryption. void setEncryptedRoom(lmdb::txn &txn, const std::string &room_id); diff --git a/src/ImagePackModel.cpp b/src/CombinedImagePackModel.cpp
index 9b0dca8d..341a34ec 100644 --- a/src/ImagePackModel.cpp +++ b/src/CombinedImagePackModel.cpp
@@ -2,21 +2,24 @@ // // SPDX-License-Identifier: GPL-3.0-or-later -#include "ImagePackModel.h" +#include "CombinedImagePackModel.h" #include "Cache_p.h" #include "CompletionModelRoles.h" -ImagePackModel::ImagePackModel(const std::string &roomId, bool stickers, QObject *parent) +CombinedImagePackModel::CombinedImagePackModel(const std::string &roomId, + bool stickers, + QObject *parent) : QAbstractListModel(parent) , room_id(roomId) { auto packs = cache::client()->getImagePacks(room_id, stickers); for (const auto &pack : packs) { - QString packname = QString::fromStdString(pack.packname); + QString packname = + pack.pack.pack ? QString::fromStdString(pack.pack.pack->display_name) : ""; - for (const auto &img : pack.images) { + for (const auto &img : pack.pack.images) { ImageDesc i{}; i.shortcode = QString::fromStdString(img.first); i.packname = packname; @@ -27,13 +30,13 @@ ImagePackModel::ImagePackModel(const std::string &roomId, bool stickers, QObject } int -ImagePackModel::rowCount(const QModelIndex &) const +CombinedImagePackModel::rowCount(const QModelIndex &) const { return (int)images.size(); } QHash<int, QByteArray> -ImagePackModel::roleNames() const +CombinedImagePackModel::roleNames() const { return { {CompletionModel::CompletionRole, "completionRole"}, @@ -48,7 +51,7 @@ ImagePackModel::roleNames() const } QVariant -ImagePackModel::data(const QModelIndex &index, int role) const +CombinedImagePackModel::data(const QModelIndex &index, int role) const { if (hasIndex(index.row(), index.column(), index.parent())) { switch (role) { diff --git a/src/ImagePackModel.h b/src/CombinedImagePackModel.h
index 937014ec..f0f69799 100644 --- a/src/ImagePackModel.h +++ b/src/CombinedImagePackModel.h
@@ -8,7 +8,7 @@ #include <mtx/events/mscs/image_packs.hpp> -class ImagePackModel : public QAbstractListModel +class CombinedImagePackModel : public QAbstractListModel { Q_OBJECT public: @@ -21,7 +21,7 @@ public: OriginalRow, }; - ImagePackModel(const std::string &roomId, bool stickers, QObject *parent = nullptr); + CombinedImagePackModel(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; diff --git a/src/ImagePackListModel.cpp b/src/ImagePackListModel.cpp new file mode 100644
index 00000000..89f1f68e --- /dev/null +++ b/src/ImagePackListModel.cpp
@@ -0,0 +1,76 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "ImagePackListModel.h" + +#include <QQmlEngine> + +#include "Cache_p.h" +#include "SingleImagePackModel.h" + +ImagePackListModel::ImagePackListModel(const std::string &roomId, QObject *parent) + : QAbstractListModel(parent) + , room_id(roomId) +{ + auto packs_ = cache::client()->getImagePacks(room_id, std::nullopt); + + for (const auto &pack : packs_) { + packs.push_back( + QSharedPointer<SingleImagePackModel>(new SingleImagePackModel(pack))); + } +} + +int +ImagePackListModel::rowCount(const QModelIndex &) const +{ + return (int)packs.size(); +} + +QHash<int, QByteArray> +ImagePackListModel::roleNames() const +{ + return { + {Roles::DisplayName, "displayName"}, + {Roles::AvatarUrl, "avatarUrl"}, + {Roles::FromAccountData, "fromAccountData"}, + {Roles::FromCurrentRoom, "fromCurrentRoom"}, + {Roles::StateKey, "statekey"}, + {Roles::RoomId, "roomid"}, + }; +} + +QVariant +ImagePackListModel::data(const QModelIndex &index, int role) const +{ + if (hasIndex(index.row(), index.column(), index.parent())) { + const auto &pack = packs.at(index.row()); + switch (role) { + case Roles::DisplayName: + return pack->packname(); + case Roles::AvatarUrl: + return pack->avatarUrl(); + case Roles::FromAccountData: + return pack->roomid().isEmpty(); + case Roles::FromCurrentRoom: + return pack->roomid().toStdString() == this->room_id; + case Roles::StateKey: + return pack->statekey(); + case Roles::RoomId: + return pack->roomid(); + default: + return {}; + } + } + return {}; +} + +SingleImagePackModel * +ImagePackListModel::packAt(int row) +{ + if (row < 0 || static_cast<size_t>(row) >= packs.size()) + return {}; + auto e = packs.at(row).get(); + QQmlEngine::setObjectOwnership(e, QQmlEngine::CppOwnership); + return e; +} diff --git a/src/ImagePackListModel.h b/src/ImagePackListModel.h new file mode 100644
index 00000000..0a044690 --- /dev/null +++ b/src/ImagePackListModel.h
@@ -0,0 +1,37 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include <QAbstractListModel> +#include <QQmlEngine> +#include <QSharedPointer> + +class SingleImagePackModel; +class ImagePackListModel : public QAbstractListModel +{ + Q_OBJECT +public: + enum Roles + { + DisplayName = Qt::UserRole, + AvatarUrl, + FromAccountData, + FromCurrentRoom, + StateKey, + RoomId, + }; + + ImagePackListModel(const std::string &roomId, 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; + + Q_INVOKABLE SingleImagePackModel *packAt(int row); + +private: + std::string room_id; + + std::vector<QSharedPointer<SingleImagePackModel>> packs; +}; diff --git a/src/SingleImagePackModel.cpp b/src/SingleImagePackModel.cpp new file mode 100644
index 00000000..ba873c19 --- /dev/null +++ b/src/SingleImagePackModel.cpp
@@ -0,0 +1,100 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "SingleImagePackModel.h" + +#include "Cache_p.h" +#include "MatrixClient.h" + +SingleImagePackModel::SingleImagePackModel(ImagePackInfo pack_, QObject *parent) + : QAbstractListModel(parent) + , roomid_(std::move(pack_.source_room)) + , statekey_(std::move(pack_.state_key)) + , pack(std::move(pack_.pack)) +{ + if (!pack.pack) + pack.pack = mtx::events::msc2545::ImagePack::PackDescription{}; + + for (const auto &e : pack.images) + shortcodes.push_back(e.first); +} + +int +SingleImagePackModel::rowCount(const QModelIndex &) const +{ + return (int)shortcodes.size(); +} + +QHash<int, QByteArray> +SingleImagePackModel::roleNames() const +{ + return { + {Roles::Url, "url"}, + {Roles::ShortCode, "shortCode"}, + {Roles::Body, "body"}, + {Roles::IsEmote, "isEmote"}, + {Roles::IsSticker, "isSticker"}, + }; +} + +QVariant +SingleImagePackModel::data(const QModelIndex &index, int role) const +{ + if (hasIndex(index.row(), index.column(), index.parent())) { + const auto &img = pack.images.at(shortcodes.at(index.row())); + switch (role) { + case Url: + return QString::fromStdString(img.url); + case ShortCode: + return QString::fromStdString(shortcodes.at(index.row())); + case Body: + return QString::fromStdString(img.body); + case IsEmote: + return img.overrides_usage() ? img.is_emoji() : pack.pack->is_emoji(); + case IsSticker: + return img.overrides_usage() ? img.is_sticker() : pack.pack->is_sticker(); + default: + return {}; + } + } + return {}; +} + +bool +SingleImagePackModel::isGloballyEnabled() const +{ + if (auto roomPacks = + cache::client()->getAccountData(mtx::events::EventType::ImagePackRooms)) { + if (auto tmp = std::get_if< + mtx::events::EphemeralEvent<mtx::events::msc2545::ImagePackRooms>>( + &*roomPacks)) { + if (tmp->content.rooms.count(roomid_) && + tmp->content.rooms.at(roomid_).count(statekey_)) + return true; + } + } + return false; +} +void +SingleImagePackModel::setGloballyEnabled(bool enabled) +{ + mtx::events::msc2545::ImagePackRooms content{}; + if (auto roomPacks = + cache::client()->getAccountData(mtx::events::EventType::ImagePackRooms)) { + if (auto tmp = std::get_if< + mtx::events::EphemeralEvent<mtx::events::msc2545::ImagePackRooms>>( + &*roomPacks)) { + content = tmp->content; + } + } + + if (enabled) + content.rooms[roomid_][statekey_] = {}; + else + content.rooms[roomid_].erase(statekey_); + + http::client()->put_account_data(content, [this](mtx::http::RequestErr) { + // emit this->globallyEnabledChanged(); + }); +} diff --git a/src/SingleImagePackModel.h b/src/SingleImagePackModel.h new file mode 100644
index 00000000..e0c791ba --- /dev/null +++ b/src/SingleImagePackModel.h
@@ -0,0 +1,61 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include <QAbstractListModel> + +#include <mtx/events/mscs/image_packs.hpp> + +#include "CacheStructs.h" + +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(bool isGloballyEnabled READ isGloballyEnabled WRITE setGloballyEnabled NOTIFY + globallyEnabledChanged) +public: + enum Roles + { + Url = Qt::UserRole, + ShortCode, + Body, + IsEmote, + IsSticker, + }; + + SingleImagePackModel(ImagePackInfo pack_, 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; + + QString roomid() const { return QString::fromStdString(roomid_); } + QString statekey() const { return QString::fromStdString(statekey_); } + QString packname() const { return QString::fromStdString(pack.pack->display_name); } + QString attribution() const { return QString::fromStdString(pack.pack->attribution); } + QString avatarUrl() const { return QString::fromStdString(pack.pack->avatar_url); } + bool isStickerPack() const { return pack.pack->is_sticker(); } + bool isEmotePack() const { return pack.pack->is_emoji(); } + + bool isGloballyEnabled() const; + void setGloballyEnabled(bool enabled); + +signals: + void globallyEnabledChanged(); + +private: + std::string roomid_; + std::string statekey_; + + mtx::events::msc2545::ImagePack pack; + std::vector<std::string> shortcodes; +}; diff --git a/src/UserSettingsPage.cpp b/src/UserSettingsPage.cpp
index ffaebe61..a062780a 100644 --- a/src/UserSettingsPage.cpp +++ b/src/UserSettingsPage.cpp
@@ -1399,7 +1399,7 @@ UserSettingsPage::exportSessionKeys() QString suffix("-----END MEGOLM SESSION DATA-----"); QString newline("\n"); QTextStream out(&file); - out << prefix << newline << b64 << newline << suffix; + out << prefix << newline << b64 << newline << suffix << newline; file.close(); } catch (const std::exception &e) { QMessageBox::warning(this, tr("Error"), e.what()); diff --git a/src/timeline/InputBar.cpp b/src/timeline/InputBar.cpp
index 56d0d1ce..f17081e5 100644 --- a/src/timeline/InputBar.cpp +++ b/src/timeline/InputBar.cpp
@@ -19,9 +19,9 @@ #include "Cache.h" #include "ChatPage.h" +#include "CombinedImagePackModel.h" #include "CompletionProxyModel.h" #include "Config.h" -#include "ImagePackModel.h" #include "Logging.h" #include "MainWindow.h" #include "MatrixClient.h" @@ -503,7 +503,7 @@ InputBar::video(const QString &filename, } void -InputBar::sticker(ImagePackModel *model, int row) +InputBar::sticker(CombinedImagePackModel *model, int row) { if (!model || row < 0) return; diff --git a/src/timeline/InputBar.h b/src/timeline/InputBar.h
index acedceb7..2e6fb5c0 100644 --- a/src/timeline/InputBar.h +++ b/src/timeline/InputBar.h
@@ -12,7 +12,7 @@ #include <mtx/responses/messages.hpp> class TimelineModel; -class ImagePackModel; +class CombinedImagePackModel; class QMimeData; class QDropEvent; class QStringList; @@ -58,7 +58,7 @@ public slots: MarkdownOverride useMarkdown = MarkdownOverride::NOT_SPECIFIED, bool rainbowify = false); void reaction(const QString &reactedEvent, const QString &reactionKey); - void sticker(ImagePackModel *model, int row); + void sticker(CombinedImagePackModel *model, int row); private slots: void startTyping(); diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp
index c08cfd53..a6922be7 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp
@@ -15,17 +15,19 @@ #include "ChatPage.h" #include "Clipboard.h" #include "ColorImageProvider.h" +#include "CombinedImagePackModel.h" #include "CompletionProxyModel.h" #include "DelegateChooser.h" #include "DeviceVerificationFlow.h" #include "EventAccessors.h" -#include "ImagePackModel.h" +#include "ImagePackListModel.h" #include "InviteesModel.h" #include "Logging.h" #include "MainWindow.h" #include "MatrixClient.h" #include "MxcImageProvider.h" #include "RoomsModel.h" +#include "SingleImagePackModel.h" #include "UserSettingsPage.h" #include "UsersModel.h" #include "dialogs/ImageOverlay.h" @@ -146,7 +148,7 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par qRegisterMetaType<mtx::events::msg::KeyVerificationReady>(); qRegisterMetaType<mtx::events::msg::KeyVerificationRequest>(); qRegisterMetaType<mtx::events::msg::KeyVerificationStart>(); - qRegisterMetaType<ImagePackModel *>(); + qRegisterMetaType<CombinedImagePackModel *>(); qmlRegisterUncreatableMetaObject(qml_mtx_events::staticMetaObject, "im.nheko", @@ -185,6 +187,18 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par "Room Settings needs to be instantiated on the C++ side"); qmlRegisterUncreatableType<TimelineModel>( "im.nheko", 1, 0, "Room", "Room needs to be instantiated on the C++ side"); + qmlRegisterUncreatableType<ImagePackListModel>( + "im.nheko", + 1, + 0, + "ImagePackListModel", + "ImagePackListModel needs to be instantiated on the C++ side"); + qmlRegisterUncreatableType<SingleImagePackModel>( + "im.nheko", + 1, + 0, + "SingleImagePackModel", + "SingleImagePackModel needs to be instantiated on the C++ side"); qmlRegisterUncreatableType<InviteesModel>( "im.nheko", 1, @@ -444,6 +458,12 @@ TimelineViewManager::openImageOverlay(QString mxcUrl, QString eventId) } void +TimelineViewManager::openImagePackSettings(QString roomid) +{ + emit showImagePackSettings(new ImagePackListModel(roomid.toStdString(), this)); +} + +void TimelineViewManager::openImageOverlayInternal(QString eventId, QImage img) { auto pixmap = QPixmap::fromImage(img); @@ -629,7 +649,7 @@ TimelineViewManager::completerFor(QString completerName, QString roomId) roomModel->setParent(proxy); return proxy; } else if (completerName == "stickers") { - auto stickerModel = new ImagePackModel(roomId.toStdString(), true); + auto stickerModel = new CombinedImagePackModel(roomId.toStdString(), true); auto proxy = new CompletionProxyModel(stickerModel, 1, static_cast<size_t>(-1) / 4); stickerModel->setParent(proxy); return proxy; diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h
index bfc116b1..54e3a935 100644 --- a/src/timeline/TimelineViewManager.h +++ b/src/timeline/TimelineViewManager.h
@@ -33,6 +33,7 @@ class ColorImageProvider; class UserSettings; class ChatPage; class DeviceVerificationFlow; +class ImagePackListModel; class TimelineViewManager : public QObject { @@ -57,6 +58,7 @@ public: Q_INVOKABLE bool isInitialSync() const { return isInitialSync_; } bool isWindowFocused() const { return isWindowFocused_; } Q_INVOKABLE void openImageOverlay(QString mxcUrl, QString eventId); + Q_INVOKABLE void openImagePackSettings(QString roomid); Q_INVOKABLE QColor userColor(QString id, QColor background); Q_INVOKABLE QString escapeEmoji(QString str) const; Q_INVOKABLE QString htmlEscape(QString str) const { return str.toHtmlEscaped(); } @@ -94,6 +96,7 @@ signals: void openRoomSettingsDialog(RoomSettings *settings); void openInviteUsersDialog(InviteesModel *invitees); void openProfile(UserProfile *profile); + void showImagePackSettings(ImagePackListModel *packlist); public slots: void updateReadReceipts(const QString &room_id, const std::vector<QString> &event_ids); diff --git a/src/ui/RoomSettings.h b/src/ui/RoomSettings.h
index 367f3111..cf36f795 100644 --- a/src/ui/RoomSettings.h +++ b/src/ui/RoomSettings.h
@@ -136,4 +136,4 @@ private: RoomInfo info_; int notifications_ = 0; int accessRules_ = 0; -}; \ No newline at end of file +};