Warning, /pim/itinerary/src/app/main.qml is written in an unsupported language. File is not indexed.
0001 /*
0002 SPDX-FileCopyrightText: 2018 Volker Krause <vkrause@kde.org>
0003
0004 SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006
0007 import QtCore as QtCore
0008 import QtQuick
0009 import QtQuick.Layouts
0010 import QtQuick.Controls as QQC2
0011 import QtQuick.Dialogs
0012 import QtLocation as QtLocation
0013 import org.kde.kirigami as Kirigami
0014 import org.kde.solidextras as Solid
0015 import org.kde.kpublictransport.onboard
0016 import internal.org.kde.kcalendarcore as KCalendarCore
0017 import org.kde.itinerary
0018
0019 Kirigami.ApplicationWindow {
0020 id: root
0021 title: i18n("KDE Itinerary")
0022
0023 width: Kirigami.Settings.isMobile ? 480 : 800
0024 height: Kirigami.Settings.isMobile ? 720 : 650
0025 minimumWidth: 300
0026 minimumHeight: 400
0027 Kirigami.PagePool {
0028 id: pagepool
0029 }
0030 pageStack {
0031 columnView.columnResizeMode: Kirigami.ColumnView.SingleColumn
0032
0033 globalToolBar {
0034 style: Kirigami.Settings.isMobile ? Kirigami.ApplicationHeaderStyle.Breadcrumb : Kirigami.ApplicationHeaderStyle.Auto
0035 showNavigationButtons: Kirigami.ApplicationHeaderStyle.ShowBackButton
0036 }
0037 }
0038
0039 // pop pages when not in use
0040 Connections {
0041 target: applicationWindow().pageStack
0042 function onCurrentIndexChanged() {
0043 // wait for animation to finish before popping pages
0044 timer.restart();
0045 }
0046 }
0047
0048 Timer {
0049 id: timer
0050 interval: 300
0051 onTriggered: {
0052 let currentIndex = applicationWindow().pageStack.currentIndex;
0053 while (applicationWindow().pageStack.depth > (currentIndex + 1) && currentIndex >= 0) {
0054 applicationWindow().pageStack.pop();
0055 }
0056 }
0057 }
0058
0059 property list<Kirigami.Action> importActions: [
0060 Kirigami.Action {
0061 text: i18n("Open File...")
0062 icon.name: "document-open"
0063 shortcut: StandardKey.Open
0064 onTriggered: {
0065 importFileDialog.open();
0066 }
0067 },
0068 Kirigami.Action {
0069 text: i18n("Paste")
0070 icon.name: "edit-paste"
0071 enabled: ApplicationController.hasClipboardContent
0072 shortcut: StandardKey.Paste
0073 onTriggered: {
0074 ApplicationController.importFromClipboard();
0075 }
0076 },
0077 Kirigami.Action {
0078 text: i18n("Scan Barcode...")
0079 icon.name: "view-barcode-qr"
0080 onTriggered: {
0081 pageStack.layers.push(scanBarcodeComponent);
0082 }
0083 },
0084 Kirigami.Action {
0085 icon.name: "view-calendar-day"
0086 text: i18n("Add from Calendar...")
0087 onTriggered: {
0088 PermissionManager.requestPermission(Permission.ReadCalendar, function() {
0089 if (!calendarSelector.model) {
0090 calendarSelector.model = calendarModel.createObject(root);
0091 }
0092 calendarSelector.open();
0093 })
0094 }
0095 visible: KCalendarCore.CalendarPluginLoader.hasPlugin
0096 },
0097 // TODO this should not be hardcoded here, but dynamically filled based on what online ticket
0098 // sources we support
0099 Kirigami.Action {
0100 text: i18n("Deutsche Bahn Online Ticket...")
0101 icon.name: "download"
0102 onTriggered: {
0103 pageStack.layers.push(Qt.createComponent("org.kde.itinerary", "OnlineImportPage"), {
0104 source: "db",
0105 });
0106 }
0107 },
0108 Kirigami.Action {
0109 text: i18n("SNCF Online Ticket...")
0110 icon.name: "download"
0111 onTriggered: {
0112 pageStack.layers.push(Qt.createComponent("org.kde.itinerary", "OnlineImportPage"), {
0113 source: "sncf"
0114 });
0115 }
0116 }
0117 ]
0118
0119 FileDialog {
0120 id: importFileDialog
0121 fileMode: FileDialog.OpenFile
0122 title: i18n("Import Reservation")
0123 currentFolder: QtCore.StandardPaths.writableLocation(QtCore.StandardPaths.DocumentsLocation)
0124 // Android has no file type selector, we get the superset of all filters there since Qt6 (apart from "all"),
0125 // so don't set any filters on Android in order to be able to open everything we can read
0126 nameFilters: Qt.platform.os === "android" ?
0127 [i18n("All Files (*.*)")] :
0128 [i18n("All Files (*.*)"), i18n("PkPass files (*.pkpass)"), i18n("PDF files (*.pdf)"), i18n("iCal events (*.ics)"), i18n("KDE Itinerary files (*.itinerary)")]
0129 onAccepted: ApplicationController.importFromUrl(selectedFile)
0130 }
0131
0132 FileDialog {
0133 id: exportDialog
0134 fileMode: FileDialog.SaveFile
0135 title: i18n("Export Itinerary Data")
0136 currentFolder: QtCore.StandardPaths.writableLocation(QtCore.StandardPaths.DocumentsLocation)
0137 nameFilters: [i18n("KDE Itinerary files (*.itinerary)")]
0138 onAccepted: ApplicationController.exportToFile(selectedFile)
0139 }
0140
0141 Component {
0142 id: calendarModel
0143 // needs to be created on demand, after we have calendar access permissions
0144 KCalendarCore.CalendarListModel {}
0145 }
0146 Component {
0147 id: calendarImportPage
0148 CalendarImportPage {}
0149 }
0150 footer: NavigationBar {
0151 id: navigationbar
0152 }
0153 CalendarSelectionSheet {
0154 id: calendarSelector
0155 // parent: root.Overlay.overlay
0156 onCalendarSelected: pageStack.push(calendarImportPage, { calendar: calendar });
0157 }
0158
0159 OnboardStatus {
0160 id: onboardStatus
0161 }
0162
0163 globalDrawer: Kirigami.GlobalDrawer {
0164 title: i18n("KDE Itinerary")
0165 titleIcon: "map-symbolic"
0166 isMenu: true
0167 actions: [
0168 Kirigami.Action {
0169 text: i18n("Import...")
0170 icon.name: "document-import"
0171 children: importActions
0172 },
0173 Kirigami.Action {
0174 text: i18n("Check for Updates")
0175 icon.name: "view-refresh"
0176 enabled: Solid.NetworkStatus.connectivity != Solid.NetworkStatus.No
0177 shortcut: StandardKey.Refresh
0178 onTriggered: {
0179 LiveDataManager.checkForUpdates();
0180 }
0181 },
0182 Kirigami.Action {
0183 text: i18n("Download Maps")
0184 icon.name: "download"
0185 enabled: Solid.NetworkStatus.connectivity != Solid.NetworkStatus.No
0186 icon.color: Solid.NetworkStatus.metered != Solid.NetworkStatus.No ? Kirigami.Theme.neutralTextColor : Kirigami.Theme.textColor
0187 onTriggered: MapDownloadManager.download();
0188 },
0189 Kirigami.Action {
0190 id: statsAction
0191 text: i18n("Statistics")
0192 icon.name: "view-statistics"
0193 enabled: pageStack.layers.depth < 2
0194 onTriggered: pageStack.layers.push(statisticsComponent)
0195 },
0196 Kirigami.Action {
0197 id: healthCertAction
0198 text: i18n("Health Certificates")
0199 icon.name: "cross-shape"
0200 onTriggered: {
0201 healtCertificateComponent.source = Qt.resolvedUrl("HealthCertificatePage.qml")
0202 pageStack.layers.push(healtCertificateComponent.item)
0203 }
0204 enabled: pageStack.layers.depth < 2
0205 visible: ApplicationController.hasHealthCertificateSupport && ApplicationController.healthCertificateManager.rowCount() > 0
0206 },
0207 Kirigami.Action {
0208 id: liveAction
0209 text: i18n("Live Status")
0210 icon.name: "media-playback-playing"
0211 onTriggered: pageStack.layers.push(Qt.createComponent("org.kde.itinerary", "LiveStatusPage"))
0212 enabled: pageStack.layers.depth < 2
0213 visible: onboardStatus.status == OnboardStatus.Onboard
0214 },
0215 Kirigami.Action {
0216 id: settingsAction
0217 text: i18n("Settings...")
0218 icon.name: "settings-configure"
0219 enabled: pageStack.layers.depth < 2
0220 shortcut: StandardKey.Preferences
0221 onTriggered: pageStack.layers.push(Qt.createComponent("org.kde.itinerary", "SettingsPage"))
0222 },
0223 Kirigami.Action {
0224 text: i18n("Export...")
0225 icon.name: "export-symbolic"
0226 onTriggered: exportDialog.open()
0227 },
0228 Kirigami.Action {
0229 text: i18n("Help")
0230 icon.name: "help-contents"
0231 enabled: pageStack.layers.depth < 2
0232 onTriggered: pageStack.layers.push(Qt.createComponent("org.kde.itinerary", "WelcomePage"))
0233 },
0234 Kirigami.Action {
0235 id: aboutAction
0236 text: i18n("About")
0237 icon.name: "help-about-symbolic"
0238 enabled: pageStack.layers.depth < 2
0239 onTriggered: pageStack.pushDialogLayer(Qt.resolvedUrl("AboutPage.qml"))
0240 },
0241 Kirigami.Action {
0242 id: devModeAction
0243 text: "Development"
0244 icon.name: "tools-report-bug"
0245 enabled: pageStack.layers.depth < 2
0246 onTriggered: pageStack.layers.push(Qt.createComponent("org.kde.itinerary", "DevelopmentModePage"));
0247 visible: Settings.developmentMode
0248 }
0249 ]
0250 }
0251 contextDrawer: Kirigami.ContextDrawer {
0252 id: contextDrawer
0253 actions: {
0254 if (pageStack.layers.depth > 1)
0255 return (pageStack.layers.currentItem as Kirigami.Page)?.actions ?? [];
0256 else (pageStack.currentItem as Kirigami.Page)?.actions ?? [];
0257 }
0258 }
0259 pageStack.initialPage: pagepool.loadPage(Qt.resolvedUrl("TimelinePage.qml"))
0260
0261 DropArea {
0262 id: topDropArea
0263 onEntered: {
0264 if (!drag.hasUrls) {
0265 drag.accepted = false;
0266 }
0267 }
0268 onDropped: {
0269 for (var i in drop.urls) {
0270 ApplicationController.importFromUrl(drop.urls[i]);
0271 }
0272 }
0273 anchors.fill: parent
0274 }
0275
0276 Connections {
0277 target: ApplicationController
0278 function onInfoMessage(msg) { showPassiveNotification(msg, "short"); }
0279 function onOpenPageRequested(page) {
0280 while (pageStack.layers.depth > 1) {
0281 pageStack.layers.pop();
0282 }
0283 switch (page) {
0284 case "currentTicket":
0285 pagepool.loadPage("TimelinePage.qml").showDetailsPageForReservation(TimelineModel.currentBatchId);
0286 break;
0287 case "stats":
0288 statsAction.trigger();
0289 break;
0290 case "healthCert":
0291 healthCertAction.trigger();
0292 break;
0293 case "live":
0294 liveAction.trigger();
0295 break;
0296 default:
0297 console.warn("Requested to open unknown page", page);
0298 }
0299 }
0300 }
0301
0302 Connections {
0303 target: MapDownloadManager
0304 function onFinished() { showPassiveNotification(i18n("Map download finished."), "short"); }
0305 }
0306
0307 Component.onCompleted: {
0308 if (ReservationManager.isEmpty()) {
0309 pageStack.push(Qt.createComponent("org.kde.itinerary", "WelcomePage"));
0310 }
0311 }
0312
0313 Component {
0314 id: mainPageComponent
0315 TimelinePage {}
0316 }
0317 Component {
0318 id: scanBarcodeComponent
0319 BarcodeScannerPage {}
0320 }
0321 Component {
0322 id: statisticsComponent
0323 StatisticsPage {
0324 reservationManager: ReservationManager
0325 tripGroupManager: TripGroupManager
0326 }
0327 }
0328 Component {
0329 id: passComponent
0330 PassPage {}
0331 }
0332 // replace loader with component once we depend on KHealthCertificate unconditionally
0333 Loader {
0334 id: healtCertificateComponent
0335 }
0336 Component {
0337 id: journeySectionPage
0338 JourneySectionPage {}
0339 }
0340 Component {
0341 id: journeyPathPage
0342 JourneyPathPage {}
0343 }
0344 Component {
0345 id: indoorMapPage
0346 IndoorMapPage {}
0347 }
0348
0349 // "singleton" OSM QtLocation plugin
0350 // we only want one of these, and created only when absolutely necessary
0351 // as this triggers network operations on creation already
0352 function osmPlugin() {
0353 if (!__qtLocationOSMPlugin) {
0354 __qtLocationOSMPlugin = __qtLocationOSMPluginComponent.createObject();
0355 }
0356 return __qtLocationOSMPlugin;
0357 }
0358 property var __qtLocationOSMPlugin: null
0359 Component {
0360 id: __qtLocationOSMPluginComponent
0361 QtLocation.Plugin {
0362 name: "osm"
0363 QtLocation.PluginParameter { name: "osm.useragent"; value: ApplicationController.userAgent }
0364 QtLocation.PluginParameter { name: "osm.mapping.providersrepository.address"; value: "https://autoconfig.kde.org/qtlocation/" }
0365 }
0366 }
0367
0368 // workaround for Back key handling on Android causing the application to close
0369 // on secondary layers if those have no focus (either explicitly or via interaction)
0370 // ### if there isn't a proepr fix for this, should this happen in Kirigami instead?
0371 Connections {
0372 target: pageStack.layers
0373 function onCurrentItemChanged() {
0374 pageStack.layers.currentItem.forceActiveFocus();
0375 }
0376 }
0377 }