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(
|
||||
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 "")
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
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
|
||||
|
||||
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 {
|
||||
|
|
|
@ -160,6 +160,7 @@
|
|||
<file>qml/device-verification/Success.qml</file>
|
||||
<file>qml/dialogs/InputDialog.qml</file>
|
||||
<file>qml/dialogs/ImagePackSettingsDialog.qml</file>
|
||||
<file>qml/dialogs/ImagePackEditorDialog.qml</file>
|
||||
<file>qml/ui/Ripple.qml</file>
|
||||
<file>qml/ui/Spinner.qml</file>
|
||||
<file>qml/ui/animations/BlinkAnimation.qml</file>
|
||||
|
@ -173,6 +174,7 @@
|
|||
<file>qml/voip/VideoCall.qml</file>
|
||||
<file>qml/components/AdaptiveLayout.qml</file>
|
||||
<file>qml/components/AdaptiveLayoutElement.qml</file>
|
||||
<file>qml/components/AvatarListTile.qml</file>
|
||||
<file>qml/components/FlatButton.qml</file>
|
||||
<file>qml/RoomMembers.qml</file>
|
||||
<file>qml/InviteDialog.qml</file>
|
||||
|
|
|
@ -125,7 +125,7 @@ template<class T>
|
|||
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_<decltype(ev)>; }, e);
|
||||
}
|
||||
|
||||
bool
|
||||
|
|
|
@ -291,15 +291,9 @@ public:
|
|||
std::optional<std::string> secret(const std::string name);
|
||||
|
||||
template<class T>
|
||||
static constexpr bool isStateEvent(const mtx::events::StateEvent<T> &)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
template<class T>
|
||||
static constexpr bool isStateEvent(const mtx::events::Event<T> &)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
constexpr static bool isStateEvent_ =
|
||||
std::is_same_v<std::remove_cv_t<std::remove_reference_t<T>>,
|
||||
mtx::events::StateEvent<decltype(std::declval<T>().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_<decltype(e)>) {
|
||||
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())
|
||||
statesdb.put(
|
||||
txn, to_string(e.type), json(e).dump());
|
||||
|
|
|
@ -22,7 +22,14 @@ QHash<QString, mtx::crypto::EncryptedFile> 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<void(QString, QSize, QImage, QString)> then)
|
||||
std::function<void(QString, QSize, QImage, QString)> then,
|
||||
bool crop)
|
||||
{
|
||||
std::optional<mtx::crypto::EncryptedFile> 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,
|
||||
|
|
|
@ -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<void(QString, QSize, QImage, QString)> then);
|
||||
std::function<void(QString, QSize, QImage, QString)> then,
|
||||
bool crop = true);
|
||||
|
||||
private:
|
||||
QThreadPool pool;
|
||||
|
|
|
@ -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)));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<int, QByteArray> 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<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::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;
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue