diff options
| author | AlexanderCurl <alexc@alexc.hu> | 2026-04-18 17:46:06 +0100 |
|---|---|---|
| committer | AlexanderCurl <alexc@alexc.hu> | 2026-04-18 17:46:06 +0100 |
| commit | 70ca3fef77ee8bdc6e3ac28589a6fa08c024cc69 (patch) | |
| tree | ab1123d4067c1b086dd6faa7ee4ea643236b565a /raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/DankDash/WeatherTab.qml | |
| parent | 5d94c0a7d44a2255b81815a52a7056a94a39842d (diff) | |
| download | RaveOS-PKGBUILD-70ca3fef77ee8bdc6e3ac28589a6fa08c024cc69.tar.gz RaveOS-PKGBUILD-70ca3fef77ee8bdc6e3ac28589a6fa08c024cc69.zip | |
Replaced file structures for themes in the correct format
Diffstat (limited to 'raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/DankDash/WeatherTab.qml')
| -rw-r--r-- | raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/DankDash/WeatherTab.qml | 1124 |
1 files changed, 1124 insertions, 0 deletions
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/DankDash/WeatherTab.qml b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/DankDash/WeatherTab.qml new file mode 100644 index 0000000..21ea656 --- /dev/null +++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/DankDash/WeatherTab.qml @@ -0,0 +1,1124 @@ +import QtQuick +import QtQuick.Effects +import QtQuick.Shapes +import qs.Common +import qs.Services +import qs.Widgets + +Item { + id: root + + LayoutMirroring.enabled: I18n.isRtl + LayoutMirroring.childrenInherit: true + + implicitWidth: SettingsData.showWeekNumber ? 736 : 700 + implicitHeight: 410 + property bool syncing: false + property bool showHourly: false + property bool available: WeatherService.weather.available + + function syncFrom(type) { + if (!dailyLoader.item || !hourlyLoader.item) + return; + const hourlyList = hourlyLoader.item; + const dailyList = dailyLoader.item; + syncing = true; + + try { + if (type === "hour") { + const date = new Date(); + date.setHours(hourlyList.currentIndex); + dateStepper.currentDate = date; + + dailyList.currentIndex = Math.max(0, Math.min((WeatherService.weather.forecast?.length ?? 1) - 1, WeatherService.calendarDayDifference((new Date()), date))); + } else if (type === "day") { + const date = new Date(dateStepper.currentDate); + date.setMonth((new Date()).getMonth()); + date.setDate((new Date()).getDate() + dailyList.currentIndex); + dateStepper.currentDate = date; + + const hourIndex = Math.max(0, Math.min((WeatherService.weather.hourlyForecast?.length ?? 1) - 1, WeatherService.calendarHourDifference((new Date()), date) + (new Date).getHours())); + hourlyList.currentIndex = hourIndex; + } else if (type === "date") { + const date = dateStepper.currentDate; + dailyList.currentIndex = Math.max(0, Math.min((WeatherService.weather.forecast?.length ?? 1) - 1, WeatherService.calendarDayDifference((new Date()), date))); + hourlyList.currentIndex = Math.max(0, Math.min((WeatherService.weather.hourlyForecast?.length ?? 1) - 1, WeatherService.calendarHourDifference((new Date()), date) + (new Date()).getHours())); + } + } catch (e) { + console.warn("Weather Date Sync Error:", e); + } + + syncing = false; + } + + readonly property string sunriseTimeText: { + if (!WeatherService.weather.rawSunrise) + return WeatherService.weather.sunrise || ""; + try { + const date = new Date(WeatherService.weather.rawSunrise); + const format = SettingsData.use24HourClock ? "HH:mm" : "h:mm AP"; + return date.toLocaleTimeString(Qt.locale(), format); + } catch (e) { + return WeatherService.weather.sunrise || ""; + } + } + + readonly property string sunsetTimeText: { + if (!WeatherService.weather.rawSunset) + return WeatherService.weather.sunset || ""; + try { + const date = new Date(WeatherService.weather.rawSunset); + const format = SettingsData.use24HourClock ? "HH:mm" : "h:mm AP"; + return date.toLocaleTimeString(Qt.locale(), format); + } catch (e) { + return WeatherService.weather.sunset || ""; + } + } + + readonly property var heroMetrics: { + SettingsData.useFahrenheit; + SettingsData.windSpeedUnit; + return [ + { + "icon": "humidity_low", + "label": I18n.tr("Humidity"), + "value": WeatherService.formatPercent(WeatherService.weather.humidity) ?? "--" + }, + { + "icon": "air", + "label": I18n.tr("Wind"), + "value": WeatherService.formatSpeed(WeatherService.weather.wind) ?? "--" + }, + { + "icon": "speed", + "label": I18n.tr("Pressure"), + "value": WeatherService.formatPressure(WeatherService.weather.pressure) ?? "--" + }, + { + "icon": "rainy", + "label": I18n.tr("Precipitation"), + "value": (WeatherService.weather.precipitationProbability ?? 0) + "%" + }, + { + "icon": "wb_twilight", + "label": I18n.tr("Sunrise"), + "value": root.sunriseTimeText || "--" + }, + { + "icon": "bedtime", + "label": I18n.tr("Sunset"), + "value": root.sunsetTimeText || "--" + } + ]; + } + + Column { + id: unavailableColumn + anchors.centerIn: parent + spacing: Theme.spacingL + visible: !root.available + + DankIcon { + name: "cloud_off" + size: Theme.iconSize * 2 + color: Theme.withAlpha(Theme.surfaceText, 0.5) + anchors.horizontalCenter: parent.horizontalCenter + } + + Row { + width: refreshButtonTwo.width + refreshText.width + height: refreshButtonTwo.height + spacing: Theme.spacingS + + StyledText { + id: refreshText + text: I18n.tr("No Weather Data Available") + font.pixelSize: Theme.fontSizeLarge + color: Theme.withAlpha(Theme.surfaceText, 0.7) + anchors.verticalCenter: parent.verticalCenter + } + + DankIcon { + id: refreshButtonTwo + name: "refresh" + size: Theme.iconSize - 4 + color: Theme.withAlpha(Theme.surfaceText, 0.4) + anchors.top: parent.top + anchors.verticalCenter: parent.verticalCenter + + property bool isRefreshing: false + enabled: !isRefreshing + + MouseArea { + id: refreshButtonMouseAreaTwo + anchors.fill: parent + hoverEnabled: true + cursorShape: parent.enabled ? Qt.PointingHandCursor : Qt.ForbiddenCursor + enabled: parent.enabled + + Timer { + id: hoverDelayTwo + interval: 300 + repeat: false + onTriggered: { + refreshButtonTooltipTwo.show(I18n.tr("Refresh Weather"), refreshButtonTwo, 0, 0, "left"); + } + } + + onEntered: { + hoverDelayTwo.restart(); + } + + onExited: { + hoverDelayTwo.stop(); + refreshButtonTooltipTwo.hide(); + } + + onClicked: { + refreshButtonTwo.isRefreshing = true; + WeatherService.forceRefresh(); + refreshTimerTwo.restart(); + } + } + + DankTooltipV2 { + id: refreshButtonTooltipTwo + } + + Timer { + id: refreshTimerTwo + interval: 2000 + onTriggered: refreshButtonTwo.isRefreshing = false + } + + NumberAnimation on rotation { + running: refreshButtonTwo.isRefreshing + from: 0 + to: 360 + duration: 1000 + loops: Animation.Infinite + } + } + } + } + + Column { + id: mainColumn + anchors.fill: parent + visible: root.available + spacing: Theme.spacingS + + Rectangle { + id: heroCard + width: parent.width + height: heroContent.height + Theme.spacingL * 2 + radius: Theme.cornerRadius + color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency) + border.color: Theme.withAlpha(Theme.outline, 0.08) + border.width: 1 + + Column { + id: heroContent + x: Theme.spacingL + y: Theme.spacingL + width: parent.width - Theme.spacingL * 2 + spacing: Theme.spacingM + + Item { + width: parent.width + height: Math.max(heroLeft.height, heroMetricsGrid.height) + + Row { + id: heroLeft + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + spacing: Theme.spacingL + + DankIcon { + id: weatherIcon + name: WeatherService.getWeatherIcon(WeatherService.weather.wCode) + size: Theme.iconSize * 2 + color: Theme.primary + anchors.verticalCenter: parent.verticalCenter + + layer.enabled: Theme.elevationEnabled + layer.effect: MultiEffect { + shadowEnabled: Theme.elevationEnabled + shadowHorizontalOffset: Theme.elevationOffsetX(Theme.elevationLevel1) + shadowVerticalOffset: Theme.elevationOffsetY(Theme.elevationLevel1, 1) + shadowBlur: Theme.elevationEnabled ? Math.max(0, Math.min(1, (Theme.elevationLevel1 && Theme.elevationLevel1.blurPx !== undefined ? Theme.elevationLevel1.blurPx : 4) / Theme.elevationBlurMax)) : 0 + blurMax: Theme.elevationBlurMax + shadowColor: Theme.elevationShadowColor(Theme.elevationLevel1) + shadowOpacity: Theme.elevationLevel1 && Theme.elevationLevel1.alpha !== undefined ? Theme.elevationLevel1.alpha : 0.2 + } + } + + Column { + id: tempColumn + spacing: Theme.spacingXS + anchors.verticalCenter: parent.verticalCenter + + Item { + anchors.left: parent.left + width: tempText.width + unitText.width + Theme.spacingXS + height: tempText.height + + StyledText { + id: tempText + LayoutMirroring.enabled: false + text: (SettingsData.useFahrenheit ? WeatherService.weather.tempF : WeatherService.weather.temp) + "°" + font.pixelSize: Theme.fontSizeXLarge + 8 + color: Theme.surfaceText + font.weight: Font.Light + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + } + + StyledText { + id: unitText + LayoutMirroring.enabled: false + text: SettingsData.useFahrenheit ? "F" : "C" + font.pixelSize: Theme.fontSizeMedium + color: Theme.withAlpha(Theme.surfaceText, 0.7) + anchors.left: tempText.right + anchors.leftMargin: Theme.spacingXS + anchors.verticalCenter: parent.verticalCenter + + MouseArea { + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: { + if (WeatherService.weather.available) + SettingsData.set("useFahrenheit", !SettingsData.useFahrenheit); + } + enabled: WeatherService.weather.available + } + } + } + + StyledText { + text: WeatherService.getWeatherCondition(WeatherService.weather.wCode) + font.pixelSize: Theme.fontSizeMedium + color: Theme.withAlpha(Theme.surfaceText, 0.7) + anchors.left: parent.left + } + + StyledText { + property var feelsLike: SettingsData.useFahrenheit ? (WeatherService.weather.feelsLikeF || WeatherService.weather.tempF) : (WeatherService.weather.feelsLike || WeatherService.weather.temp) + text: I18n.tr("Feels Like %1°", "weather feels like temperature").arg(feelsLike) + font.pixelSize: Theme.fontSizeSmall + color: Theme.withAlpha(Theme.surfaceText, 0.5) + anchors.left: parent.left + } + + StyledText { + text: WeatherService.weather.city || "" + font.pixelSize: Theme.fontSizeSmall + color: Theme.withAlpha(Theme.surfaceText, 0.5) + visible: text.length > 0 + anchors.left: parent.left + } + } + } + + Grid { + id: heroMetricsGrid + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + columns: 3 + columnSpacing: Theme.spacingXL + rowSpacing: Theme.spacingS + + Repeater { + model: root.heroMetrics + + Row { + spacing: Theme.spacingXS + + DankIcon { + name: modelData.icon + size: Theme.iconSizeSmall - 2 + color: Theme.withAlpha(Theme.surfaceText, 0.5) + anchors.verticalCenter: parent.verticalCenter + } + + Column { + spacing: 2 + + StyledText { + text: modelData.label + font.pixelSize: Theme.fontSizeSmall + color: Theme.withAlpha(Theme.surfaceText, 0.5) + anchors.left: parent.left + } + + StyledText { + text: modelData.value + font.pixelSize: Theme.fontSizeMedium + color: Theme.surfaceText + anchors.left: parent.left + } + } + } + } + } + } + } + } + + Item { + id: skyDateRow + width: parent.width + height: dateStepper.height + + Item { + id: dateStepper + height: dateStepperInner.height + Theme.spacingM * 2 + width: dateStepperInner.width + + property var currentDate: new Date() + + readonly property var changeDate: (magnitudeIndex, sign) => { + switch (magnitudeIndex) { + case 0: + break; + case 1: + var newDate = new Date(dateStepper.currentDate); + newDate.setMonth(dateStepper.currentDate.getMonth() + sign * 1); + dateStepper.currentDate = newDate; + break; + case 2: + dateStepper.currentDate = new Date(dateStepper.currentDate.getTime() + sign * 24 * 3600 * 1000); + break; + case 3: + dateStepper.currentDate = new Date(dateStepper.currentDate.getTime() + sign * 3600 * 1000); + break; + case 4: + dateStepper.currentDate = new Date(dateStepper.currentDate.getTime() + sign * 5 * 60 * 1000); + break; + } + } + readonly property var splitDate: Qt.formatDateTime(dateStepper.currentDate, SettingsData.use24HourClock ? "yyyy.MM.dd.HH.mm" : "yyyy.MM.dd.hh.mm.AP").split('.') + + Item { + id: dateStepperInner + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + readonly property var space: Theme.spacingXS + width: yearStepper.width + monthStepper.width + dayStepper.width + hourStepper.width + minuteStepper.width + (suffix.visible ? suffix.width : 0) + 10.5 * space + 2 * dateStepperInnerPadding.width + height: Math.max(yearStepper.height, monthStepper.height, dayStepper.height, hourStepper.height, minuteStepper.height) + + Item { + id: dateStepperInnerPadding + width: dateResetButton.width + } + + DankNumberStepper { + id: yearStepper + anchors.left: dateStepperInnerPadding.right + anchors.leftMargin: parent.space + width: implicitWidth + text: dateStepper.splitDate[0] + } + + DankNumberStepper { + id: monthStepper + width: implicitWidth + anchors.left: yearStepper.right + anchors.leftMargin: parent.space + text: dateStepper.splitDate[1] + onIncrement: () => dateStepper.changeDate(1, +1) + onDecrement: () => dateStepper.changeDate(1, -1) + } + + DankNumberStepper { + id: dayStepper + width: implicitWidth + anchors.left: monthStepper.right + anchors.leftMargin: parent.space + text: dateStepper.splitDate[2] + onIncrement: () => dateStepper.changeDate(2, +1) + onDecrement: () => dateStepper.changeDate(2, -1) + } + + DankNumberStepper { + id: hourStepper + width: implicitWidth + anchors.left: dayStepper.right + anchors.leftMargin: 1.5 * parent.space + text: dateStepper.splitDate[3] + onIncrement: () => dateStepper.changeDate(3, +1) + onDecrement: () => dateStepper.changeDate(3, -1) + } + + DankNumberStepper { + id: minuteStepper + width: implicitWidth + anchors.left: hourStepper.right + anchors.leftMargin: parent.space + text: dateStepper.splitDate[4] + onIncrement: () => dateStepper.changeDate(4, +1) + onDecrement: () => dateStepper.changeDate(4, -1) + } + + Item { + anchors.verticalCenter: parent.verticalCenter + anchors.left: yearStepper.right + anchors.right: monthStepper.left + StyledText { + anchors.verticalCenter: parent.verticalCenter + anchors.horizontalCenter: parent.horizontalCenter + text: "-" + } + } + Item { + anchors.verticalCenter: parent.verticalCenter + anchors.left: monthStepper.right + anchors.right: dayStepper.left + StyledText { + anchors.verticalCenter: parent.verticalCenter + anchors.horizontalCenter: parent.horizontalCenter + text: "-" + } + } + Item { + anchors.verticalCenter: parent.verticalCenter + anchors.left: hourStepper.right + anchors.right: minuteStepper.left + StyledText { + anchors.verticalCenter: parent.verticalCenter + anchors.horizontalCenter: parent.horizontalCenter + text: ":" + } + } + StyledText { + id: suffix + visible: !SettingsData.use24HourClock + anchors.verticalCenter: minuteStepper.verticalCenter + anchors.left: minuteStepper.right + anchors.leftMargin: 2 * parent.space + isMonospace: true + text: dateStepper.splitDate[5] ?? "" + font.pixelSize: Theme.fontSizeSmall + } + DankActionButton { + id: dateResetButton + anchors.verticalCenter: parent.verticalCenter + anchors.right: parent.right + enabled: Math.abs(dateStepper.currentDate - new Date()) > 1000 + iconColor: enabled ? Theme.blendAlpha(Theme.surfaceText, 0.5) : "transparent" + iconSize: 12 + buttonSize: 20 + iconName: "replay" + onClicked: { + dateStepper.currentDate = new Date(); + } + } + } + + onCurrentDateChanged: if (!syncing) + root.syncFrom("date") + } + + Rectangle { + id: skyBox + anchors.left: dateStepper.right + anchors.leftMargin: Theme.spacingM + anchors.right: parent.right + height: parent.height + + LayoutMirroring.enabled: false + LayoutMirroring.childrenInherit: true + + property var backgroundOpacity: 0.3 + property var sunTime: WeatherService.getCurrentSunTime(dateStepper.currentDate) + property var periodIndex: sunTime?.periodIndex + property var periodPercent: sunTime?.periodPercent + property var blackColor: Theme.blend(Theme.surface, Qt.rgba(0, 0, 0, 255), 0.2) + property var redColor: Theme.secondary + property var blueColor: Theme.primary + function blackBlue(r) { + return Theme.blend(blackColor, blueColor, r); + } + property var topColor: { + const colorMap = [blackColor, Theme.withAlpha(blackBlue(0.0), 0.8), Theme.withAlpha(blackBlue(0.2), 0.7), Theme.withAlpha(blackBlue(0.5), 0.6), Theme.withAlpha(blackBlue(0.7), 0.6), Theme.withAlpha(blackBlue(0.9), 0.6), Theme.withAlpha(blackBlue(1.0), 0.6), Theme.withAlpha(blackBlue(0.9), 0.6), Theme.withAlpha(blackBlue(0.7), 0.6), Theme.withAlpha(blackBlue(0.5), 0.6), Theme.withAlpha(blackBlue(0.2), 0.7), Theme.withAlpha(blackBlue(0.0), 0.8), blackColor, blackColor]; + const index = periodIndex ?? 0; + return Theme.blend(colorMap[index], colorMap[index + 1], periodPercent ?? 0); + } + property var sunColor: { + const colorMap = [Theme.withAlpha(redColor, 0.05), Theme.withAlpha(redColor, 0.1), Theme.withAlpha(redColor, 0.3), Theme.withAlpha(redColor, 0.4), Theme.withAlpha(redColor, 0.5), Theme.withAlpha(blueColor, 0.2), Theme.withAlpha(blueColor, 0.0), Theme.withAlpha(blueColor, 0.2), Theme.withAlpha(redColor, 0.5), Theme.withAlpha(redColor, 0.4), Theme.withAlpha(redColor, 0.3), Theme.withAlpha(redColor, 0.1), Theme.withAlpha(redColor, 0.05), Theme.withAlpha(redColor, 0.0)]; + const index = periodIndex ?? 0; + return Theme.blend(colorMap[index], colorMap[index + 1], periodPercent ?? 0); + } + + color: "transparent" + + Rectangle { + anchors.fill: parent + opacity: skyBox.backgroundOpacity + + gradient: Gradient { + GradientStop { + position: 0.0 + color: Theme.withAlpha(skyBox.blackColor, 0.0) + } + GradientStop { + position: 0.05 + color: skyBox.topColor + } + GradientStop { + position: 0.3 + color: skyBox.topColor + } + GradientStop { + position: 0.5 + color: skyBox.topColor + } + GradientStop { + position: 0.501 + color: skyBox.blackColor + } + GradientStop { + position: 0.9 + color: skyBox.blackColor + } + GradientStop { + position: 1.0 + color: Theme.withAlpha(skyBox.blackColor, 0.0) + } + } + } + + property var currentDate: dateStepper.currentDate + property var hMargin: 0 + property var vMargin: Theme.spacingM + property var effectiveHeight: skyBox.height - 2 * vMargin + property var effectiveWidth: skyBox.width - 2 * hMargin + + StyledText { + text: parent.sunTime?.period ?? "" + font.pixelSize: Theme.fontSizeSmall + color: Theme.withAlpha(Theme.surfaceText, 0.7) + x: 0 + y: 0 + } + + Shape { + id: skyShape + anchors.left: parent.left + anchors.top: parent.top + anchors.right: parent.right + height: parent.height / 2 + opacity: skyBox.backgroundOpacity + + ShapePath { + strokeColor: "transparent" + fillGradient: RadialGradient { + centerX: skyBox.hMargin + sun.x + sun.width / 2 + centerY: skyBox.vMargin + sun.y + 30 + centerRadius: { + const a = Math.abs((skyBox.sunTime?.dayPercent ?? 0) - 0.5); + const out = 200 * (0.5 - a * a); + return out; + } + focalX: skyBox.hMargin + sun.x + sun.width / 2 + focalY: skyBox.vMargin + sun.y + GradientStop { + position: 0 + color: skyBox.sunColor + } + GradientStop { + position: 0.3 + color: Theme.blendAlpha(skyBox.sunColor, 0.5) + } + GradientStop { + position: 1 + color: "transparent" + } + } + PathLine { + x: 0 + y: 0 + } + PathLine { + x: skyShape.width + y: 0 + } + PathLine { + x: skyShape.width + y: skyShape.height + } + PathLine { + x: 0 + y: skyShape.height + } + } + + ShapePath { + strokeColor: "transparent" + fillGradient: RadialGradient { + centerX: sun.x + centerY: sun.y + centerRadius: 500 + focalX: centerX + focalY: centerY + 0.99 * (centerRadius - focalRadius) + focalRadius: 10 + GradientStop { + position: 0 + color: skyBox.sunColor + } + GradientStop { + position: 0.45 + color: skyBox.sunColor + } + GradientStop { + position: 0.55 + color: "transparent" + } + GradientStop { + position: 1 + color: "transparent" + } + } + PathLine { + x: 0 + y: 0 + } + PathLine { + x: skyShape.width + y: 0 + } + PathLine { + x: skyShape.width + y: skyShape.height + } + PathLine { + x: 0 + y: skyShape.height + } + } + } + + Canvas { + id: ecliptic + anchors.fill: parent + property var points: WeatherService.getEcliptic(dateStepper.currentDate) + + function getX(index) { + return points[index].h * skyBox.effectiveWidth + skyBox.hMargin; + } + function getY(index) { + return points[index].v * -(skyBox.effectiveHeight / 2) + skyBox.effectiveHeight / 2 + skyBox.vMargin; + } + + onPointsChanged: requestPaint() + + onPaint: { + var ctx = getContext("2d"); + ctx.clearRect(0, 0, width, height); + if (!points || points.length === 0) + return; + ctx.beginPath(); + ctx.moveTo(getX(0), getY(0)); + for (var i = 1; i < points.length; i++) { + ctx.lineTo(getX(i), getY(i)); + } + ctx.strokeStyle = Theme.withAlpha(Theme.outline, 0.2); + ctx.stroke(); + } + } + + property real latitude: WeatherService.getLocation()?.latitude ?? 0 + property real sunDeclination: WeatherService.getSunDeclination(dateStepper.currentDate) + + readonly property bool solarNoonIsSouth: latitude > sunDeclination + + StyledText { + id: middle + text: skyBox.solarNoonIsSouth ? "S" : "N" + font.pixelSize: Theme.fontSizeSmall + color: Theme.primary + x: skyBox.width / 2 - middle.width / 2 + y: skyBox.height / 2 - middle.height / 2 + } + + StyledText { + id: left + text: skyBox.solarNoonIsSouth ? "E" : "W" + font.pixelSize: Theme.fontSizeSmall + color: Theme.primary + x: skyBox.width / 4 - left.width / 2 + y: skyBox.height / 2 - left.height / 2 + } + + StyledText { + id: right + text: skyBox.solarNoonIsSouth ? "W" : "E" + font.pixelSize: Theme.fontSizeSmall + color: Theme.primary + x: 3 * skyBox.width / 4 - right.width / 2 + y: skyBox.height / 2 - right.height / 2 + } + + Rectangle { + height: 1 + anchors.leftMargin: Theme.spacingS + anchors.rightMargin: Theme.spacingS + anchors.left: right.right + anchors.right: skyBox.right + anchors.verticalCenter: middle.verticalCenter + color: Theme.outline + } + + Rectangle { + height: 1 + anchors.leftMargin: Theme.spacingS + anchors.rightMargin: Theme.spacingS + anchors.left: middle.right + anchors.right: right.left + anchors.verticalCenter: middle.verticalCenter + color: Theme.outline + } + + Rectangle { + height: 1 + anchors.leftMargin: Theme.spacingS + anchors.rightMargin: Theme.spacingS + anchors.left: left.right + anchors.right: middle.left + anchors.verticalCenter: middle.verticalCenter + color: Theme.outline + } + + Rectangle { + height: 1 + anchors.leftMargin: Theme.spacingS + anchors.rightMargin: Theme.spacingS + anchors.left: skyBox.left + anchors.right: left.left + anchors.verticalCenter: middle.verticalCenter + color: Theme.outline + } + + DankNFIcon { + id: moonPhase + name: WeatherService.getMoonPhase(skyBox.currentDate) || "" + size: Theme.fontSizeXLarge + color: Theme.withAlpha(Theme.surfaceText, 0.7) + rotation: (WeatherService.getMoonAngle(skyBox.currentDate) || 0) / Math.PI * 180 + visible: !!pos + + property var pos: WeatherService.getSkyArcPosition(skyBox.currentDate, false) + x: (pos?.h ?? 0) * skyBox.effectiveWidth - (moonPhase.width / 2) + skyBox.hMargin + y: (pos?.v ?? 0) * -(skyBox.effectiveHeight / 2) + skyBox.effectiveHeight / 2 - (moonPhase.height / 2) + skyBox.vMargin + + layer.enabled: Theme.elevationEnabled + layer.effect: MultiEffect { + shadowEnabled: Theme.elevationEnabled + shadowHorizontalOffset: Theme.elevationOffsetX(Theme.elevationLevel2) + shadowVerticalOffset: Theme.elevationOffsetY(Theme.elevationLevel2, 4) + shadowBlur: Theme.elevationEnabled ? Math.max(0, Math.min(1, (Theme.elevationLevel2 && Theme.elevationLevel2.blurPx !== undefined ? Theme.elevationLevel2.blurPx : 8) / Theme.elevationBlurMax)) : 0 + blurMax: Theme.elevationBlurMax + shadowColor: Theme.elevationShadowColor(Theme.elevationLevel2) + } + } + + DankIcon { + id: sun + name: "light_mode" + size: Theme.fontSizeXLarge + color: Theme.primary + visible: !!pos + + property var pos: WeatherService.getSkyArcPosition(skyBox.currentDate, true) + x: (pos?.h ?? 0) * skyBox.effectiveWidth - (sun.width / 2) + skyBox.hMargin + y: (pos?.v ?? 0) * -(skyBox.effectiveHeight / 2) + skyBox.effectiveHeight / 2 - (sun.height / 2) + skyBox.vMargin + + layer.enabled: Theme.elevationEnabled + layer.effect: MultiEffect { + shadowEnabled: Theme.elevationEnabled + shadowHorizontalOffset: Theme.elevationOffsetX(Theme.elevationLevel2) + shadowVerticalOffset: Theme.elevationOffsetY(Theme.elevationLevel2, 4) + shadowBlur: Theme.elevationEnabled ? Math.max(0, Math.min(1, (Theme.elevationLevel2 && Theme.elevationLevel2.blurPx !== undefined ? Theme.elevationLevel2.blurPx : 8) / Theme.elevationBlurMax)) : 0 + blurMax: Theme.elevationBlurMax + shadowColor: Theme.elevationShadowColor(Theme.elevationLevel2) + } + } + } + } + + Item { + id: chipsRow + width: parent.width + height: forecastChips.height + + DankFilterChips { + id: forecastChips + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + model: [I18n.tr("Daily"), I18n.tr("Hourly")] + currentIndex: root.showHourly ? 1 : 0 + showCheck: false + showCounts: false + onSelectionChanged: index => { + root.showHourly = index === 1; + } + } + + DankActionButton { + id: denseButton + anchors.right: refreshButton.left + anchors.rightMargin: Theme.spacingXS + anchors.verticalCenter: parent.verticalCenter + visible: root.showHourly && hourlyLoader.item !== null + iconName: SessionData.weatherHourlyDetailed ? "tile_large" : "tile_medium" + onClicked: SessionData.setWeatherHourlyDetailed(!SessionData.weatherHourlyDetailed) + } + + DankIcon { + id: refreshButton + name: "refresh" + size: Theme.iconSize - 4 + color: Theme.withAlpha(Theme.surfaceText, 0.4) + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + + property bool isRefreshing: false + enabled: !isRefreshing + + MouseArea { + id: refreshButtonMouseArea + anchors.fill: parent + hoverEnabled: true + cursorShape: parent.enabled ? Qt.PointingHandCursor : Qt.ForbiddenCursor + enabled: parent.enabled + + Timer { + id: hoverDelay + interval: 300 + repeat: false + onTriggered: { + refreshButtonTooltip.show(I18n.tr("Refresh Weather"), refreshButton, 0, 0, "left"); + } + } + + onEntered: { + hoverDelay.restart(); + } + + onExited: { + hoverDelay.stop(); + refreshButtonTooltip.hide(); + } + + onClicked: { + refreshButton.isRefreshing = true; + WeatherService.forceRefresh(); + refreshTimer.restart(); + } + } + + DankTooltipV2 { + id: refreshButtonTooltip + } + + Timer { + id: refreshTimer + interval: 2000 + onTriggered: refreshButton.isRefreshing = false + } + + NumberAnimation on rotation { + running: refreshButton.isRefreshing + from: 0 + to: 360 + duration: 1000 + loops: Animation.Infinite + } + } + } + + Item { + width: parent.width + height: root.height - heroCard.height - skyDateRow.height - chipsRow.height - mainColumn.spacing * 3 + + Loader { + id: dailyLoader + anchors.fill: parent + sourceComponent: dailyComponent + active: root.visible && root.available + visible: !root.showHourly + asynchronous: true + opacity: 0 + onLoaded: { + root.syncing = true; + item.currentIndex = item.initialIndex; + item.positionViewAtIndex(item.initialIndex, ListView.SnapPosition); + root.syncing = false; + opacity = 1; + } + } + + Loader { + id: hourlyLoader + anchors.fill: parent + sourceComponent: hourlyComponent + active: root.visible && root.available + visible: root.showHourly + asynchronous: true + opacity: 0 + onLoaded: { + root.syncing = true; + item.currentIndex = item.initialIndex; + item.positionViewAtIndex(item.initialIndex, ListView.SnapPosition); + root.syncing = false; + opacity = 1; + } + } + } + } + + Component { + id: hourlyComponent + ListView { + id: hourlyList + anchors.fill: parent + orientation: ListView.Horizontal + spacing: Theme.spacingS + clip: true + snapMode: ListView.SnapToItem + highlightRangeMode: ListView.StrictlyEnforceRange + highlightMoveDuration: 0 + interactive: true + + property var cardHeight: height + property var cardWidth: ((hourlyList.width + hourlyList.spacing) / hourlyList.visibleCount) - hourlyList.spacing + property int initialIndex: (new Date()).getHours() + property bool dense: !SessionData.weatherHourlyDetailed + property int visibleCount: dense ? 10 : 5 + + model: WeatherService.weather.hourlyForecast?.length ?? 0 + + delegate: WeatherForecastCard { + width: hourlyList.cardWidth + height: hourlyList.cardHeight + dense: hourlyList.dense + daily: false + + date: { + const d = new Date(); + d.setHours(index); + return d; + } + forecastData: WeatherService.weather.hourlyForecast[index] + } + + onCurrentIndexChanged: if (!syncing) + root.syncFrom("hour") + + states: [ + State { + name: "denseState" + when: hourlyList.dense + PropertyChanges { + target: hourlyList + visibleCount: 10 + } + }, + State { + name: "normalState" + when: !hourlyList.dense + PropertyChanges { + target: hourlyList + visibleCount: 5 + } + } + ] + + transitions: [ + Transition { + NumberAnimation { + properties: "visibleCount" + duration: Theme.shortDuration + easing.type: Theme.standardEasing + } + } + ] + + MouseArea { + anchors.fill: parent + onWheel: wheel => { + if (wheel.modifiers & Qt.ShiftModifier) { + if (wheel.angleDelta.y % 120 == 0 && wheel.angleDelta.x == 0) { + const newIndex = hourlyList.currentIndex - Math.sign(wheel.angleDelta.y); + if (newIndex < hourlyList.model && newIndex >= 0) { + hourlyList.currentIndex = newIndex; + wheel.accepted = true; + return; + } + } + } + wheel.accepted = false; + } + } + } + } + + Component { + id: dailyComponent + ListView { + id: dailyList + anchors.fill: parent + orientation: ListView.Horizontal + spacing: Theme.spacingS + clip: true + snapMode: ListView.SnapToItem + highlightRangeMode: ListView.StrictlyEnforceRange + highlightMoveDuration: 0 + interactive: true + + property var cardHeight: height + property var cardWidth: ((dailyList.width + dailyList.spacing) / dailyList.visibleCount) - dailyList.spacing + property int initialIndex: 0 + property bool dense: false + property int visibleCount: 7 + + model: WeatherService.weather.forecast?.length ?? 0 + + delegate: WeatherForecastCard { + width: dailyList.cardWidth + height: dailyList.cardHeight + dense: true + daily: true + + date: { + const date = new Date(); + date.setDate(date.getDate() + index); + return date; + } + forecastData: WeatherService.weather.forecast[index] + } + + onCurrentIndexChanged: if (!syncing) + root.syncFrom("day") + + MouseArea { + anchors.fill: parent + onWheel: wheel => { + if (wheel.modifiers & Qt.ShiftModifier) { + if (wheel.angleDelta.y % 120 == 0 && wheel.angleDelta.x == 0) { + const newIndex = dailyList.currentIndex - Math.sign(wheel.angleDelta.y); + if (newIndex < dailyList.model && newIndex >= 0) { + dailyList.currentIndex = newIndex; + wheel.accepted = true; + return; + } + } + } + wheel.accepted = false; + } + } + } + } +} |