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:
James Graham
2024-03-16 09:28:30 +00:00
parent 81a79105d7
commit 5ff199cc3e
9 changed files with 161 additions and 120 deletions

View File

@@ -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

View File

@@ -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). */

View File

@@ -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();
}

View File

@@ -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();

View File

@@ -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());
}
}
}
}

View File

@@ -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();
};

View File

@@ -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
}
}

View 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
}
}

View File

@@ -117,6 +117,13 @@ DelegateChooser {
}
}
DelegateChoice {
roleValue: MessageComponentType.Itinerary
delegate: ItineraryComponent {
maxContentWidth: root.maxContentWidth
}
}
DelegateChoice {
roleValue: MessageComponentType.Poll
delegate: PollComponent {