Itinerary Component
Move the itinerary model representation to it's own component and instantiate from MessageComponentModel. This starts to lay some groundwork for previewing other files.
This commit is contained in:
@@ -338,6 +338,7 @@ qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
|
||||
qml/StateKeys.qml
|
||||
qml/CodeComponent.qml
|
||||
qml/QuoteComponent.qml
|
||||
qml/ItineraryComponent.qml
|
||||
RESOURCES
|
||||
qml/confetti.png
|
||||
qml/glowdot.png
|
||||
|
||||
@@ -40,6 +40,7 @@ public:
|
||||
Code, /**< A code section. */
|
||||
Quote, /**< A quote section. */
|
||||
File, /**< A message that is a file. */
|
||||
Itinerary, /**< A preview for a file that can integrate with KDE itinerary.. */
|
||||
Poll, /**< The initial event for a poll. */
|
||||
Location, /**< A location event. */
|
||||
LiveLocation, /**< The initial event of a shared live location (i.e., the place where this is supposed to be shown in the timeline). */
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include "itinerarymodel.h"
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QProcess>
|
||||
|
||||
#include "config-neochat.h"
|
||||
@@ -16,20 +17,6 @@ ItineraryModel::ItineraryModel(QObject *parent)
|
||||
{
|
||||
}
|
||||
|
||||
void ItineraryModel::setConnection(NeoChatConnection *connection)
|
||||
{
|
||||
if (m_connection == connection) {
|
||||
return;
|
||||
}
|
||||
m_connection = connection;
|
||||
Q_EMIT connectionChanged();
|
||||
}
|
||||
|
||||
NeoChatConnection *ItineraryModel::connection() const
|
||||
{
|
||||
return m_connection;
|
||||
}
|
||||
|
||||
QVariant ItineraryModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid()) {
|
||||
@@ -133,11 +120,7 @@ QString ItineraryModel::path() const
|
||||
|
||||
void ItineraryModel::setPath(const QString &path)
|
||||
{
|
||||
if (path == m_path) {
|
||||
return;
|
||||
}
|
||||
m_path = path;
|
||||
Q_EMIT pathChanged();
|
||||
loadData();
|
||||
}
|
||||
|
||||
|
||||
@@ -4,19 +4,16 @@
|
||||
#pragma once
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QJsonArray>
|
||||
#include <QPointer>
|
||||
#include <QQmlEngine>
|
||||
#include <QString>
|
||||
|
||||
#include "neochatconnection.h"
|
||||
|
||||
class ItineraryModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
|
||||
Q_PROPERTY(NeoChatConnection *connection READ connection WRITE setConnection NOTIFY connectionChanged)
|
||||
Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged)
|
||||
QML_UNCREATABLE("")
|
||||
|
||||
public:
|
||||
enum Roles {
|
||||
@@ -37,9 +34,6 @@ public:
|
||||
Q_ENUM(Roles)
|
||||
explicit ItineraryModel(QObject *parent = nullptr);
|
||||
|
||||
void setConnection(NeoChatConnection *connection);
|
||||
NeoChatConnection *connection() const;
|
||||
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
int rowCount(const QModelIndex &parent = {}) const override;
|
||||
|
||||
@@ -50,12 +44,7 @@ public:
|
||||
|
||||
Q_INVOKABLE void sendToItinerary();
|
||||
|
||||
Q_SIGNALS:
|
||||
void connectionChanged();
|
||||
void pathChanged();
|
||||
|
||||
private:
|
||||
QPointer<NeoChatConnection> m_connection;
|
||||
QJsonArray m_data;
|
||||
QString m_path;
|
||||
void loadData();
|
||||
|
||||
@@ -64,11 +64,13 @@ MessageContentModel::MessageContentModel(const Quotient::RoomEvent *event, NeoCh
|
||||
});
|
||||
connect(m_room, &NeoChatRoom::fileTransferCompleted, this, [this](const QString &eventId) {
|
||||
if (m_event != nullptr && eventId == m_event->id()) {
|
||||
updateComponents();
|
||||
Q_EMIT dataChanged(index(0), index(rowCount() - 1), {FileTransferInfoRole});
|
||||
}
|
||||
});
|
||||
connect(m_room, &NeoChatRoom::fileTransferFailed, this, [this](const QString &eventId) {
|
||||
if (m_event != nullptr && eventId == m_event->id()) {
|
||||
updateComponents();
|
||||
Q_EMIT dataChanged(index(0), index(rowCount() - 1), {FileTransferInfoRole});
|
||||
}
|
||||
});
|
||||
@@ -152,6 +154,9 @@ QVariant MessageContentModel::data(const QModelIndex &index, int role) const
|
||||
return QVariant::fromValue(m_room->fileTransferInfo(event->id()));
|
||||
}
|
||||
}
|
||||
if (role == ItineraryModelRole) {
|
||||
return QVariant::fromValue<ItineraryModel *>(m_itineraryModel);
|
||||
}
|
||||
if (role == LatitudeRole) {
|
||||
return eventHandler.getLatitude();
|
||||
}
|
||||
@@ -209,6 +214,7 @@ QHash<int, QByteArray> MessageContentModel::roleNames() const
|
||||
roles[AuthorRole] = "author";
|
||||
roles[MediaInfoRole] = "mediaInfo";
|
||||
roles[FileTransferInfoRole] = "fileTransferInfo";
|
||||
roles[ItineraryModelRole] = "itineraryModel";
|
||||
roles[LatitudeRole] = "latitude";
|
||||
roles[LongitudeRole] = "longitude";
|
||||
roles[AssetRole] = "asset";
|
||||
@@ -247,6 +253,12 @@ void MessageContentModel::updateComponents(bool isEditing)
|
||||
const auto event = eventCast<const Quotient::RoomMessageEvent>(m_event);
|
||||
auto body = EventHandler::rawMessageBody(*event);
|
||||
m_components.append(TextHandler().textComponents(body, EventHandler::messageBodyInputFormat(*event), m_room, event, event->isReplaced()));
|
||||
} else if (eventHandler.messageComponentType() == MessageComponentType::File) {
|
||||
m_components += MessageComponent{MessageComponentType::File, QString(), {}};
|
||||
updateItineraryModel();
|
||||
if (m_itineraryModel != nullptr) {
|
||||
m_components += MessageComponent{MessageComponentType::Itinerary, QString(), {}};
|
||||
}
|
||||
} else {
|
||||
m_components += MessageComponent{eventHandler.messageComponentType(), QString(), {}};
|
||||
}
|
||||
@@ -262,3 +274,25 @@ void MessageContentModel::updateComponents(bool isEditing)
|
||||
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
void MessageContentModel::updateItineraryModel()
|
||||
{
|
||||
if (m_room == nullptr || m_event == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (auto event = eventCast<const Quotient::RoomMessageEvent>(m_event)) {
|
||||
if (event->hasFileContent()) {
|
||||
auto filePath = m_room->fileTransferInfo(event->id()).localPath;
|
||||
if (filePath.isEmpty() && m_itineraryModel != nullptr) {
|
||||
delete m_itineraryModel;
|
||||
m_itineraryModel = nullptr;
|
||||
} else if (!filePath.isEmpty()) {
|
||||
if (m_itineraryModel == nullptr) {
|
||||
m_itineraryModel = new ItineraryModel(this);
|
||||
}
|
||||
m_itineraryModel->setPath(filePath.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include "enums/messagecomponenttype.h"
|
||||
#include "eventhandler.h"
|
||||
#include "itinerarymodel.h"
|
||||
#include "linkpreviewer.h"
|
||||
#include "neochatroom.h"
|
||||
|
||||
@@ -45,6 +46,7 @@ public:
|
||||
AuthorRole, /**< The author of the event. */
|
||||
MediaInfoRole, /**< The media info for the event. */
|
||||
FileTransferInfoRole, /**< FileTransferInfo for any downloading files. */
|
||||
ItineraryModelRole, /**< The itinerary model for a file. */
|
||||
LatitudeRole, /**< Latitude for a location event. */
|
||||
LongitudeRole, /**< Longitude for a location event. */
|
||||
AssetRole, /**< Type of location event, e.g. self pin of the user location. */
|
||||
@@ -92,4 +94,7 @@ private:
|
||||
void updateComponents(bool isEditing = false);
|
||||
|
||||
LinkPreviewer *m_linkPreviewer = nullptr;
|
||||
ItineraryModel *m_itineraryModel = nullptr;
|
||||
|
||||
void updateItineraryModel();
|
||||
};
|
||||
|
||||
@@ -59,7 +59,6 @@ ColumnLayout {
|
||||
*/
|
||||
readonly property bool downloaded: root.fileTransferInfo && root.fileTransferInfo.completed
|
||||
onDownloadedChanged: {
|
||||
itineraryModel.path = root.fileTransferInfo.localPath;
|
||||
if (autoOpenFile) {
|
||||
openSavedFile();
|
||||
}
|
||||
@@ -145,15 +144,6 @@ ColumnLayout {
|
||||
QQC2.ToolTip.text: i18nc("tooltip for a button on a message; stops downloading the message's file", "Stop Download")
|
||||
onClicked: root.room.cancelFileTransfer(root.eventId)
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "raw"
|
||||
when: true
|
||||
|
||||
PropertyChanges {
|
||||
target: downloadButton
|
||||
onClicked: root.saveFileAs()
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -196,6 +186,7 @@ ColumnLayout {
|
||||
QQC2.Button {
|
||||
id: downloadButton
|
||||
icon.name: "download"
|
||||
onClicked: root.saveFileAs()
|
||||
|
||||
QQC2.ToolTip.text: i18nc("tooltip for a button on a message; offers ability to download its file", "Download")
|
||||
QQC2.ToolTip.visible: hovered
|
||||
@@ -220,83 +211,4 @@ ColumnLayout {
|
||||
}
|
||||
}
|
||||
}
|
||||
Repeater {
|
||||
id: itinerary
|
||||
model: ItineraryModel {
|
||||
id: itineraryModel
|
||||
connection: root.room.connection
|
||||
}
|
||||
delegate: DelegateChooser {
|
||||
role: "type"
|
||||
DelegateChoice {
|
||||
roleValue: "TrainReservation"
|
||||
delegate: ColumnLayout {
|
||||
Kirigami.Separator {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
RowLayout {
|
||||
QQC2.Label {
|
||||
text: model.name
|
||||
}
|
||||
QQC2.Label {
|
||||
text: model.coach ? i18n("Coach: %1, Seat: %2", model.coach, model.seat) : ""
|
||||
visible: model.coach
|
||||
opacity: 0.7
|
||||
}
|
||||
}
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
ColumnLayout {
|
||||
QQC2.Label {
|
||||
text: model.departureStation + (model.departurePlatform ? (" [" + model.departurePlatform + "]") : "")
|
||||
}
|
||||
QQC2.Label {
|
||||
text: model.departureTime
|
||||
opacity: 0.7
|
||||
}
|
||||
}
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
ColumnLayout {
|
||||
QQC2.Label {
|
||||
text: model.arrivalStation + (model.arrivalPlatform ? (" [" + model.arrivalPlatform + "]") : "")
|
||||
}
|
||||
QQC2.Label {
|
||||
text: model.arrivalTime
|
||||
opacity: 0.7
|
||||
Layout.alignment: Qt.AlignRight
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
DelegateChoice {
|
||||
roleValue: "LodgingReservation"
|
||||
delegate: ColumnLayout {
|
||||
Kirigami.Separator {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
QQC2.Label {
|
||||
text: model.name
|
||||
}
|
||||
QQC2.Label {
|
||||
text: i18nc("<start time> - <end time>", "%1 - %2", model.startTime, model.endTime)
|
||||
}
|
||||
QQC2.Label {
|
||||
text: model.address
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
QQC2.Button {
|
||||
icon.name: "map-globe"
|
||||
text: i18nc("@action", "Send to KDE Itinerary")
|
||||
QQC2.ToolTip.visible: hovered
|
||||
QQC2.ToolTip.text: text
|
||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
onClicked: itineraryModel.sendToItinerary()
|
||||
visible: itinerary.count > 0
|
||||
}
|
||||
}
|
||||
|
||||
109
src/qml/ItineraryComponent.qml
Normal file
109
src/qml/ItineraryComponent.qml
Normal file
@@ -0,0 +1,109 @@
|
||||
// SPDX-FileCopyrightText: 2024 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
import QtQuick.Layouts
|
||||
import Qt.labs.qmlmodels
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
|
||||
/**
|
||||
* @brief A component to show a preview of a file that can integrate with KDE itinerary.
|
||||
*/
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
/**
|
||||
* @brief A model with the itinerary preview of the file.
|
||||
*/
|
||||
required property var itineraryModel
|
||||
|
||||
/**
|
||||
* @brief The maximum width that the bubble's content can be.
|
||||
*/
|
||||
property real maxContentWidth: -1
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumWidth: root.maxContentWidth
|
||||
spacing: Kirigami.Units.largeSpacing
|
||||
|
||||
Repeater {
|
||||
id: itinerary
|
||||
model: root.itineraryModel
|
||||
onModelChanged: console.warn(itinerary.count)
|
||||
delegate: DelegateChooser {
|
||||
role: "type"
|
||||
DelegateChoice {
|
||||
roleValue: "TrainReservation"
|
||||
delegate: ColumnLayout {
|
||||
Kirigami.Separator {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
RowLayout {
|
||||
QQC2.Label {
|
||||
text: model.name
|
||||
}
|
||||
QQC2.Label {
|
||||
text: model.coach ? i18n("Coach: %1, Seat: %2", model.coach, model.seat) : ""
|
||||
visible: model.coach
|
||||
opacity: 0.7
|
||||
}
|
||||
}
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
ColumnLayout {
|
||||
QQC2.Label {
|
||||
text: model.departureStation + (model.departurePlatform ? (" [" + model.departurePlatform + "]") : "")
|
||||
}
|
||||
QQC2.Label {
|
||||
text: model.departureTime
|
||||
opacity: 0.7
|
||||
}
|
||||
}
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
ColumnLayout {
|
||||
QQC2.Label {
|
||||
text: model.arrivalStation + (model.arrivalPlatform ? (" [" + model.arrivalPlatform + "]") : "")
|
||||
}
|
||||
QQC2.Label {
|
||||
text: model.arrivalTime
|
||||
opacity: 0.7
|
||||
Layout.alignment: Qt.AlignRight
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
DelegateChoice {
|
||||
roleValue: "LodgingReservation"
|
||||
delegate: ColumnLayout {
|
||||
Kirigami.Separator {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
QQC2.Label {
|
||||
text: model.name
|
||||
}
|
||||
QQC2.Label {
|
||||
text: i18nc("<start time> - <end time>", "%1 - %2", model.startTime, model.endTime)
|
||||
}
|
||||
QQC2.Label {
|
||||
text: model.address
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
QQC2.Button {
|
||||
icon.name: "map-globe"
|
||||
text: i18nc("@action", "Send to KDE Itinerary")
|
||||
QQC2.ToolTip.visible: hovered
|
||||
QQC2.ToolTip.text: text
|
||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
onClicked: itineraryModel.sendToItinerary()
|
||||
visible: itinerary.count > 0
|
||||
}
|
||||
}
|
||||
@@ -117,6 +117,13 @@ DelegateChooser {
|
||||
}
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: MessageComponentType.Itinerary
|
||||
delegate: ItineraryComponent {
|
||||
maxContentWidth: root.maxContentWidth
|
||||
}
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: MessageComponentType.Poll
|
||||
delegate: PollComponent {
|
||||
|
||||
Reference in New Issue
Block a user