diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index 4114274..bf11e06 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -1,109 +1,110 @@ set(itinerary_srcs countryinformation.cpp pkpassmanager.cpp reservationmanager.cpp timelinemodel.cpp ) ecm_qt_declare_logging_category(itinerary_srcs HEADER logging.h IDENTIFIER Log CATEGORY_NAME org.kde.itinerary ) add_library(itinerary STATIC ${itinerary_srcs}) target_link_libraries(itinerary PUBLIC itinerary-weather KPim::Itinerary KPim::PkPass KF5::I18n Qt5::Network ) if (Qt5QuickCompiler_FOUND) qtquick_compiler_add_resources(qml_srcs qml.qrc) else () set(qml_srcs qml.qrc) endif() add_executable(itinerary-app main.cpp applicationcontroller.cpp countrymodel.cpp localizer.cpp pkpassimageprovider.cpp settings.cpp tickettokenmodel.cpp util.cpp + weatherforecastmodel.cpp ${qml_srcs} ) target_include_directories(itinerary-app PRIVATE ${CMAKE_BINARY_DIR}) target_link_libraries(itinerary-app PRIVATE itinerary Qt5::Quick KF5::Contacts ) if (ANDROID) # explicitly add runtime dependencies and transitive link dependencies, # so androiddeployqt picks them up target_link_libraries(itinerary-app PRIVATE KF5::Archive KF5::Kirigami2 Qt5::AndroidExtras Qt5::Svg KF5::Prison OpenSSL::SSL ) kirigami_package_breeze_icons(ICONS checkmark dialog-cancel document-edit document-open document-save edit-delete edit-download go-home go-next-symbolic help-about map-symbolic meeting-attending settings-configure view-calendar-day view-refresh weather-clear weather-clear-night weather-few-clouds weather-few-clouds-night weather-clouds weather-clouds-night weather-showers-day weather-showers-night weather-showers-scattered-day weather-showers-scattered-night weather-snow-scattered-day weather-snow-scattered-night weather-storm-day weather-storm-night weather-many-clouds weather-fog weather-showers weather-showers-scattered weather-hail weather-snow weather-snow-scattered weather-storm ) else () target_link_libraries(itinerary-app PRIVATE KF5::DBusAddons Qt5::Positioning Qt5::Widgets ) set_target_properties(itinerary-app PROPERTIES OUTPUT_NAME "itinerary") endif() install(TARGETS itinerary-app ${INSTALL_TARGETS_DEFAULT_ARGS}) if (NOT ANDROID) install(PROGRAMS org.kde.itinerary.desktop DESTINATION ${KDE_INSTALL_APPDIR}) install(FILES org.kde.itinerary.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR}) endif() diff --git a/src/app/WeatherForecastDelegate.qml b/src/app/WeatherForecastDelegate.qml index 508cec9..6e59bc3 100644 --- a/src/app/WeatherForecastDelegate.qml +++ b/src/app/WeatherForecastDelegate.qml @@ -1,64 +1,74 @@ /* Copyright (C) 2018 Volker Krause 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 . */ 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.itinerary 1.0 import "." as App Kirigami.AbstractCard { id: root property var weatherForecast visible: weatherForecast.valid headerOrientation: Qt.Horizontal + showClickFeedback: true header: Rectangle { id: headerBackground Kirigami.Theme.colorSet: Kirigami.Theme.Complementary Kirigami.Theme.inherit: false color: Kirigami.Theme.backgroundColor implicitWidth: icon.implicitWidth + Kirigami.Units.largeSpacing * 2 Layout.minimumHeight: implicitWidth Layout.fillHeight: true anchors.leftMargin: -root.leftPadding anchors.topMargin: -root.topPadding anchors.bottomMargin: -root.rightPadding Kirigami.Icon { id: icon anchors.fill: parent anchors.margins: Kirigami.Units.largeSpacing source: weatherForecast.symbolIconName } } contentItem: ColumnLayout { Layout.fillWidth: true QQC2.Label { text: i18n("Temperature: %1 °C / %2 °C", weatherForecast.minimumTemperature, weatherForecast.maximumTemperature) color: Kirigami.Theme.textColor Layout.fillWidth: true } QQC2.Label { text: i18n("Precipitation: %1 mm", weatherForecast.precipitation) color: Kirigami.Theme.textColor Layout.fillWidth: true } } + + Component { + id: detailsComponent + App.WeatherForecastPage { + weatherForecast: root.weatherForecast + } + } + + onClicked: applicationWindow().pageStack.push(detailsComponent); } diff --git a/src/app/WeatherForecastPage.qml b/src/app/WeatherForecastPage.qml new file mode 100644 index 0000000..c235396 --- /dev/null +++ b/src/app/WeatherForecastPage.qml @@ -0,0 +1,58 @@ +/* + Copyright (C) 2018 Volker Krause + + 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 . +*/ + +import QtQuick 2.5 +import QtQuick.Layouts 1.1 +import QtQuick.Controls 2.1 as QQC2 +import org.kde.kirigami 2.0 as Kirigami +import org.kde.kitinerary 1.0 +import org.kde.itinerary 1.0 +import "." as App + +Kirigami.Page { + id: root + property alias weatherForecast: forecastModel.weatherForecast + title: i18n("Weather Forecast") + + WeatherForecastModel { + id: forecastModel + weatherForecastManager: _weatherForecastManager + } + + ListView { + anchors.fill: parent + id: forecastList + model: forecastModel + delegate: Kirigami.BasicListItem { + icon: model.weatherForecast.symbolIconName + label: { + var fc = model.weatherForecast; + if (fc.maximumTemperature == fc.minimumTemperature) { + if (fc.precipitation == 0) + return i18n("%1 %2°C", model.localizedTime, fc.maximumTemperature); + else + return i18n("%1 %2°C ☂ %3mm", model.localizedTime, fc.maximumTemperature, fc.precipitation); + } + if (fc.precipitation == 0) + return i18n("%1 %2°C / %3°C", model.localizedTime, fc.minimumTemperature, fc.maximumTemperature); + return i18n("%1 %2°C / %3°C ☂ %4 mm", model.localizedTime, model.weatherForecast.minimumTemperature, model.weatherForecast.maximumTemperature, model.weatherForecast.precipitation); + } + } + } + + onBackRequested: pageStack.pop() +} diff --git a/src/app/main.cpp b/src/app/main.cpp index dc42a38..fe3db42 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -1,179 +1,182 @@ /* Copyright (C) 2018 Volker Krause 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 . */ #include "itinerary_version.h" #include "logging.h" #include "applicationcontroller.h" #include "countryinformation.h" #include "countrymodel.h" #include "localizer.h" #include "pkpassmanager.h" #include "timelinemodel.h" #include "pkpassimageprovider.h" #include "reservationmanager.h" #include "settings.h" #include "tickettokenmodel.h" #include "util.h" +#include "weatherforecastmodel.h" #include #include #include #include #include #ifndef Q_OS_ANDROID #include #endif #include #include #include #include #ifdef Q_OS_ANDROID #include #include #else #include #endif #include #include #include #include #include #include void handleViewIntent(ApplicationController *appController) { #ifdef Q_OS_ANDROID // handle opened files const auto activity = QtAndroid::androidActivity(); if (!activity.isValid()) return; const auto intent = activity.callObjectMethod("getIntent", "()Landroid/content/Intent;"); appController->importFromIntent(intent); #else Q_UNUSED(appController); #endif } #ifdef Q_OS_ANDROID Q_DECL_EXPORT #endif int main(int argc, char **argv) { QCoreApplication::setApplicationName(QStringLiteral("kde-itinerary")); QCoreApplication::setOrganizationName(QStringLiteral("KDE")); QCoreApplication::setOrganizationDomain(QStringLiteral("kde.org")); QCoreApplication::setApplicationVersion(QStringLiteral(ITINERARY_VERSION_STRING)); QGuiApplication::setApplicationDisplayName(i18n("KDE Itinerary")); QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QGuiApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); #ifdef Q_OS_ANDROID QGuiApplication app(argc, argv); #else QApplication app(argc, argv); // for native file dialogs #endif QGuiApplication::setWindowIcon(QIcon::fromTheme(QStringLiteral("map-globe"))); QCommandLineParser parser; parser.addHelpOption(); parser.addVersionOption(); parser.addPositionalArgument(QStringLiteral("file"), i18n("PkPass or JSON-LD file to import.")); parser.process(app); #ifndef Q_OS_ANDROID KDBusService service(KDBusService::Unique); #endif Settings settings; PkPassManager passMgr; ReservationManager resMgr; resMgr.setPkPassManager(&passMgr); ApplicationController appController; appController.setReservationManager(&resMgr); appController.setPkPassManager(&passMgr); #ifndef Q_OS_ANDROID QObject::connect(&service, &KDBusService::activateRequested, [&parser, &appController](const QStringList &args, const QString &workingDir) { qCDebug(Log) << "remote activation" << args << workingDir; if (!args.isEmpty()) { QDir::setCurrent(workingDir); parser.parse(args); for (const auto &file : parser.positionalArguments()) { appController.importLocalFile(QUrl::fromLocalFile(file)); } } if (!QGuiApplication::allWindows().isEmpty()) { QGuiApplication::allWindows().at(0)->requestActivate(); } }); #endif TimelineModel timelineModel; timelineModel.setHomeCountryIsoCode(settings.homeCountryIsoCode()); timelineModel.setReservationManager(&resMgr); QObject::connect(&settings, &Settings::homeCountryIsoCodeChanged, &timelineModel, &TimelineModel::setHomeCountryIsoCode); WeatherForecastManager weatherForecastMgr; weatherForecastMgr.setAllowNetworkAccess(settings.weatherForecastEnabled()); QObject::connect(&settings, &Settings::weatherForecastEnabledChanged, &weatherForecastMgr, &WeatherForecastManager::setAllowNetworkAccess); timelineModel.setWeatherForecastManager(&weatherForecastMgr); qmlRegisterUncreatableType("org.kde.pkpass", 1, 0, "Barcode", {}); qmlRegisterUncreatableType("org.kde.pkpass", 1, 0, "Field", {}); qRegisterMetaType(); qmlRegisterUncreatableType("org.kde.kitinerary", 1, 0, "Ticket", {}); qmlRegisterUncreatableMetaObject(KItinerary::KnowledgeDb::staticMetaObject, "org.kde.kitinerary", 1, 0, "KnowledgeDb", {}); qmlRegisterUncreatableType("org.kde.itinerary", 1, 0, "CountryInformation", {}); qmlRegisterType("org.kde.itinerary", 1, 0, "CountryModel"); qmlRegisterSingletonType("org.kde.itinerary", 1, 0, "Localizer", [](QQmlEngine*, QJSEngine*) -> QObject*{ return new Localizer; }); qmlRegisterType("org.kde.itinerary", 1, 0, "TicketTokenModel"); qmlRegisterUncreatableType("org.kde.itinerary", 1, 0, "TimelineModel", {}); qmlRegisterSingletonType("org.kde.itinerary", 1, 0, "Util", [](QQmlEngine*, QJSEngine*) -> QObject*{ return new Util; }); + qmlRegisterType("org.kde.itinerary", 1, 0, "WeatherForecastModel"); QQmlApplicationEngine engine; engine.addImageProvider(QStringLiteral("org.kde.pkpass"), new PkPassImageProvider(&passMgr)); engine.rootContext()->setContextObject(new KLocalizedContext(&engine)); engine.rootContext()->setContextProperty(QStringLiteral("_pkpassManager"), &passMgr); engine.rootContext()->setContextProperty(QStringLiteral("_reservationManager"), &resMgr); engine.rootContext()->setContextProperty(QStringLiteral("_timelineModel"), &timelineModel); engine.rootContext()->setContextProperty(QStringLiteral("_appController"), &appController); engine.rootContext()->setContextProperty(QStringLiteral("_settings"), &settings); + engine.rootContext()->setContextProperty(QStringLiteral("_weatherForecastManager"), &weatherForecastMgr); engine.load(QStringLiteral("qrc:/main.qml")); for (const auto &file : parser.positionalArguments()) { appController.importLocalFile(QUrl::fromLocalFile(file)); } handleViewIntent(&appController); return app.exec(); } diff --git a/src/app/qml.qrc b/src/app/qml.qrc index 5963785..81f13d0 100644 --- a/src/app/qml.qrc +++ b/src/app/qml.qrc @@ -1,47 +1,48 @@ qtquickcontrols2.conf main.qml AboutPage.qml BoardingPass.qml BusDelegate.qml BusPage.qml CountryInfoDelegate.qml DateInput.qml DateTimeEdit.qml DetailsPage.qml EditorPage.qml EventDelegate.qml EventPage.qml FlightDelegate.qml FlightEditor.qml FlightPage.qml HotelDelegate.qml HotelPage.qml ImportDialog.qml PkPassPage.qml PlaceDelegate.qml PlaceEditor.qml RestaurantDelegate.qml RestaurantEditor.qml RestaurantPage.qml SettingsPage.qml TicketTokenDelegate.qml TimeInput.qml TimelineDelegate.qml TimelinePage.qml TouristAttractionDelegate.qml TouristAttractionPage.qml TrainDelegate.qml TrainPage.qml WeatherForecastDelegate.qml + WeatherForecastPage.qml +android/ImportDialog.qml images/bus.svg images/flight.svg images/foodestablishment.svg images/train.svg diff --git a/src/app/weatherforecastmodel.cpp b/src/app/weatherforecastmodel.cpp new file mode 100644 index 0000000..214b0cf --- /dev/null +++ b/src/app/weatherforecastmodel.cpp @@ -0,0 +1,87 @@ +/* + Copyright (C) 2018 Volker Krause + + 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 . +*/ + +#include "weatherforecastmodel.h" + +#include + +#include +#include + +WeatherForecastModel::WeatherForecastModel(QObject* parent) + : QAbstractListModel(parent) +{ +} + +WeatherForecastModel::~WeatherForecastModel() = default; + +QObject* WeatherForecastModel::weatherForecastManager() const +{ + return m_mgr; +} + +void WeatherForecastModel::setWeatherForecastManager(QObject* mgr) +{ + beginResetModel(); + m_mgr = qobject_cast(mgr); + endResetModel(); +} + +QVariant WeatherForecastModel::weatherForecast() const +{ + return QVariant::fromValue(m_fc); +} + +void WeatherForecastModel::setWeatherForecast(const QVariant& fc) +{ + beginResetModel(); + m_fc = fc.value(); + endResetModel(); +} + +int WeatherForecastModel::rowCount(const QModelIndex& parent) const +{ + if (parent.isValid() || !m_mgr) + return 0; + return m_fc.range(); +} + +QVariant WeatherForecastModel::data(const QModelIndex& index, int role) const +{ + if (!index.isValid() || !m_mgr || !m_fc.isValid()) + return {}; + + switch (role) { + case WeatherForecastRole: + { + const auto fc = m_mgr->forecast(m_fc.tile().latitude(), m_fc.tile().longitude(), m_fc.dateTime().addSecs(index.row() * 3600)); + return QVariant::fromValue(fc); + } + case LocalizedTimeRole: + return QLocale().toString(m_fc.dateTime().addSecs(index.row() * 3600).toLocalTime().time(), QLocale::ShortFormat); + } + + return {}; +} + +QHash WeatherForecastModel::roleNames() const +{ + auto names = QAbstractListModel::roleNames(); + names.insert(WeatherForecastRole, "weatherForecast"); + names.insert(LocalizedTimeRole, "localizedTime"); + return names; +} diff --git a/src/app/weatherforecastmodel.h b/src/app/weatherforecastmodel.h new file mode 100644 index 0000000..06d9cd1 --- /dev/null +++ b/src/app/weatherforecastmodel.h @@ -0,0 +1,56 @@ +/* + Copyright (C) 2018 Volker Krause + + 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 . +*/ + +#ifndef WEATHERFORECASTMODEL_H +#define WEATHERFORECASTMODEL_H + +#include + +#include + +class WeatherForecastManager; + +/** Weather forecast details page model. */ +class WeatherForecastModel : public QAbstractListModel +{ + Q_OBJECT + Q_PROPERTY(QObject* weatherForecastManager READ weatherForecastManager WRITE setWeatherForecastManager) + Q_PROPERTY(QVariant weatherForecast READ weatherForecast WRITE setWeatherForecast) +public: + enum Roles { + WeatherForecastRole = Qt::UserRole, + LocalizedTimeRole + }; + + explicit WeatherForecastModel(QObject *parent = nullptr); + ~WeatherForecastModel(); + + int rowCount(const QModelIndex &parent) const override; + QVariant data(const QModelIndex &index, int role) const override; + QHash roleNames() const override; + + QObject* weatherForecastManager() const; + void setWeatherForecastManager(QObject *mgr); + QVariant weatherForecast() const; + void setWeatherForecast(const QVariant &fc); + +private: + WeatherForecastManager *m_mgr = nullptr; + WeatherForecast m_fc; +}; + +#endif // WEATHERFORECASTMODEL_H