diff --git a/modules/lock/WeatherInfo.qml b/modules/lock/WeatherInfo.qml index 85a3113f..0eebc0ab 100644 --- a/modules/lock/WeatherInfo.qml +++ b/modules/lock/WeatherInfo.qml @@ -7,9 +7,15 @@ import qs.services StyledRect { id: root - required property int rootHeight // TODO: add forecast when large height + required property int rootHeight + readonly property bool showForecast: rootHeight >= Tokens.sizes.lock.showForecastHeight - implicitHeight: layout.implicitHeight + Tokens.padding.extraLarge * 2 + implicitHeight: { + const base = brief.implicitHeight + brief.anchors.topMargin; + if (showForecast) + return base + Tokens.spacing.largeIncreased + forecast.implicitHeight + forecast.anchors.margins; + return base + brief.anchors.topMargin; + } radius: Tokens.rounding.extraExtraLarge color: Colours.tPalette.m3surfaceContainer @@ -22,8 +28,24 @@ StyledRect { } BriefInfo { - id: layout + id: brief - anchors.centerIn: parent + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + anchors.topMargin: Tokens.padding.extraLarge + } + + Loader { + id: forecast + + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.margins: Tokens.padding.large + + active: root.showForecast + asynchronous: true + + sourceComponent: Forecast {} } } diff --git a/modules/lock/weather/Forecast.qml b/modules/lock/weather/Forecast.qml new file mode 100644 index 00000000..d23d357e --- /dev/null +++ b/modules/lock/weather/Forecast.qml @@ -0,0 +1,105 @@ +import QtQuick +import QtQuick.Layouts +import M3Shapes +import Caelestia +import Caelestia.Config +import qs.components +import qs.services + +StyledRect { + id: root + + color: Colours.layer(Colours.palette.m3surfaceContainerHigh, 2) + radius: Tokens.rounding.extraLargeIncreased + implicitHeight: header.anchors.margins + header.implicitHeight + Tokens.spacing.medium + layout.implicitHeight + layout.anchors.bottomMargin + + RowLayout { + id: header + + anchors.left: parent.left + anchors.top: parent.top + anchors.margins: Tokens.padding.largeIncreased + + spacing: Tokens.spacing.small + + MaterialIcon { + Layout.topMargin: Math.round(fontInfo.pointSize * 0.12) + text: "schedule" + fontStyle: Tokens.font.icon.builders.medium.weight(title.font.weight).build() + } + + StyledText { + id: title + + text: qsTr("Hourly forecast") + font: Tokens.font.title.medium + } + } + + RowLayout { + id: layout + + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.bottomMargin: Tokens.padding.largeIncreased + anchors.margins: Tokens.padding.large + + spacing: Tokens.spacing.small + + Repeater { + model: CUtils.clamp(Math.floor((layout.width + layout.spacing) / (Tokens.sizes.lock.forecastItemWidth + layout.spacing)), 0, Weather.hourlyForecast.length) + + ColumnLayout { + id: hour + + required property int index + readonly property var cond: Weather.hourlyForecast[index] + + Layout.fillWidth: true + spacing: Tokens.spacing.extraSmall + + MaterialShape { + Layout.alignment: Qt.AlignHCenter + implicitSize: temp.implicitHeight + Tokens.padding.medium * 2 + shape: MaterialShape.Cookie4Sided + color: Qt.alpha(Colours.palette.m3primary, hour.index === 0 ? 1 : 0) + + Behavior on color { + CAnim {} + } + + StyledText { + id: temp + + anchors.centerIn: parent + text: Weather.formatTemp(hour.cond.tempC).slice(0, -1) // Remove C/F + color: hour.index === 0 ? Colours.palette.m3onPrimary : Colours.palette.m3onSurface + font: Tokens.font.title.medium + } + } + + MaterialIcon { + Layout.alignment: Qt.AlignHCenter + text: hour.cond.icon + color: Colours.palette.m3secondary + fontStyle: Tokens.font.icon.large + } + + StyledText { + Layout.alignment: Qt.AlignHCenter + text: hour.cond.precipChance + "%" + color: Colours.palette.m3primary + } + + StyledText { + Layout.topMargin: Tokens.spacing.extraSmall + Layout.alignment: Qt.AlignHCenter + text: hour.index === 0 ? qsTr("Now") : Qt.formatDateTime(new Date(hour.cond.timestamp.replace("T", " ")), GlobalConfig.services.useTwelveHourClock ? "ha" : "hh00") + color: Colours.palette.m3onSurfaceVariant + font: Tokens.font.body.medium + } + } + } + } +} diff --git a/plugin/src/Caelestia/Config/tokens.hpp b/plugin/src/Caelestia/Config/tokens.hpp index 9725556a..d95498bc 100644 --- a/plugin/src/Caelestia/Config/tokens.hpp +++ b/plugin/src/Caelestia/Config/tokens.hpp @@ -285,6 +285,8 @@ class LockTokens : public ConfigObject { CONFIG_PROPERTY(qreal, heightMult, 0.7) CONFIG_PROPERTY(qreal, ratio, 16.0 / 9.0) CONFIG_PROPERTY(int, centerWidth, 600) + CONFIG_PROPERTY(int, showForecastHeight, 975) + CONFIG_PROPERTY(int, forecastItemWidth, 51) public: explicit LockTokens(QObject* parent = nullptr) diff --git a/services/Weather.qml b/services/Weather.qml index 3b1acaaa..f2b36eec 100644 --- a/services/Weather.qml +++ b/services/Weather.qml @@ -150,7 +150,7 @@ Singleton { timestamp: json.hourly.time[i], hour: time.getHours(), tempC: Math.round(json.hourly.temperature_2m[i]), - tempF: Math.round(toFahrenheit(json.hourly.temperature_2m[i])), + precipChance: json.hourly.precipitation_probability[i], weatherCode: json.hourly.weather_code[i], icon: Icons.getWeatherIcon(json.hourly.weather_code[i]) }); @@ -169,7 +169,7 @@ Singleton { const [lat, lon] = loc.split(",").map(s => s.trim()); const baseUrl = "https://api.open-meteo.com/v1/forecast"; - const params = ["latitude=" + lat, "longitude=" + lon, "hourly=weather_code,temperature_2m", "daily=weather_code,temperature_2m_max,temperature_2m_min,sunrise,sunset", "current=temperature_2m,relative_humidity_2m,apparent_temperature,is_day,weather_code,wind_speed_10m", "timezone=auto", "forecast_days=7"]; + const params = ["latitude=" + lat, "longitude=" + lon, "hourly=weather_code,temperature_2m,precipitation_probability", "daily=weather_code,temperature_2m_max,temperature_2m_min,sunrise,sunset", "current=temperature_2m,relative_humidity_2m,apparent_temperature,is_day,weather_code,wind_speed_10m", "timezone=auto", "forecast_days=7"]; return baseUrl + "?" + params.join("&"); }