Warning, /utilities/kweather/src/qml/DynamicForecastPage.qml is written in an unsupported language. File is not indexed.
0001 /* 0002 * SPDX-FileCopyrightText: 2020 Han Young <hanyoung@protonmail.com> 0003 * SPDX-FileCopyrightText: 2020-2021 Devin Lin <espidev@gmail.com> 0004 * SPDX-FileCopyrightText: 2021 Nicolas Fella <nicolas.fella@gmx.de> 0005 * 0006 * SPDX-License-Identifier: GPL-2.0-or-later 0007 */ 0008 0009 import QtQuick 0010 import QtQuick.Controls 0011 import QtQuick.Layouts 0012 0013 import org.kde.kirigami as Kirigami 0014 0015 import org.kde.kweather.backgrounds 0016 import org.kde.kweather.backgrounds.components 0017 0018 import org.kde.kweather 0019 0020 Kirigami.ScrollablePage { 0021 id: page 0022 0023 property int currentIndex: 0 0024 property var weatherLocation: WeatherLocationListModel.locations[page.currentIndex] 0025 property var selectedDay: dailyListView.currentItem ? dailyListView.currentItem.weather : weatherLocation.dayForecasts[0] 0026 0027 property int maximumContentWidth: Kirigami.Units.gridUnit * 35 0028 0029 // HACK: disable this scrollable when needed because it steals events from dialogs 0030 flickable.interactive: !applicationWindow().isDialogOpen 0031 0032 // x-drag threshold to change page 0033 property real pageChangeThreshold: page.width / 4 0034 0035 // page functions 0036 property bool canGoLeft: currentIndex > 0 0037 property bool canGoRight: currentIndex < WeatherLocationListModel.count - 1 0038 function moveLeft() { 0039 if (page.canGoLeft) { 0040 xOutAnim.goLeft = true; 0041 xOutAnim.to = pageChangeThreshold; 0042 xOutAnim.restart(); 0043 } 0044 } 0045 function moveRight() { 0046 if (page.canGoRight) { 0047 xOutAnim.goLeft = false; 0048 xOutAnim.to = -pageChangeThreshold; 0049 xOutAnim.restart(); 0050 } 0051 } 0052 function finishMoveLeft() { 0053 if (page.canGoLeft) { 0054 --currentIndex; 0055 // animate as if new cards are coming in from the screen side 0056 rootMask.x = -pageChangeThreshold; 0057 xAnim.restart(); 0058 } 0059 } 0060 function finishMoveRight() { 0061 if (page.canGoRight) { 0062 ++currentIndex; 0063 // animate as if new cards are coming in from the screen side 0064 rootMask.x = pageChangeThreshold; 0065 xAnim.restart(); 0066 } 0067 } 0068 0069 Connections { 0070 target: WeatherLocationListModel 0071 0072 function onLocationsChanged() { 0073 if (page.currentIndex >= WeatherLocationListModel.count) { 0074 page.currentIndex = WeatherLocationListModel.count - 1; 0075 } 0076 } 0077 } 0078 0079 // animate x fade out before page switch 0080 NumberAnimation { 0081 id: xOutAnim 0082 target: rootMask 0083 property: "x" 0084 easing.type: Easing.InOutQuad 0085 duration: Kirigami.Units.longDuration 0086 0087 property bool goLeft: false 0088 0089 onFinished: { 0090 goLeft ? finishMoveLeft() : finishMoveRight(); 0091 } 0092 } 0093 0094 // background 0095 background: backgroundQml.item // disable OpenGL background for now as it causes issues: backgroundGL.item 0096 0097 // Loader { 0098 // id: backgroundGL 0099 // active: !KWEATHER_IS_ANDROID 0100 // sourceComponent: WeatherBackground { 0101 // anchors.fill: parent 0102 // rain: weatherLocation.rain 0103 // cloud: weatherLocation.cloud 0104 // sun: weatherLocation.sun 0105 // star: weatherLocation.star 0106 // snow: weatherLocation.snow 0107 // colorTop: weatherLocation.topColor 0108 // colorBottom: weatherLocation.bottomColor 0109 // cloudColor: weatherLocation.cloudColor 0110 // } 0111 // } 0112 0113 Loader { 0114 id: backgroundQml 0115 active: true 0116 sourceComponent: Rectangle { 0117 anchors.fill: parent 0118 color: "#24a3de" 0119 0120 // background colours 0121 gradient: Gradient { 0122 GradientStop { 0123 color: backgroundLoader.item ? backgroundLoader.item.gradientColorTop : "white" 0124 position: 0.0 0125 Behavior on color { 0126 ColorAnimation { duration: Kirigami.Units.longDuration } 0127 } 0128 } 0129 GradientStop { 0130 color: backgroundLoader.item ? backgroundLoader.item.gradientColorBottom : "white" 0131 position: 1.0 0132 Behavior on color { 0133 ColorAnimation { duration: Kirigami.Units.longDuration } 0134 } 0135 } 0136 } 0137 0138 // separator between the top window decoration bar and the components 0139 Rectangle { 0140 anchors.top: parent.top 0141 anchors.left: parent.left 0142 anchors.right: parent.right 0143 height: 1 0144 color: Qt.rgba(0, 0, 0, 0.2) 0145 } 0146 0147 // background components (ex. cloud, sun, etc.) 0148 Item { 0149 anchors.fill: parent 0150 opacity: { // opacity lightens when you scroll down the page 0151 let scrollAmount = page.flickable.contentY - (Kirigami.Units.gridUnit * 3); 0152 if (scrollAmount < 0) { 0153 scrollAmount = 0; 0154 } 0155 0156 return 1 - 0.5 * (scrollAmount / (Kirigami.Units.gridUnit * 5)); 0157 } 0158 0159 // weather elements 0160 Loader { 0161 anchors.fill: parent 0162 opacity: backgroundLoader.item && backgroundLoader.item.sun ? 1 : 0 0163 Behavior on opacity { NumberAnimation { duration: Kirigami.Units.longDuration } } 0164 active: opacity !== 0 0165 sourceComponent: Sun {} 0166 } 0167 Loader { 0168 anchors.fill: parent 0169 opacity: backgroundLoader.item && backgroundLoader.item.stars ? 1 : 0 0170 Behavior on opacity { NumberAnimation { duration: Kirigami.Units.longDuration } } 0171 active: opacity !== 0 0172 sourceComponent: Stars {} 0173 } 0174 Loader { 0175 anchors.fill: parent 0176 opacity: backgroundLoader.item && backgroundLoader.item.clouds ? 1 : 0 0177 Behavior on opacity { NumberAnimation { duration: Kirigami.Units.longDuration } } 0178 active: opacity !== 0 0179 sourceComponent: Cloudy { cloudColor: backgroundLoader.item.cloudsColor } 0180 } 0181 Loader { 0182 anchors.fill: parent 0183 opacity: backgroundLoader.item && backgroundLoader.item.rain ? 1 : 0 0184 Behavior on opacity { NumberAnimation { duration: Kirigami.Units.longDuration } } 0185 active: opacity !== 0 0186 sourceComponent: Rain {} 0187 } 0188 Loader { 0189 anchors.fill: parent 0190 opacity: backgroundLoader.item && backgroundLoader.item.snow ? 1 : 0 0191 Behavior on opacity { NumberAnimation { duration: Kirigami.Units.longDuration } } 0192 active: opacity !== 0 0193 sourceComponent: Snow {} 0194 } 0195 0196 Loader { 0197 id: backgroundLoader 0198 anchors.fill: parent 0199 Component.onCompleted: { 0200 if (weatherLocation) { 0201 source = weatherLocation.backgroundComponent; 0202 } 0203 } 0204 0205 NumberAnimation { 0206 id: backgroundLoaderOpacity 0207 target: backgroundLoader.item 0208 property: "opacity" 0209 duration: Kirigami.Units.longDuration 0210 to: 1 0211 running: true 0212 onFinished: { 0213 backgroundLoader.source = weatherLocation.backgroundComponent; 0214 to = 1; 0215 restart(); 0216 } 0217 } 0218 } 0219 } 0220 0221 // fade away background when locations changed 0222 Connections { 0223 target: page 0224 function onCurrentIndexChanged() { 0225 backgroundLoaderOpacity.to = 0; 0226 backgroundLoaderOpacity.restart(); 0227 } 0228 } 0229 } 0230 } 0231 Connections { 0232 target: weatherLocation 0233 ignoreUnknownSignals: true // weatherLocation may be null 0234 function onStopLoadingIndicator() { 0235 showPassiveNotification(i18n("Weather refreshed for %1", weatherLocation.name)); 0236 } 0237 } 0238 0239 Item { 0240 implicitHeight: mainLayout.implicitHeight 0241 0242 Rectangle { 0243 id: rootMask 0244 color: "transparent" 0245 opacity: 1 - (Math.abs(x) / (page.width / 4)) 0246 height: parent.height 0247 width: parent.width 0248 0249 // left/right dragging for switching pages 0250 DragHandler { 0251 id: dragHandler 0252 target: rootMask 0253 yAxis.enabled: false; xAxis.enabled: true 0254 xAxis.minimum: page.canGoRight ? -page.width : -pageChangeThreshold / 2 // extra feedback 0255 xAxis.maximum: page.canGoLeft ? page.width : pageChangeThreshold / 2 // extra feedback 0256 0257 // HACK: when a delegate, or the listview is being interacted with, disable the DragHandler so that it doesn't switch pages 0258 enabled: dailyCard.pressedCount == 0 && hourlyCard.pressedCount == 0 0259 0260 onActiveChanged: { 0261 if (!active) { 0262 // if drag passed threshold, change page 0263 if (rootMask.x >= pageChangeThreshold) { 0264 page.finishMoveLeft(); 0265 } else if (rootMask.x <= -pageChangeThreshold) { 0266 page.finishMoveRight(); 0267 } else { 0268 xAnim.restart(); // reset position 0269 } 0270 } 0271 } 0272 } 0273 0274 // reset to position 0275 NumberAnimation on x { 0276 id: xAnim; to: 0 0277 running: false 0278 easing.type: Easing.InOutQuad 0279 duration: Kirigami.Units.longDuration 0280 } 0281 0282 // header 0283 RowLayout { 0284 id: header 0285 anchors.left: parent.left 0286 anchors.top: parent.top 0287 anchors.right: parent.right 0288 anchors.margins: Kirigami.Units.smallSpacing 0289 0290 ColumnLayout { 0291 Layout.fillWidth: true 0292 spacing: Kirigami.Units.largeSpacing 0293 Label { 0294 Layout.fillWidth: true 0295 font.pointSize: Kirigami.Theme.defaultFont.pointSize * 2 0296 font.weight: Font.Bold 0297 text: weatherLocation.name 0298 color: "white" 0299 wrapMode: Text.Wrap 0300 } 0301 Label { 0302 font.pointSize: Kirigami.Theme.defaultFont.pointSize * 0.9 0303 color: "white" 0304 opacity: 0.9 0305 Layout.alignment: Qt.AlignLeft 0306 horizontalAlignment: Text.AlignLeft 0307 text: i18n("Updated at %1", weatherLocation.lastUpdated) 0308 } 0309 } 0310 0311 property real buttonLength: Kirigami.Units.gridUnit * 2.5 0312 property real iconLength: Kirigami.Units.gridUnit * 1.2 0313 0314 ToolButton { 0315 Layout.alignment: Qt.AlignTop 0316 Layout.minimumWidth: header.buttonLength 0317 Layout.minimumHeight: header.buttonLength 0318 0319 Kirigami.Theme.colorSet: Kirigami.Theme.Complementary 0320 Kirigami.Theme.inherit: false 0321 0322 icon.name: "find-location" 0323 icon.height: header.iconLength 0324 icon.width: header.iconLength 0325 icon.color: "white" 0326 0327 text: i18n("Locations") 0328 display: ToolButton.IconOnly 0329 onClicked: applicationWindow().openLocationsList() 0330 0331 ToolTip.visible: down 0332 ToolTip.text: i18n("Choose location") 0333 } 0334 ToolButton { 0335 Layout.alignment: Qt.AlignTop 0336 Layout.minimumWidth: header.buttonLength 0337 Layout.minimumHeight: header.buttonLength 0338 0339 Kirigami.Theme.colorSet: Kirigami.Theme.Complementary 0340 Kirigami.Theme.inherit: false 0341 0342 icon.name: "settings-configure" 0343 icon.height: header.iconLength 0344 icon.width: header.iconLength 0345 icon.color: "white" 0346 0347 text: i18n("Settings") 0348 display: ToolButton.IconOnly 0349 onClicked: applicationWindow().openSettings() 0350 0351 ToolTip.visible: down 0352 ToolTip.text: i18n("Settings") 0353 } 0354 ToolButton { 0355 Layout.alignment: Qt.AlignTop 0356 Layout.minimumWidth: header.buttonLength 0357 Layout.minimumHeight: header.buttonLength 0358 0359 Kirigami.Theme.colorSet: Kirigami.Theme.Complementary 0360 Kirigami.Theme.inherit: false 0361 0362 icon.name: "view-refresh" 0363 icon.height: header.iconLength 0364 icon.width: header.iconLength 0365 icon.color: "white" 0366 0367 visible: !Kirigami.Settings.isMobile 0368 text: i18n("Refresh") 0369 display: ToolButton.IconOnly 0370 onClicked: WeatherLocationListModel.locations[page.currentIndex].update() 0371 0372 ToolTip.visible: down 0373 ToolTip.text: i18n("Refresh") 0374 } 0375 } 0376 0377 // content 0378 ColumnLayout { 0379 id: mainLayout 0380 anchors.horizontalCenter: parent.horizontalCenter 0381 width: Math.min(page.width - Kirigami.Units.largeSpacing * 4, maximumContentWidth) 0382 0383 // separator from top 0384 // used instead of topMargin, since it can be shrunk when needed (small window height) 0385 Item { 0386 Layout.preferredHeight: Math.max(header.height + Kirigami.Units.gridUnit * 2, // header height 0387 page.height - headerText.height - dailyHeader.height - dailyCard.height - Kirigami.Units.gridUnit * 3) // pin to bottom of window 0388 } 0389 0390 // weather header 0391 ColumnLayout { 0392 id: headerText 0393 Layout.fillWidth: true 0394 RowLayout { 0395 spacing: 0 0396 Label { 0397 font.pointSize: Kirigami.Theme.defaultFont.pointSize * 4 0398 font.weight: Font.Light 0399 color: "white" 0400 text: Math.round(Formatter.convertTemp(weatherLocation.currentHourForecast.temperature, settingsModel.temperatureUnits)) 0401 font.family: lightHeadingFont.name 0402 } 0403 Label { 0404 Layout.alignment: Qt.AlignTop 0405 Layout.topMargin: Kirigami.Units.largeSpacing 0406 font.pointSize: Kirigami.Theme.defaultFont.pointSize * 2 0407 font.weight: Font.Light 0408 color: "white" 0409 font.family: lightHeadingFont.name 0410 text: Formatter.formatTemperatureUnitDegrees(settingsModel.temperatureUnits) 0411 } 0412 } 0413 Label { 0414 font.pointSize: Kirigami.Theme.defaultFont.pointSize * 2 0415 font.weight: Font.DemiBold 0416 color: "white" 0417 Layout.alignment: Qt.AlignLeft 0418 horizontalAlignment: Text.AlignLeft 0419 text: weatherLocation.currentHourForecast.weatherDescription 0420 font.family: lightHeadingFont.name 0421 } 0422 } 0423 0424 // daily view header 0425 ColumnLayout { 0426 id: dailyHeader 0427 spacing: Kirigami.Units.smallSpacing 0428 Layout.fillWidth: true 0429 Layout.topMargin: Kirigami.Units.largeSpacing * 2 0430 Layout.bottomMargin: Kirigami.Units.largeSpacing 0431 0432 Label { 0433 text: i18n("Daily") 0434 font.pointSize: Kirigami.Theme.defaultFont.pointSize * 1.3 0435 color: "white" 0436 } 0437 Label { 0438 text: i18n("Local date: %1", weatherLocation.currentDate) 0439 font: Kirigami.Theme.smallFont 0440 color: "white" 0441 opacity: 0.7 0442 } 0443 } 0444 0445 // daily view 0446 Control { 0447 id: dailyCard 0448 Layout.fillWidth: true 0449 padding: Kirigami.Units.largeSpacing 0450 0451 property int pressedCount: 0 0452 0453 background: Rectangle { 0454 color: weatherLocation.cardBackgroundColor 0455 radius: Kirigami.Units.smallSpacing 0456 anchors.fill: parent 0457 } 0458 0459 contentItem: WeatherStrip { 0460 id: dailyListView 0461 selectable: true 0462 0463 highlightMoveDuration: 250 0464 highlightMoveVelocity: -1 0465 highlight: Rectangle { 0466 color: Kirigami.Theme.focusColor 0467 border { 0468 color: Kirigami.Theme.focusColor 0469 width: 1 0470 } 0471 radius: 4 0472 opacity: 0.3 0473 focus: true 0474 } 0475 0476 spacing: Kirigami.Units.largeSpacing 0477 0478 onDraggingChanged: dailyCard.pressedCount += dragging? 1 : -1; 0479 0480 model: weatherLocation.dayForecasts 0481 delegate: WeatherDayDelegate { 0482 id: delegate 0483 weather: modelData 0484 textColor: weatherLocation.cardTextColor 0485 secondaryTextColor: weatherLocation.cardSecondaryTextColor 0486 0487 Connections { 0488 target: delegate.mouseArea 0489 function onPressedChanged() { 0490 dailyCard.pressedCount += delegate.mouseArea.pressed ? 1 : -1; 0491 } 0492 } 0493 } 0494 0495 onCurrentIndexChanged: { 0496 weatherLocation.selectedDay = currentIndex 0497 } 0498 } 0499 } 0500 0501 // temperature chart 0502 TemperatureChartCard { 0503 Layout.fillWidth: true 0504 location: weatherLocation 0505 0506 background: Rectangle { 0507 color: weatherLocation.cardBackgroundColor 0508 radius: Kirigami.Units.smallSpacing 0509 anchors.fill: parent 0510 } 0511 } 0512 0513 // hourly view header 0514 ColumnLayout { 0515 spacing: Kirigami.Units.smallSpacing 0516 Layout.fillWidth: true 0517 Layout.topMargin: Kirigami.Units.largeSpacing * 2 0518 Layout.bottomMargin: Kirigami.Units.largeSpacing 0519 0520 Label { 0521 text: i18n("Hourly") 0522 font.pointSize: Kirigami.Theme.defaultFont.pointSize * 1.3 0523 color: "white" 0524 } 0525 Label { 0526 text: i18n("Local time: %1", weatherLocation.currentTime) 0527 font: Kirigami.Theme.smallFont 0528 color: "white" 0529 opacity: 0.7 0530 } 0531 } 0532 0533 // hourly view 0534 Kirigami.AbstractCard { 0535 id: hourlyCard 0536 topPadding: Kirigami.Units.gridUnit 0537 bottomPadding: Kirigami.Units.gridUnit 0538 leftPadding: Kirigami.Units.gridUnit 0539 rightPadding: Kirigami.Units.gridUnit 0540 Layout.fillWidth: true 0541 0542 property int pressedCount: 0 0543 0544 background: Rectangle { 0545 color: weatherLocation.cardBackgroundColor 0546 radius: Kirigami.Units.smallSpacing 0547 anchors.fill: parent 0548 } 0549 0550 contentItem: WeatherStrip { 0551 id: hourlyListView 0552 selectable: false 0553 model: weatherLocation.hourForecasts 0554 onDraggingChanged: hourlyCard.pressedCount += dragging? 1 : -1; 0555 0556 delegate: WeatherHourDelegate { 0557 id: delegate 0558 weather: modelData 0559 textColor: weatherLocation.cardTextColor 0560 secondaryTextColor: weatherLocation.cardSecondaryTextColor 0561 0562 Connections { 0563 target: delegate.mouseArea 0564 function onPressedChanged() { 0565 hourlyCard.pressedCount += delegate.mouseArea.pressed ? 1 : -1; 0566 } 0567 } 0568 } 0569 } 0570 } 0571 0572 // bottom card (extra info for selected day) 0573 InfoCard { 0574 Layout.fillWidth: true 0575 0576 textColor: weatherLocation.cardTextColor 0577 0578 background: Rectangle { 0579 color: weatherLocation.cardBackgroundColor 0580 radius: Kirigami.Units.smallSpacing 0581 anchors.fill: parent 0582 } 0583 } 0584 0585 SunriseCard { 0586 Layout.fillWidth: true 0587 0588 textColor: weatherLocation.cardTextColor 0589 selectedDay: page.selectedDay 0590 0591 background: Rectangle { 0592 color: weatherLocation.cardBackgroundColor 0593 radius: Kirigami.Units.smallSpacing 0594 anchors.fill: parent 0595 } 0596 } 0597 } 0598 } 0599 } 0600 } 0601