Implement sharing with Purpose (export)
This provide both a mobile and desktop view Fix #181
This commit is contained in:
71
imports/NeoChat/Menu/ShareAction.qml
Normal file
71
imports/NeoChat/Menu/ShareAction.qml
Normal file
@@ -0,0 +1,71 @@
|
||||
// SPDX-FileCopyrightText: 2021 Carl Schwan <carl@carlschwan.eu>
|
||||
// SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
|
||||
|
||||
import QtQuick 2.7
|
||||
import QtQuick.Layouts 1.3
|
||||
import QtQuick.Controls 2.15 as Controls
|
||||
import org.kde.kirigami 2.14 as Kirigami
|
||||
|
||||
/**
|
||||
* Action that allows an user to share data with other apps and service
|
||||
* installed on their computer. The goal of this high level API is to
|
||||
* adapte itself for each platform and adopt the native component.
|
||||
*
|
||||
* TODO add Android support
|
||||
*/
|
||||
Kirigami.Action {
|
||||
id: shareAction
|
||||
iconName: "emblem-shared-symbolic"
|
||||
text: i18n("Share")
|
||||
tooltip: i18n("Share the selected media")
|
||||
|
||||
property var doBeforeSharing: () => {}
|
||||
visible: false
|
||||
|
||||
/**
|
||||
* This property holds the input data for purpose.
|
||||
*
|
||||
* @code{.qml}
|
||||
* Purpose.ShareAction {
|
||||
* inputData: {
|
||||
* 'urls': ['file://home/notroot/Pictures/mypicture.png'],
|
||||
* 'mimeType': ['image/png']
|
||||
* }
|
||||
* }
|
||||
* @endcode
|
||||
*/
|
||||
property var inputData: ({})
|
||||
|
||||
property Instantiator _instantiator: Instantiator {
|
||||
Component.onCompleted: {
|
||||
const purposeModel = Qt.createQmlObject('import org.kde.purpose 1.0 as Purpose;
|
||||
Purpose.PurposeAlternativesModel {
|
||||
pluginType: "Export"
|
||||
}', shareAction._instantiator);
|
||||
purposeModel.inputData = Qt.binding(function() {
|
||||
return shareAction.inputData;
|
||||
});
|
||||
_instantiator.model = purposeModel;
|
||||
shareAction.visible = true;
|
||||
}
|
||||
|
||||
delegate: Kirigami.Action {
|
||||
property int index
|
||||
text: model.display
|
||||
icon.name: model.iconName
|
||||
onTriggered: {
|
||||
doBeforeSharing();
|
||||
applicationWindow().pageStack.pushDialogLayer('qrc:/imports/NeoChat/Menu/ShareDialog.qml', {
|
||||
title: shareAction.tooltip,
|
||||
index: index,
|
||||
model: shareAction._instantiator.model
|
||||
})
|
||||
}
|
||||
}
|
||||
onObjectAdded: {
|
||||
object.index = index;
|
||||
shareAction.children.push(object)
|
||||
}
|
||||
onObjectRemoved: shareAction.children = Array.from(shareAction.children).filter(obj => obj.pluginId !== object.pluginId)
|
||||
}
|
||||
}
|
||||
10
imports/NeoChat/Menu/ShareActionAndroid.qml
Normal file
10
imports/NeoChat/Menu/ShareActionAndroid.qml
Normal file
@@ -0,0 +1,10 @@
|
||||
// SPDX-FileCopyrightText: 2021 Carl Schwan <carl@carlschwan.eu>
|
||||
// SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
|
||||
|
||||
import org.kde.kirigami 2.14 as Kirigami
|
||||
|
||||
Kirigami.Action {
|
||||
property var inputData: ({})
|
||||
property var doBeforeSharing: () => {}
|
||||
visible: false
|
||||
}
|
||||
69
imports/NeoChat/Menu/ShareDialog.qml
Normal file
69
imports/NeoChat/Menu/ShareDialog.qml
Normal file
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2017 Atul Sharma <atulsharma406@gmail.com>
|
||||
* SPDX-FileCopyrightText: 2021 Carl Schwan <carl@carlschwan.eu>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
|
||||
*/
|
||||
|
||||
import QtQuick 2.7
|
||||
import QtQuick.Layouts 1.3
|
||||
import QtQuick.Controls 2.15 as Controls
|
||||
import org.kde.purpose 1.0 as Purpose
|
||||
import org.kde.notification 1.0
|
||||
import org.kde.kirigami 2.14 as Kirigami
|
||||
|
||||
Kirigami.Page {
|
||||
id: window
|
||||
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
topPadding: 0
|
||||
bottomPadding: 0
|
||||
|
||||
property alias index: jobView.index
|
||||
property alias model: jobView.model
|
||||
|
||||
Controls.Action {
|
||||
shortcut: 'Escape'
|
||||
onTriggered: window.closeDialog()
|
||||
}
|
||||
|
||||
Notification {
|
||||
id: sharingFailed
|
||||
eventId: "sharingFailed"
|
||||
text: i18n("Sharing failed")
|
||||
urgency: Notification.NormalUrgency
|
||||
}
|
||||
|
||||
Notification {
|
||||
id: sharingSuccess
|
||||
eventId: "sharingSuccess"
|
||||
flags: Notification.Persistent
|
||||
}
|
||||
|
||||
Component.onCompleted: jobView.start()
|
||||
|
||||
contentItem: Purpose.JobView {
|
||||
id: jobView
|
||||
onStateChanged: {
|
||||
if (state === Purpose.PurposeJobController.Finished) {
|
||||
if (jobView.job.output.url !== "") {
|
||||
// Show share url
|
||||
// TODO no needed anymore in purpose > 5.90
|
||||
sharingSuccess.text = i18n("Shared url for image is <a href='%1'>%1</a>", jobView.output.url);
|
||||
sharingSuccess.sendEvent();
|
||||
Clipboard.saveText(jobView.output.url);
|
||||
}
|
||||
window.closeDialog()
|
||||
} else if (state === Purpose.PurposeJobController.Error) {
|
||||
// Show failure notification
|
||||
sharingFailed.sendEvent();
|
||||
|
||||
window.closeDialog()
|
||||
} else if (state === Purpose.PurposeJobController.Cancelled) {
|
||||
// Do nothing
|
||||
window.closeDialog()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ MessageDelegateContextMenu {
|
||||
|
||||
required property var file
|
||||
required property var progressInfo
|
||||
required property string mimeType
|
||||
|
||||
property list<Kirigami.Action> actions: [
|
||||
Kirigami.Action {
|
||||
@@ -74,6 +75,27 @@ MessageDelegateContextMenu {
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
property list<Kirigami.Action> nestedActions: [
|
||||
ShareAction {
|
||||
id: shareAction
|
||||
inputData: {
|
||||
'urls': [],
|
||||
'mimeType': [mimeType]
|
||||
}
|
||||
property string filename: StandardPaths.writableLocation(StandardPaths.CacheLocation) + "/" + eventId.replace(":", "_").replace("/", "_").replace("+", "_") + currentRoom.fileNameToDownload(eventId);
|
||||
|
||||
doBeforeSharing: () => {
|
||||
currentRoom.downloadFile(eventId, filename)
|
||||
}
|
||||
Component.onCompleted: {
|
||||
shareAction.inputData = {
|
||||
urls: [filename],
|
||||
mimeType: [mimeType]
|
||||
};
|
||||
}
|
||||
}
|
||||
]
|
||||
Component {
|
||||
id: saveAsDialog
|
||||
FileDialog {
|
||||
|
||||
@@ -21,6 +21,8 @@ Loader {
|
||||
required property string source
|
||||
property string selectedText: ""
|
||||
|
||||
property list<Kirigami.Action> nestedActions
|
||||
|
||||
property list<Kirigami.Action> actions: [
|
||||
Kirigami.Action {
|
||||
text: i18n("Edit")
|
||||
@@ -63,9 +65,36 @@ Loader {
|
||||
id: regularMenu
|
||||
|
||||
QQC2.Menu {
|
||||
id: menu
|
||||
Instantiator {
|
||||
model: loadRoot.nestedActions
|
||||
delegate: QQC2.Menu {
|
||||
id: menuItem
|
||||
visible: modelData.visible
|
||||
title: modelData.text
|
||||
|
||||
Instantiator {
|
||||
model: modelData.children
|
||||
delegate: QQC2.MenuItem {
|
||||
text: modelData.text
|
||||
icon.name: modelData.icon.name
|
||||
onTriggered: modelData.trigger()
|
||||
}
|
||||
onObjectAdded: {
|
||||
menuItem.insertItem(0, object)
|
||||
}
|
||||
}
|
||||
}
|
||||
onObjectAdded: {
|
||||
object.visible = false;
|
||||
menu.addMenu(object)
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: loadRoot.actions
|
||||
QQC2.MenuItem {
|
||||
id: menuItem
|
||||
visible: modelData.visible
|
||||
action: modelData
|
||||
onClicked: loadRoot.item.close();
|
||||
@@ -106,7 +135,7 @@ Loader {
|
||||
|
||||
Kirigami.OverlayDrawer {
|
||||
id: drawer
|
||||
height: popupContent.implicitHeight
|
||||
height: stackView.implicitHeight
|
||||
edge: Qt.BottomEdge
|
||||
padding: 0
|
||||
leftPadding: 0
|
||||
@@ -116,85 +145,146 @@ Loader {
|
||||
|
||||
parent: applicationWindow().overlay
|
||||
|
||||
ColumnLayout {
|
||||
id: popupContent
|
||||
QQC2.StackView {
|
||||
id: stackView
|
||||
width: parent.width
|
||||
spacing: 0
|
||||
RowLayout {
|
||||
id: headerLayout
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: Kirigami.Units.largeSpacing
|
||||
spacing: Kirigami.Units.largeSpacing
|
||||
Kirigami.Avatar {
|
||||
id: avatar
|
||||
source: author.avatarMediaId ? ("image://mxc/" + author.avatarMediaId) : ""
|
||||
Layout.preferredWidth: Kirigami.Units.gridUnit * 3
|
||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 3
|
||||
Layout.alignment: Qt.AlignTop
|
||||
}
|
||||
implicitHeight: currentItem.implicitHeight
|
||||
|
||||
Component {
|
||||
id: nestedActionsComponent
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
Kirigami.Heading {
|
||||
level: 3
|
||||
Layout.fillWidth: true
|
||||
text: currentRoom.htmlSafeMemberName(author.id)
|
||||
wrapMode: Text.WordWrap
|
||||
id: actionLayout
|
||||
property string title: ""
|
||||
property list<Kirigami.Action> actions
|
||||
width: parent.width
|
||||
spacing: 0
|
||||
RowLayout {
|
||||
QQC2.ToolButton {
|
||||
icon.name: 'draw-arrow-back'
|
||||
onClicked: stackView.pop()
|
||||
}
|
||||
Kirigami.Heading {
|
||||
level: 3
|
||||
Layout.fillWidth: true
|
||||
text: actionLayout.title
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
}
|
||||
QQC2.Label {
|
||||
text: message
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.WordWrap
|
||||
Repeater {
|
||||
id: listViewAction
|
||||
model: actionLayout.actions
|
||||
|
||||
onLinkActivated: RoomManager.openResource(link);
|
||||
Kirigami.BasicListItem {
|
||||
icon: modelData.icon.name
|
||||
iconColor: modelData.icon.color ?? undefined
|
||||
enabled: modelData.enabled
|
||||
visible: modelData.visible
|
||||
text: modelData.text
|
||||
onClicked: {
|
||||
modelData.triggered()
|
||||
loadRoot.item.close();
|
||||
}
|
||||
implicitHeight: visible ? Kirigami.Units.gridUnit * 3 : 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Kirigami.Separator {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
RowLayout {
|
||||
initialItem: ColumnLayout {
|
||||
id: popupContent
|
||||
width: parent.width
|
||||
spacing: 0
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 2.5
|
||||
Repeater {
|
||||
model: ["👍", "👎️", "😄", "🎉", "🚀", "👀"]
|
||||
delegate: QQC2.ItemDelegate {
|
||||
RowLayout {
|
||||
id: headerLayout
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: Kirigami.Units.largeSpacing
|
||||
spacing: Kirigami.Units.largeSpacing
|
||||
Kirigami.Avatar {
|
||||
id: avatar
|
||||
source: author.avatarMediaId ? ("image://mxc/" + author.avatarMediaId) : ""
|
||||
Layout.preferredWidth: Kirigami.Units.gridUnit * 3
|
||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 3
|
||||
Layout.alignment: Qt.AlignTop
|
||||
}
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
contentItem: Kirigami.Heading {
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
|
||||
font.family: "emoji"
|
||||
text: modelData
|
||||
Kirigami.Heading {
|
||||
level: 3
|
||||
Layout.fillWidth: true
|
||||
text: currentRoom.htmlSafeMemberName(author.id)
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
QQC2.Label {
|
||||
text: message
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.WordWrap
|
||||
|
||||
onLinkActivated: RoomManager.openResource(link);
|
||||
}
|
||||
}
|
||||
}
|
||||
Kirigami.Separator {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
RowLayout {
|
||||
spacing: 0
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 2.5
|
||||
Repeater {
|
||||
model: ["👍", "👎️", "😄", "🎉", "🚀", "👀"]
|
||||
delegate: QQC2.ItemDelegate {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
contentItem: Kirigami.Heading {
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
|
||||
font.family: "emoji"
|
||||
text: modelData
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
currentRoom.toggleReaction(eventId, modelData);
|
||||
loadRoot.item.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Kirigami.Separator {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
Repeater {
|
||||
id: listViewAction
|
||||
model: loadRoot.actions
|
||||
|
||||
Kirigami.BasicListItem {
|
||||
icon: modelData.icon.name
|
||||
iconColor: modelData.icon.color ?? undefined
|
||||
enabled: modelData.enabled
|
||||
visible: modelData.visible
|
||||
text: modelData.text
|
||||
onClicked: {
|
||||
currentRoom.toggleReaction(eventId, modelData);
|
||||
modelData.triggered()
|
||||
loadRoot.item.close();
|
||||
}
|
||||
implicitHeight: visible ? Kirigami.Units.gridUnit * 3 : 0
|
||||
}
|
||||
}
|
||||
}
|
||||
Kirigami.Separator {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
Repeater {
|
||||
id: listViewAction
|
||||
model: loadRoot.actions
|
||||
|
||||
Kirigami.BasicListItem {
|
||||
icon: modelData.icon.name
|
||||
iconColor: modelData.icon.color ?? undefined
|
||||
enabled: modelData.enabled
|
||||
visible: modelData.visible
|
||||
text: modelData.text
|
||||
onClicked: {
|
||||
modelData.triggered()
|
||||
loadRoot.item.close();
|
||||
Repeater {
|
||||
model: loadRoot.nestedActions
|
||||
|
||||
Kirigami.BasicListItem {
|
||||
action: modelData
|
||||
visible: modelData.visible
|
||||
implicitHeight: visible ? Kirigami.Units.gridUnit * 3 : 0
|
||||
onClicked: {
|
||||
stackView.push(nestedActionsComponent, {
|
||||
title: modelData.text,
|
||||
actions: modelData.children
|
||||
});
|
||||
}
|
||||
}
|
||||
implicitHeight: visible ? Kirigami.Units.gridUnit * 3 : 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,3 +2,5 @@ module NeoChat.Menu
|
||||
RoomListContextMenu 1.0 RoomListContextMenu.qml
|
||||
GlobalMenu 1.0 GlobalMenu.qml
|
||||
EditMenu 1.0 EditMenu.qml
|
||||
ShareAction 1.0 ShareAction.qml
|
||||
ShareDialog 1.0 ShareDialog.qml
|
||||
|
||||
Reference in New Issue
Block a user