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