Commit 153b9116 authored by Volker Krause's avatar Volker Krause

First draft of journey query integration

This allows you to search for alternative connections for a train
reservation. Useful for example when missing a connection, or for
picking a specific connection for an unbound ticket.

Still lots of work to do, it still looks horrible and you can't actually
save a new connection yet.
parent 3ad04a31
...@@ -47,6 +47,7 @@ set(itinerary_app_srcs ...@@ -47,6 +47,7 @@ set(itinerary_app_srcs
${qml_srcs} ${qml_srcs}
brightnessmanager.cpp brightnessmanager.cpp
livedatamanager.cpp livedatamanager.cpp
journeyquerycontroller.cpp
) )
if (ANDROID) if (ANDROID)
list(APPEND itinerary_app_srcs androidbrightnessbackend.cpp) list(APPEND itinerary_app_srcs androidbrightnessbackend.cpp)
......
/*
Copyright (C) 2019 Volker Krause <vkrause@kde.org>
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU Library General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at your
option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import QtQuick 2.5
import QtQuick.Layouts 1.1
import QtQuick.Controls 2.1 as QQC2
import org.kde.kirigami 2.4 as Kirigami
import org.kde.kpublictransport 1.0
import "." as App
Kirigami.ScrollablePage {
property var batchId;
id: root
title: i18n("Alternative Connections")
Component.onCompleted: _journeyQueryController.queryJourney(batchId);
// TODO localize and move to C++
function displayDuration(dur)
{
if (dur < 60)
return "<1min";
if (dur < 3600)
return Math.floor(dur/60) + "min";
return Math.floor(dur/3600) + ":" + Math.floor((dur % 3600)/60)
}
Component {
id: sectionDelegate
Item {
implicitHeight: topLayout.implicitHeight
implicitWidth: topLayout.implicitWidth
RowLayout {
id: topLayout
Rectangle {
id: colorBar
width: Kirigami.Units.largeSpacing
color: modelData.route.line.color
Layout.fillHeight: true
}
QQC2.Label {
text: {
switch (modelData.mode) {
case JourneySection.PublicTransport:
{
switch (modelData.route.line.mode) {
case Line.Air: return "✈️";
case Line.Boat: return "🛥️";
case Line.Bus: return "🚍";
case Line.BusRapidTransit: return "🚌";
case Line.Coach: return "🚌";
case Line.Ferry: return "⛴️";
case Line.Funicular: return "🚞";
case Line.LocalTrain: return "🚆";
case Line.LongDistanceTrain: return "🚄";
case Line.Metro: return "🚇";
case Line.RailShuttle: return "🚅";
case Line.RapidTransit: return "🚊";
case Line.Shuttle: return "🚐";
case Line.Taxi: return "🚕";
case Line.Train: return "🚆";
case Line.Tramway: return "🚈";
default: return "?";
}
break;
}
case JourneySection.Walking: return "🚶";
case JourneySection.Waiting: return "";
case JourneySection.Transfer: return "";
default: return "?";
}
}
font.pointSize: Kirigami.Theme.defaultFont.pointSize * 2
}
ColumnLayout {
Layout.fillWidth: true
RowLayout {
QQC2.Label {
text: "From: " + modelData.from.name + " Platform: " + modelData.scheduledDeparturePlatform
}
QQC2.Label {
text: modelData.expectedDeparturePlatform
color: modelData.departurePlatformChanged ? Kirigami.Theme.negativeTextColor : Kirigami.Theme.positiveTextColor
visible: modelData.hasExpectedDeparturePlatform
}
}
RowLayout {
QQC2.Label {
text: "Departure: " + modelData.scheduledDepartureTime.toTimeString()
}
QQC2.Label {
text: (modelData.departureDelay >= 0 ? "+" : "") + modelData.departureDelay
color: modelData.departureDelay > 1 ? Kirigami.Theme.negativeTextColor : Kirigami.Theme.positiveTextColor
visible: modelData.hasExpectedDepartureTime
}
}
QQC2.Label {
text: {
switch (modelData.mode) {
case JourneySection.PublicTransport:
return modelData.route.line.modeString + " " + modelData.route.line.name + " " + displayDuration(modelData.duration);
case JourneySection.Walking:
return "Walk " + displayDuration(modelData.duration)
case JourneySection.Transfer:
return "Transfer " + displayDuration(modelData.duration)
case JourneySection.Waiting:
return "Wait " + displayDuration(modelData.duration)
return "???";
}}
}
RowLayout {
QQC2.Label {
text: "To: " + modelData.to.name + " Platform: " + modelData.scheduledArrivalPlatform
}
QQC2.Label {
text: modelData.expectedArrivalPlatform
color: modelData.arrivalPlatformChanged ? Kirigami.Theme.negativeTextColor : Kirigami.Theme.positiveTextColor
visible: modelData.hasExpectedArrivalPlatform
}
}
RowLayout {
QQC2.Label {
text: "Arrival: " + modelData.scheduledArrivalTime.toTimeString()
}
QQC2.Label {
text: (modelData.arrivalDelay >= 0 ? "+" : "") + modelData.arrivalDelay
color: modelData.arrivalDelay > 1 ? Kirigami.Theme.negativeTextColor : Kirigami.Theme.positiveTextColor
visible: modelData.hasExpectedArrivalTime
}
}
}
}
}
}
Component {
id: journeyDelegate
Kirigami.Card {
property var journey: modelData
header: Text { text: journey.scheduledDepartureTime } // TODO proper header
// TODO collapse sections by default, only expand current element
contentItem: ListView {
delegate: sectionDelegate
model: journey.sections
implicitHeight: contentHeight
spacing: Kirigami.Units.smallSpacing
}
// TODO add actions to update to this alternative
}
}
Kirigami.CardsListView {
anchors.fill: parent
clip: true
delegate: journeyDelegate
model: _journeyQueryController.journeys
}
// TODO loading state, error message display
}
...@@ -29,6 +29,28 @@ App.DetailsPage { ...@@ -29,6 +29,28 @@ App.DetailsPage {
property var arrival: _liveDataManager.arrival(batchId) property var arrival: _liveDataManager.arrival(batchId)
property var departure: _liveDataManager.departure(batchId) property var departure: _liveDataManager.departure(batchId)
Component {
id: alternativePage
App.TrainJourneyQueryPage {
batchId: root.batchId
}
}
Component {
id: alternativeAction
Kirigami.Action {
text: i18n("Alternatives")
iconName: "clock"
onTriggered: {
applicationWindow().pageStack.push(alternativePage);
}
}
}
Component.onCompleted: {
actions.contextualActions.push(alternativeAction.createObject(root));
}
Kirigami.FormLayout { Kirigami.FormLayout {
width: root.width width: root.width
Component.onCompleted: Util.fixFormLayoutTouchTransparency(this) Component.onCompleted: Util.fixFormLayoutTouchTransparency(this)
......
/*
Copyright (C) 2019 Volker Krause <vkrause@kde.org>
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU Library General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at your
option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "journeyquerycontroller.h"
#include "logging.h"
#include "reservationmanager.h"
#include <KItinerary/Reservation>
#include <KItinerary/TrainTrip>
#include <KPublicTransport/Manager>
#include <KPublicTransport/Journey>
#include <KPublicTransport/JourneyReply>
#include <KPublicTransport/JourneyRequest>
#include <QDebug>
using namespace KItinerary;
JourneyQueryController::JourneyQueryController(QObject *parent)
: QObject(parent)
{
m_ptMgr.reset(new KPublicTransport::Manager);
}
JourneyQueryController::~JourneyQueryController() = default;
void JourneyQueryController::setReservationManager(ReservationManager *mgr)
{
m_resMgr = mgr;
}
bool JourneyQueryController::isLoading() const
{
return m_isLoading;
}
QString JourneyQueryController::errorMessage() const
{
return m_errorMsg;
}
QVariantList JourneyQueryController::journeys() const
{
QVariantList l;
l.reserve(m_journeys.size());
std::transform(m_journeys.begin(), m_journeys.end(), std::back_inserter(l), [](const auto &journey) { return QVariant::fromValue(journey); });
return l;
}
static KPublicTransport::Location locationFromStation(const TrainStation &station) // TODO share with livedatamanager!!
{
using namespace KPublicTransport;
Location loc;
loc.setName(station.name());
loc.setCoordinate(station.geo().latitude(), station.geo().longitude());
if (!station.identifier().isEmpty()) {
const auto idSplit = station.identifier().split(QLatin1Char(':'));
if (idSplit.size() == 2) {
loc.setIdentifier(idSplit.at(0), idSplit.at(1));
}
}
return loc;
}
void JourneyQueryController::queryJourney(const QString &batchId)
{
qDebug() << batchId;
const auto res = m_resMgr->reservation(batchId);
if (!JsonLd::isA<TrainReservation>(res)) {
return;
}
const auto trip = res.value<TrainReservation>().reservationFor().value<TrainTrip>();
m_errorMsg.clear();
emit errorMessageChanged();
m_isLoading = true;
emit loadingChanged();
const auto from = locationFromStation(trip.departureStation());
const auto to = locationFromStation(trip.arrivalStation());
// TODO consider scheduled time, if in the future
auto reply = m_ptMgr->queryJourney({from, to});
QObject::connect(reply, &KPublicTransport::JourneyReply::finished, [reply, this]{
m_isLoading = false;
emit loadingChanged();
if (reply->error() == KPublicTransport::JourneyReply::NoError) {
m_journeys = reply->takeResult();
emit journeysChanged();
} else {
m_errorMsg = reply->errorString();
emit errorMessageChanged();
}
});
}
/*
Copyright (C) 2019 Volker Krause <vkrause@kde.org>
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU Library General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at your
option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef JOURNEYQUERYCONTROLLER_H
#define JOURNEYQUERYCONTROLLER_H
#include <QObject>
#include <QString>
#include <memory>
#include <vector>
namespace KPublicTransport {
class Journey;
class Manager;
}
class ReservationManager;
/** Alternative train connection query handling. */
class JourneyQueryController : public QObject
{
Q_OBJECT
Q_PROPERTY(bool loading READ isLoading NOTIFY loadingChanged)
Q_PROPERTY(QString errorMessage READ errorMessage NOTIFY errorMessageChanged)
Q_PROPERTY(QVariantList journeys READ journeys NOTIFY journeysChanged)
public:
explicit JourneyQueryController(QObject *parent = nullptr);
~JourneyQueryController();
void setReservationManager(ReservationManager *mgr);
Q_INVOKABLE void queryJourney(const QString &batchId);
bool isLoading() const;
QString errorMessage() const;
QVariantList journeys() const;
Q_SIGNALS:
void loadingChanged();
void errorMessageChanged();
void journeysChanged();
private:
ReservationManager *m_resMgr;
std::unique_ptr<KPublicTransport::Manager> m_ptMgr; // TODO share with LiveDataManager!
bool m_isLoading = false;
QString m_errorMsg;
std::vector<KPublicTransport::Journey> m_journeys;
};
#endif // JOURNEYQUERYCONTROLLER_H
...@@ -22,6 +22,7 @@ ...@@ -22,6 +22,7 @@
#include "brightnessmanager.h" #include "brightnessmanager.h"
#include "countryinformation.h" #include "countryinformation.h"
#include "countrymodel.h" #include "countrymodel.h"
#include "journeyquerycontroller.h"
#include "livedatamanager.h" #include "livedatamanager.h"
#include "localizer.h" #include "localizer.h"
#include "pkpassmanager.h" #include "pkpassmanager.h"
...@@ -40,6 +41,9 @@ ...@@ -40,6 +41,9 @@
#include <KItinerary/CountryDb> #include <KItinerary/CountryDb>
#include <KItinerary/Ticket> #include <KItinerary/Ticket>
#include <KPublicTransport/Journey>
#include <KPublicTransport/Line>
#include <KPkPass/Field> #include <KPkPass/Field>
#include <KPkPass/Barcode> #include <KPkPass/Barcode>
#include <KPkPass/BoardingPass> #include <KPkPass/BoardingPass>
...@@ -143,6 +147,9 @@ int main(int argc, char **argv) ...@@ -143,6 +147,9 @@ int main(int argc, char **argv)
QObject::connect(&settings, &Settings::queryLiveDataChanged, &liveDataMgr, &LiveDataManager::setPollingEnabled); QObject::connect(&settings, &Settings::queryLiveDataChanged, &liveDataMgr, &LiveDataManager::setPollingEnabled);
QObject::connect(&settings, &Settings::allowInsecureServicesChanged, &liveDataMgr, &LiveDataManager::setAllowInsecureServices); QObject::connect(&settings, &Settings::allowInsecureServicesChanged, &liveDataMgr, &LiveDataManager::setAllowInsecureServices);
JourneyQueryController journeyQueryController;
journeyQueryController.setReservationManager(&resMgr);
#ifndef Q_OS_ANDROID #ifndef Q_OS_ANDROID
QObject::connect(&service, &KDBusService::activateRequested, [&parser, &appController](const QStringList &args, const QString &workingDir) { QObject::connect(&service, &KDBusService::activateRequested, [&parser, &appController](const QStringList &args, const QString &workingDir) {
qCDebug(Log) << "remote activation" << args << workingDir; qCDebug(Log) << "remote activation" << args << workingDir;
...@@ -178,6 +185,9 @@ int main(int argc, char **argv) ...@@ -178,6 +185,9 @@ int main(int argc, char **argv)
qmlRegisterUncreatableType<KPkPass::Pass>("org.kde.pkpass", 1, 0, "Pass", {}); qmlRegisterUncreatableType<KPkPass::Pass>("org.kde.pkpass", 1, 0, "Pass", {});
qmlRegisterUncreatableType<KPkPass::BoardingPass>("org.kde.pkpass", 1, 0, "BoardingPass", {}); qmlRegisterUncreatableType<KPkPass::BoardingPass>("org.kde.pkpass", 1, 0, "BoardingPass", {});
qmlRegisterUncreatableType<KPublicTransport::Line>("org.kde.kpublictransport", 1, 0, "Line", {});
qmlRegisterUncreatableType<KPublicTransport::JourneySection>("org.kde.kpublictransport", 1, 0, "JourneySection", {});
qRegisterMetaType<KItinerary::KnowledgeDb::DrivingSide>(); qRegisterMetaType<KItinerary::KnowledgeDb::DrivingSide>();
qmlRegisterUncreatableType<KItinerary::Ticket>("org.kde.kitinerary", 1, 0, "Ticket", {}); qmlRegisterUncreatableType<KItinerary::Ticket>("org.kde.kitinerary", 1, 0, "Ticket", {});
qmlRegisterUncreatableMetaObject(KItinerary::KnowledgeDb::staticMetaObject, "org.kde.kitinerary", 1, 0, "KnowledgeDb", {}); qmlRegisterUncreatableMetaObject(KItinerary::KnowledgeDb::staticMetaObject, "org.kde.kitinerary", 1, 0, "KnowledgeDb", {});
...@@ -205,6 +215,7 @@ int main(int argc, char **argv) ...@@ -205,6 +215,7 @@ int main(int argc, char **argv)
engine.rootContext()->setContextProperty(QStringLiteral("_weatherForecastManager"), &weatherForecastMgr); engine.rootContext()->setContextProperty(QStringLiteral("_weatherForecastManager"), &weatherForecastMgr);
engine.rootContext()->setContextProperty(QStringLiteral("_brightnessManager"), &brightnessManager); engine.rootContext()->setContextProperty(QStringLiteral("_brightnessManager"), &brightnessManager);
engine.rootContext()->setContextProperty(QStringLiteral("_liveDataManager"), &liveDataMgr); engine.rootContext()->setContextProperty(QStringLiteral("_liveDataManager"), &liveDataMgr);
engine.rootContext()->setContextProperty(QStringLiteral("_journeyQueryController"), &journeyQueryController);
engine.load(QStringLiteral("qrc:/main.qml")); engine.load(QStringLiteral("qrc:/main.qml"));
handlePositionalArguments(&appController, parser.positionalArguments()); handlePositionalArguments(&appController, parser.positionalArguments());
......
...@@ -38,6 +38,7 @@ ...@@ -38,6 +38,7 @@
<file>TouristAttractionDelegate.qml</file> <file>TouristAttractionDelegate.qml</file>
<file>TouristAttractionPage.qml</file> <file>TouristAttractionPage.qml</file>
<file>TrainDelegate.qml</file> <file>TrainDelegate.qml</file>
<file>TrainJourneyQueryPage.qml</file>
<file>TrainPage.qml</file> <file>TrainPage.qml</file>
<file>TripGroupDelegate.qml</file> <file>TripGroupDelegate.qml</file>
<file>WeatherForecastDelegate.qml</file> <file>WeatherForecastDelegate.qml</file>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment