mirror of
https://github.com/Nheko-Reborn/nheko.git
synced 2024-11-22 11:00:48 +03:00
Basic sticker pack editor
This commit is contained in:
parent
6d83b7c675
commit
a57a15a2e0
17 changed files with 751 additions and 167 deletions
|
@ -381,7 +381,7 @@ if(USE_BUNDLED_MTXCLIENT)
|
||||||
FetchContent_Declare(
|
FetchContent_Declare(
|
||||||
MatrixClient
|
MatrixClient
|
||||||
GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git
|
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_EXAMPLES OFF CACHE INTERNAL "")
|
||||||
set(BUILD_LIB_TESTS OFF CACHE INTERNAL "")
|
set(BUILD_LIB_TESTS OFF CACHE INTERNAL "")
|
||||||
|
|
|
@ -161,7 +161,7 @@ modules:
|
||||||
buildsystem: cmake-ninja
|
buildsystem: cmake-ninja
|
||||||
name: mtxclient
|
name: mtxclient
|
||||||
sources:
|
sources:
|
||||||
- commit: 316a4040785ee2eabac7ef5ce7b4acb71c48f6eb
|
- commit: e5688a2c5987a614b5055595f991f18568127bd2
|
||||||
type: git
|
type: git
|
||||||
url: https://github.com/Nheko-Reborn/mtxclient.git
|
url: https://github.com/Nheko-Reborn/mtxclient.git
|
||||||
- config-opts:
|
- config-opts:
|
||||||
|
|
|
@ -11,10 +11,11 @@ import im.nheko 1.0
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: avatar
|
id: avatar
|
||||||
|
|
||||||
property alias url: img.source
|
property string url
|
||||||
property string userid
|
property string userid
|
||||||
property string displayName
|
property string displayName
|
||||||
property alias textColor: label.color
|
property alias textColor: label.color
|
||||||
|
property bool crop: true
|
||||||
|
|
||||||
signal clicked(var mouse)
|
signal clicked(var mouse)
|
||||||
|
|
||||||
|
@ -44,12 +45,13 @@ Rectangle {
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
asynchronous: true
|
asynchronous: true
|
||||||
fillMode: Image.PreserveAspectCrop
|
fillMode: avatar.crop ? Image.PreserveAspectCrop : Image.PreserveAspectFit
|
||||||
mipmap: true
|
mipmap: true
|
||||||
smooth: true
|
smooth: true
|
||||||
sourceSize.width: avatar.width
|
sourceSize.width: avatar.width
|
||||||
sourceSize.height: avatar.height
|
sourceSize.height: avatar.height
|
||||||
layer.enabled: true
|
layer.enabled: true
|
||||||
|
source: avatar.url + ((avatar.crop || !avatar.url) ? "" : "?scale")
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
id: mouseArea
|
id: mouseArea
|
||||||
|
|
|
@ -154,7 +154,7 @@ ApplicationWindow {
|
||||||
|
|
||||||
GridLayout {
|
GridLayout {
|
||||||
columns: 2
|
columns: 2
|
||||||
rowSpacing: 10
|
rowSpacing: Nheko.paddingLarge
|
||||||
|
|
||||||
MatrixText {
|
MatrixText {
|
||||||
text: qsTr("SETTINGS")
|
text: qsTr("SETTINGS")
|
||||||
|
@ -180,7 +180,7 @@ ApplicationWindow {
|
||||||
}
|
}
|
||||||
|
|
||||||
MatrixText {
|
MatrixText {
|
||||||
text: "Room access"
|
text: qsTr("Room access")
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,10 @@ MouseArea {
|
||||||
property alias enabled: root.enabled
|
property alias enabled: root.enabled
|
||||||
|
|
||||||
function calculateNewPosition(flickableItem, wheel) {
|
function calculateNewPosition(flickableItem, wheel) {
|
||||||
|
// breaks ListView's with headers...
|
||||||
|
//if (typeof (flickableItem.headerItem) !== "undefined" && flickableItem.headerItem)
|
||||||
|
// minYExtent += flickableItem.headerItem.height;
|
||||||
|
|
||||||
//Nothing to scroll
|
//Nothing to scroll
|
||||||
if (flickableItem.contentHeight < flickableItem.height)
|
if (flickableItem.contentHeight < flickableItem.height)
|
||||||
return flickableItem.contentY;
|
return flickableItem.contentY;
|
||||||
|
@ -55,9 +59,6 @@ MouseArea {
|
||||||
|
|
||||||
var minYExtent = flickableItem.originY + flickableItem.topMargin;
|
var minYExtent = flickableItem.originY + flickableItem.topMargin;
|
||||||
var maxYExtent = (flickableItem.contentHeight + flickableItem.bottomMargin + flickableItem.originY) - flickableItem.height;
|
var maxYExtent = (flickableItem.contentHeight + flickableItem.bottomMargin + flickableItem.originY) - flickableItem.height;
|
||||||
if (typeof (flickableItem.headerItem) !== "undefined" && flickableItem.headerItem)
|
|
||||||
minYExtent += flickableItem.headerItem.height;
|
|
||||||
|
|
||||||
//Avoid overscrolling
|
//Avoid overscrolling
|
||||||
return Math.max(minYExtent, Math.min(maxYExtent, flickableItem.contentY - pixelDelta));
|
return Math.max(minYExtent, Math.min(maxYExtent, flickableItem.contentY - pixelDelta));
|
||||||
}
|
}
|
||||||
|
|
133
resources/qml/components/AvatarListTile.qml
Normal file
133
resources/qml/components/AvatarListTile.qml
Normal file
|
@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
283
resources/qml/dialogs/ImagePackEditorDialog.qml
Normal file
283
resources/qml/dialogs/ImagePackEditorDialog.qml
Normal file
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -20,14 +20,22 @@ ApplicationWindow {
|
||||||
readonly property int stickerDimPad: 128 + Nheko.paddingSmall
|
readonly property int stickerDimPad: 128 + Nheko.paddingSmall
|
||||||
|
|
||||||
title: qsTr("Image pack settings")
|
title: qsTr("Image pack settings")
|
||||||
height: 400
|
height: 600
|
||||||
width: 600
|
width: 800
|
||||||
palette: Nheko.colors
|
palette: Nheko.colors
|
||||||
color: Nheko.colors.base
|
color: Nheko.colors.base
|
||||||
modality: Qt.NonModal
|
modality: Qt.NonModal
|
||||||
flags: Qt.Dialog | Qt.WindowCloseButtonHint
|
flags: Qt.Dialog | Qt.WindowCloseButtonHint
|
||||||
Component.onCompleted: Nheko.reparent(win)
|
Component.onCompleted: Nheko.reparent(win)
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: packEditor
|
||||||
|
|
||||||
|
ImagePackEditorDialog {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
AdaptiveLayout {
|
AdaptiveLayout {
|
||||||
id: adaptiveView
|
id: adaptiveView
|
||||||
|
|
||||||
|
@ -54,7 +62,7 @@ ApplicationWindow {
|
||||||
enabled: !Settings.mobileMode
|
enabled: !Settings.mobileMode
|
||||||
}
|
}
|
||||||
|
|
||||||
delegate: Rectangle {
|
delegate: AvatarListTile {
|
||||||
id: packItem
|
id: packItem
|
||||||
|
|
||||||
property color background: Nheko.colors.window
|
property color background: Nheko.colors.window
|
||||||
|
@ -63,131 +71,24 @@ ApplicationWindow {
|
||||||
property color bubbleBackground: Nheko.colors.highlight
|
property color bubbleBackground: Nheko.colors.highlight
|
||||||
property color bubbleText: Nheko.colors.highlightedText
|
property color bubbleText: Nheko.colors.highlightedText
|
||||||
required property string displayName
|
required property string displayName
|
||||||
required property string avatarUrl
|
|
||||||
required property bool fromAccountData
|
required property bool fromAccountData
|
||||||
required property bool fromCurrentRoom
|
required property bool fromCurrentRoom
|
||||||
required property int index
|
|
||||||
|
|
||||||
color: background
|
title: displayName
|
||||||
height: avatarSize + 2 * Nheko.paddingMedium
|
subtitle: {
|
||||||
width: ListView.view.width
|
if (fromAccountData)
|
||||||
state: "normal"
|
return qsTr("Private pack");
|
||||||
states: [
|
else if (fromCurrentRoom)
|
||||||
State {
|
return qsTr("Pack from this room");
|
||||||
name: "highlight"
|
else
|
||||||
when: hovered.hovered && !(index == currentPackIndex)
|
return qsTr("Globally enabled pack");
|
||||||
|
}
|
||||||
PropertyChanges {
|
selectedIndex: currentPackIndex
|
||||||
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 {
|
TapHandler {
|
||||||
margin: -Nheko.paddingSmall
|
|
||||||
onSingleTapped: currentPackIndex = index
|
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
|
color: Nheko.colors.window
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
//Button {
|
|
||||||
// Layout.alignment: Qt.AlignHCenter
|
|
||||||
// text: qsTr("Edit")
|
|
||||||
// enabled: currentPack.canEdit
|
|
||||||
//}
|
|
||||||
|
|
||||||
id: packinfo
|
id: packinfo
|
||||||
|
|
||||||
property string packName: currentPack ? currentPack.packname : ""
|
property string packName: currentPack ? currentPack.packname : ""
|
||||||
|
property string attribution: currentPack ? currentPack.attribution : ""
|
||||||
property string avatarUrl: currentPack ? currentPack.avatarUrl : ""
|
property string avatarUrl: currentPack ? currentPack.avatarUrl : ""
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
@ -227,8 +123,18 @@ ApplicationWindow {
|
||||||
|
|
||||||
MatrixText {
|
MatrixText {
|
||||||
text: packinfo.packName
|
text: packinfo.packName
|
||||||
font.pixelSize: 24
|
font.pixelSize: Math.ceil(fontMetrics.pixelSize * 1.1)
|
||||||
|
horizontalAlignment: TextEdit.AlignHCenter
|
||||||
Layout.alignment: Qt.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 {
|
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 {
|
GridView {
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
@ -272,7 +190,7 @@ ApplicationWindow {
|
||||||
width: stickerDim
|
width: stickerDim
|
||||||
height: stickerDim
|
height: stickerDim
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
ToolTip.text: ":" + model.shortcode + ": - " + model.body
|
ToolTip.text: ":" + model.shortCode + ": - " + model.body
|
||||||
ToolTip.visible: hovered
|
ToolTip.visible: hovered
|
||||||
|
|
||||||
contentItem: Image {
|
contentItem: Image {
|
||||||
|
|
|
@ -160,6 +160,7 @@
|
||||||
<file>qml/device-verification/Success.qml</file>
|
<file>qml/device-verification/Success.qml</file>
|
||||||
<file>qml/dialogs/InputDialog.qml</file>
|
<file>qml/dialogs/InputDialog.qml</file>
|
||||||
<file>qml/dialogs/ImagePackSettingsDialog.qml</file>
|
<file>qml/dialogs/ImagePackSettingsDialog.qml</file>
|
||||||
|
<file>qml/dialogs/ImagePackEditorDialog.qml</file>
|
||||||
<file>qml/ui/Ripple.qml</file>
|
<file>qml/ui/Ripple.qml</file>
|
||||||
<file>qml/ui/Spinner.qml</file>
|
<file>qml/ui/Spinner.qml</file>
|
||||||
<file>qml/ui/animations/BlinkAnimation.qml</file>
|
<file>qml/ui/animations/BlinkAnimation.qml</file>
|
||||||
|
@ -173,6 +174,7 @@
|
||||||
<file>qml/voip/VideoCall.qml</file>
|
<file>qml/voip/VideoCall.qml</file>
|
||||||
<file>qml/components/AdaptiveLayout.qml</file>
|
<file>qml/components/AdaptiveLayout.qml</file>
|
||||||
<file>qml/components/AdaptiveLayoutElement.qml</file>
|
<file>qml/components/AdaptiveLayoutElement.qml</file>
|
||||||
|
<file>qml/components/AvatarListTile.qml</file>
|
||||||
<file>qml/components/FlatButton.qml</file>
|
<file>qml/components/FlatButton.qml</file>
|
||||||
<file>qml/RoomMembers.qml</file>
|
<file>qml/RoomMembers.qml</file>
|
||||||
<file>qml/InviteDialog.qml</file>
|
<file>qml/InviteDialog.qml</file>
|
||||||
|
|
|
@ -125,7 +125,7 @@ template<class T>
|
||||||
bool
|
bool
|
||||||
containsStateUpdates(const T &e)
|
containsStateUpdates(const T &e)
|
||||||
{
|
{
|
||||||
return std::visit([](const auto &ev) { return Cache::isStateEvent(ev); }, e);
|
return std::visit([](const auto &ev) { return Cache::isStateEvent_<decltype(ev)>; }, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
|
|
|
@ -291,15 +291,9 @@ public:
|
||||||
std::optional<std::string> secret(const std::string name);
|
std::optional<std::string> secret(const std::string name);
|
||||||
|
|
||||||
template<class T>
|
template<class T>
|
||||||
static constexpr bool isStateEvent(const mtx::events::StateEvent<T> &)
|
constexpr static bool isStateEvent_ =
|
||||||
{
|
std::is_same_v<std::remove_cv_t<std::remove_reference_t<T>>,
|
||||||
return true;
|
mtx::events::StateEvent<decltype(std::declval<T>().content)>>;
|
||||||
}
|
|
||||||
template<class T>
|
|
||||||
static constexpr bool isStateEvent(const mtx::events::Event<T> &)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int compare_state_key(const MDB_val *a, const MDB_val *b)
|
static int compare_state_key(const MDB_val *a, const MDB_val *b)
|
||||||
{
|
{
|
||||||
|
@ -416,11 +410,27 @@ private:
|
||||||
}
|
}
|
||||||
|
|
||||||
std::visit(
|
std::visit(
|
||||||
[&txn, &statesdb, &stateskeydb, &eventsDb](auto e) {
|
[&txn, &statesdb, &stateskeydb, &eventsDb, &membersdb](const auto &e) {
|
||||||
if constexpr (isStateEvent(e)) {
|
if constexpr (isStateEvent_<decltype(e)>) {
|
||||||
eventsDb.put(txn, e.event_id, json(e).dump());
|
eventsDb.put(txn, e.event_id, json(e).dump());
|
||||||
|
|
||||||
if (e.type != EventType::Unsupported) {
|
if (std::is_same_v<
|
||||||
|
std::remove_cv_t<std::remove_reference_t<decltype(e)>>,
|
||||||
|
StateEvent<mtx::events::msg::Redacted>>) {
|
||||||
|
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())
|
if (e.state_key.empty())
|
||||||
statesdb.put(
|
statesdb.put(
|
||||||
txn, to_string(e.type), json(e).dump());
|
txn, to_string(e.type), json(e).dump());
|
||||||
|
|
|
@ -22,7 +22,14 @@ QHash<QString, mtx::crypto::EncryptedFile> infos;
|
||||||
QQuickImageResponse *
|
QQuickImageResponse *
|
||||||
MxcImageProvider::requestImageResponse(const QString &id, const QSize &requestedSize)
|
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);
|
pool.start(response);
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
@ -36,20 +43,24 @@ void
|
||||||
MxcImageResponse::run()
|
MxcImageResponse::run()
|
||||||
{
|
{
|
||||||
MxcImageProvider::download(
|
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()) {
|
if (image.isNull()) {
|
||||||
m_error = "Failed to download image.";
|
m_error = "Failed to download image.";
|
||||||
} else {
|
} else {
|
||||||
m_image = image;
|
m_image = image;
|
||||||
}
|
}
|
||||||
emit finished();
|
emit finished();
|
||||||
});
|
},
|
||||||
|
m_crop);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
MxcImageProvider::download(const QString &id,
|
MxcImageProvider::download(const QString &id,
|
||||||
const QSize &requestedSize,
|
const QSize &requestedSize,
|
||||||
std::function<void(QString, QSize, QImage, QString)> then)
|
std::function<void(QString, QSize, QImage, QString)> then,
|
||||||
|
bool crop)
|
||||||
{
|
{
|
||||||
std::optional<mtx::crypto::EncryptedFile> encryptionInfo;
|
std::optional<mtx::crypto::EncryptedFile> encryptionInfo;
|
||||||
auto temp = infos.find("mxc://" + id);
|
auto temp = infos.find("mxc://" + id);
|
||||||
|
@ -58,11 +69,12 @@ MxcImageProvider::download(const QString &id,
|
||||||
|
|
||||||
if (requestedSize.isValid() && !encryptionInfo) {
|
if (requestedSize.isValid() && !encryptionInfo) {
|
||||||
QString fileName =
|
QString fileName =
|
||||||
QString("%1_%2x%3_crop")
|
QString("%1_%2x%3_%4")
|
||||||
.arg(QString::fromUtf8(id.toUtf8().toBase64(QByteArray::Base64UrlEncoding |
|
.arg(QString::fromUtf8(id.toUtf8().toBase64(QByteArray::Base64UrlEncoding |
|
||||||
QByteArray::OmitTrailingEquals)))
|
QByteArray::OmitTrailingEquals)))
|
||||||
.arg(requestedSize.width())
|
.arg(requestedSize.width())
|
||||||
.arg(requestedSize.height());
|
.arg(requestedSize.height())
|
||||||
|
.arg(crop ? "crop" : "scale");
|
||||||
QFileInfo fileInfo(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) +
|
QFileInfo fileInfo(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) +
|
||||||
"/media_cache",
|
"/media_cache",
|
||||||
fileName);
|
fileName);
|
||||||
|
@ -85,7 +97,7 @@ MxcImageProvider::download(const QString &id,
|
||||||
opts.mxc_url = "mxc://" + id.toStdString();
|
opts.mxc_url = "mxc://" + id.toStdString();
|
||||||
opts.width = requestedSize.width() > 0 ? requestedSize.width() : -1;
|
opts.width = requestedSize.width() > 0 ? requestedSize.width() : -1;
|
||||||
opts.height = requestedSize.height() > 0 ? requestedSize.height() : -1;
|
opts.height = requestedSize.height() > 0 ? requestedSize.height() : -1;
|
||||||
opts.method = "crop";
|
opts.method = crop ? "crop" : "scale";
|
||||||
http::client()->get_thumbnail(
|
http::client()->get_thumbnail(
|
||||||
opts,
|
opts,
|
||||||
[fileInfo, requestedSize, then, id](const std::string &res,
|
[fileInfo, requestedSize, then, id](const std::string &res,
|
||||||
|
|
|
@ -19,9 +19,10 @@ class MxcImageResponse
|
||||||
, public QRunnable
|
, public QRunnable
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
MxcImageResponse(const QString &id, const QSize &requestedSize)
|
MxcImageResponse(const QString &id, bool crop, const QSize &requestedSize)
|
||||||
: m_id(id)
|
: m_id(id)
|
||||||
, m_requestedSize(requestedSize)
|
, m_requestedSize(requestedSize)
|
||||||
|
, m_crop(crop)
|
||||||
{
|
{
|
||||||
setAutoDelete(false);
|
setAutoDelete(false);
|
||||||
}
|
}
|
||||||
|
@ -37,6 +38,7 @@ public:
|
||||||
QString m_id, m_error;
|
QString m_id, m_error;
|
||||||
QSize m_requestedSize;
|
QSize m_requestedSize;
|
||||||
QImage m_image;
|
QImage m_image;
|
||||||
|
bool m_crop;
|
||||||
};
|
};
|
||||||
|
|
||||||
class MxcImageProvider
|
class MxcImageProvider
|
||||||
|
@ -51,7 +53,8 @@ public slots:
|
||||||
static void addEncryptionInfo(mtx::crypto::EncryptedFile info);
|
static void addEncryptionInfo(mtx::crypto::EncryptedFile info);
|
||||||
static void download(const QString &id,
|
static void download(const QString &id,
|
||||||
const QSize &requestedSize,
|
const QSize &requestedSize,
|
||||||
std::function<void(QString, QSize, QImage, QString)> then);
|
std::function<void(QString, QSize, QImage, QString)> then,
|
||||||
|
bool crop = true);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QThreadPool pool;
|
QThreadPool pool;
|
||||||
|
|
|
@ -5,12 +5,18 @@
|
||||||
#include "SingleImagePackModel.h"
|
#include "SingleImagePackModel.h"
|
||||||
|
|
||||||
#include "Cache_p.h"
|
#include "Cache_p.h"
|
||||||
|
#include "ChatPage.h"
|
||||||
#include "MatrixClient.h"
|
#include "MatrixClient.h"
|
||||||
|
#include "timeline/Permissions.h"
|
||||||
|
#include "timeline/TimelineModel.h"
|
||||||
|
|
||||||
|
#include "Logging.h"
|
||||||
|
|
||||||
SingleImagePackModel::SingleImagePackModel(ImagePackInfo pack_, QObject *parent)
|
SingleImagePackModel::SingleImagePackModel(ImagePackInfo pack_, QObject *parent)
|
||||||
: QAbstractListModel(parent)
|
: QAbstractListModel(parent)
|
||||||
, roomid_(std::move(pack_.source_room))
|
, roomid_(std::move(pack_.source_room))
|
||||||
, statekey_(std::move(pack_.state_key))
|
, statekey_(std::move(pack_.state_key))
|
||||||
|
, old_statekey_(statekey_)
|
||||||
, pack(std::move(pack_.pack))
|
, pack(std::move(pack_.pack))
|
||||||
{
|
{
|
||||||
if (!pack.pack)
|
if (!pack.pack)
|
||||||
|
@ -61,6 +67,73 @@ SingleImagePackModel::data(const QModelIndex &index, int role) const
|
||||||
return {};
|
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
|
bool
|
||||||
SingleImagePackModel::isGloballyEnabled() const
|
SingleImagePackModel::isGloballyEnabled() const
|
||||||
{
|
{
|
||||||
|
@ -98,3 +171,111 @@ SingleImagePackModel::setGloballyEnabled(bool enabled)
|
||||||
// emit this->globallyEnabledChanged();
|
// 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)));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -15,14 +15,18 @@ class SingleImagePackModel : public QAbstractListModel
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
Q_PROPERTY(QString roomid READ roomid CONSTANT)
|
Q_PROPERTY(QString roomid READ roomid CONSTANT)
|
||||||
Q_PROPERTY(QString statekey READ statekey CONSTANT)
|
Q_PROPERTY(QString statekey READ statekey WRITE setStatekey NOTIFY statekeyChanged)
|
||||||
Q_PROPERTY(QString attribution READ statekey CONSTANT)
|
Q_PROPERTY(
|
||||||
Q_PROPERTY(QString packname READ packname CONSTANT)
|
QString attribution READ attribution WRITE setAttribution NOTIFY attributionChanged)
|
||||||
Q_PROPERTY(QString avatarUrl READ avatarUrl CONSTANT)
|
Q_PROPERTY(QString packname READ packname WRITE setPackname NOTIFY packnameChanged)
|
||||||
Q_PROPERTY(bool isStickerPack READ isStickerPack CONSTANT)
|
Q_PROPERTY(QString avatarUrl READ avatarUrl WRITE setAvatarUrl NOTIFY avatarUrlChanged)
|
||||||
Q_PROPERTY(bool isEmotePack READ isEmotePack CONSTANT)
|
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
|
Q_PROPERTY(bool isGloballyEnabled READ isGloballyEnabled WRITE setGloballyEnabled NOTIFY
|
||||||
globallyEnabledChanged)
|
globallyEnabledChanged)
|
||||||
|
Q_PROPERTY(bool canEdit READ canEdit CONSTANT)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
enum Roles
|
enum Roles
|
||||||
{
|
{
|
||||||
|
@ -32,11 +36,15 @@ public:
|
||||||
IsEmote,
|
IsEmote,
|
||||||
IsSticker,
|
IsSticker,
|
||||||
};
|
};
|
||||||
|
Q_ENUM(Roles);
|
||||||
|
|
||||||
SingleImagePackModel(ImagePackInfo pack_, QObject *parent = nullptr);
|
SingleImagePackModel(ImagePackInfo pack_, QObject *parent = nullptr);
|
||||||
QHash<int, QByteArray> roleNames() const override;
|
QHash<int, QByteArray> roleNames() const override;
|
||||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||||
QVariant data(const QModelIndex &index, int role) 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 roomid() const { return QString::fromStdString(roomid_); }
|
||||||
QString statekey() const { return QString::fromStdString(statekey_); }
|
QString statekey() const { return QString::fromStdString(statekey_); }
|
||||||
|
@ -47,14 +55,30 @@ public:
|
||||||
bool isEmotePack() const { return pack.pack->is_emoji(); }
|
bool isEmotePack() const { return pack.pack->is_emoji(); }
|
||||||
|
|
||||||
bool isGloballyEnabled() const;
|
bool isGloballyEnabled() const;
|
||||||
|
bool canEdit() const;
|
||||||
void setGloballyEnabled(bool enabled);
|
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:
|
signals:
|
||||||
void globallyEnabledChanged();
|
void globallyEnabledChanged();
|
||||||
|
void statekeyChanged();
|
||||||
|
void attributionChanged();
|
||||||
|
void packnameChanged();
|
||||||
|
void avatarUrlChanged();
|
||||||
|
void isEmotePackChanged();
|
||||||
|
void isStickerPackChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::string roomid_;
|
std::string roomid_;
|
||||||
std::string statekey_;
|
std::string statekey_, old_statekey_;
|
||||||
|
|
||||||
mtx::events::msc2545::ImagePack pack;
|
mtx::events::msc2545::ImagePack pack;
|
||||||
std::vector<std::string> shortcodes;
|
std::vector<std::string> shortcodes;
|
||||||
|
|
|
@ -308,6 +308,15 @@ qml_mtx_events::fromRoomEventType(qml_mtx_events::EventType t)
|
||||||
case qml_mtx_events::KeyVerificationDone:
|
case qml_mtx_events::KeyVerificationDone:
|
||||||
case qml_mtx_events::KeyVerificationReady:
|
case qml_mtx_events::KeyVerificationReady:
|
||||||
return mtx::events::EventType::RoomMessage;
|
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:
|
default:
|
||||||
return mtx::events::EventType::Unsupported;
|
return mtx::events::EventType::Unsupported;
|
||||||
};
|
};
|
||||||
|
|
|
@ -107,7 +107,13 @@ enum EventType
|
||||||
KeyVerificationCancel,
|
KeyVerificationCancel,
|
||||||
KeyVerificationKey,
|
KeyVerificationKey,
|
||||||
KeyVerificationDone,
|
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)
|
Q_ENUM_NS(EventType)
|
||||||
mtx::events::EventType fromRoomEventType(qml_mtx_events::EventType);
|
mtx::events::EventType fromRoomEventType(qml_mtx_events::EventType);
|
||||||
|
|
Loading…
Reference in a new issue