import QtQuick 2.15 import QtQuick.Layouts 1.15 import QtQuick.Controls 2.15 import qs.Settings import "root:/Helpers/Weather.js" as WeatherHelper Rectangle { id: weatherRoot width: 440 height: 180 color: "transparent" anchors.horizontalCenterOffset: -2 property string city: Settings.weatherCity !== undefined ? Settings.weatherCity : "" property var weatherData: null property string errorString: "" property bool isVisible: false Component.onCompleted: { if (isVisible) { fetchCityWeather() } } function fetchCityWeather() { WeatherHelper.fetchCityWeather(city, function(result) { weatherData = result.weather; errorString = ""; }, function(err) { errorString = err; } ); } function startWeatherFetch() { isVisible = true fetchCityWeather() } function stopWeatherFetch() { isVisible = false } Rectangle { id: card anchors.fill: parent color: Theme.surface radius: 18 ColumnLayout { anchors.fill: parent anchors.margins: 18 spacing: 12 // Current weather row RowLayout { spacing: 12 Layout.fillWidth: true // Weather icon and basic info RowLayout { spacing: 12 Layout.preferredWidth: 140 // Material Symbol icon Text { id: weatherIcon text: weatherData && weatherData.current_weather ? materialSymbolForCode(weatherData.current_weather.weathercode) : "cloud" font.family: "Material Symbols Outlined" font.pixelSize: 28 verticalAlignment: Text.AlignVCenter color: Theme.accentPrimary Layout.alignment: Qt.AlignVCenter } ColumnLayout { spacing: 2 RowLayout { spacing: 4 Text { text: city font.pixelSize: 14 font.bold: true color: Theme.textPrimary } Text { text: weatherData && weatherData.timezone_abbreviation ? `(${weatherData.timezone_abbreviation})` : "" font.pixelSize: 10 color: Theme.textSecondary leftPadding: 2 } } Text { text: weatherData && weatherData.current_weather ? ((Settings.useFahrenheit !== undefined ? Settings.useFahrenheit : false) ? `${Math.round(weatherData.current_weather.temperature * 9/5 + 32)}°F` : `${Math.round(weatherData.current_weather.temperature)}°C`) : ((Settings.useFahrenheit !== undefined ? Settings.useFahrenheit : false) ? "--°F" : "--°C") font.pixelSize: 24 font.bold: true color: Theme.textPrimary } } } // Spacer to push content to the right Item { Layout.fillWidth: true } } // Separator line Rectangle { width: parent.width height: 1 color: Qt.rgba(Theme.textSecondary.g, Theme.textSecondary.g, Theme.textSecondary.b, 0.12) Layout.fillWidth: true Layout.topMargin: 2 Layout.bottomMargin: 2 } // 5-day forecast row (smaller) RowLayout { spacing: 12 Layout.fillWidth: true Layout.alignment: Qt.AlignHCenter visible: weatherData && weatherData.daily && weatherData.daily.time Repeater { model: weatherData && weatherData.daily && weatherData.daily.time ? 5 : 0 delegate: ColumnLayout { spacing: 2 Layout.alignment: Qt.AlignHCenter Text { // Day name (e.g., Mon) text: Qt.formatDateTime(new Date(weatherData.daily.time[index]), "ddd") font.pixelSize: 12 color: Theme.textSecondary horizontalAlignment: Text.AlignHCenter Layout.alignment: Qt.AlignHCenter } Text { // Material Symbol icon text: materialSymbolForCode(weatherData.daily.weathercode[index]) font.family: "Material Symbols Outlined" font.pixelSize: 22 color: Theme.accentPrimary horizontalAlignment: Text.AlignHCenter Layout.alignment: Qt.AlignHCenter } Text { // High/low temp text: weatherData && weatherData.daily ? ((Settings.useFahrenheit !== undefined ? Settings.useFahrenheit : false) ? `${Math.round(weatherData.daily.temperature_2m_max[index] * 9/5 + 32)}° / ${Math.round(weatherData.daily.temperature_2m_min[index] * 9/5 + 32)}°` : `${Math.round(weatherData.daily.temperature_2m_max[index])}° / ${Math.round(weatherData.daily.temperature_2m_min[index])}°`) : ((Settings.useFahrenheit !== undefined ? Settings.useFahrenheit : false) ? "--° / --°" : "--° / --°") font.pixelSize: 12 color: Theme.textPrimary horizontalAlignment: Text.AlignHCenter Layout.alignment: Qt.AlignHCenter } } } } // Error message (if any) Text { text: errorString color: Theme.error visible: errorString !== "" font.pixelSize: 10 horizontalAlignment: Text.AlignHCenter Layout.alignment: Qt.AlignHCenter } } } // Weather code to Material Symbol ligature function materialSymbolForCode(code) { // Open-Meteo WMO code mapping if (code === 0) return "sunny"; // Clear if (code === 1 || code === 2) return "partly_cloudy_day"; // Mainly clear/partly cloudy if (code === 3) return "cloud"; // Overcast if (code >= 45 && code <= 48) return "foggy"; // Fog if (code >= 51 && code <= 67) return "rainy"; // Drizzle if (code >= 71 && code <= 77) return "weather_snowy"; // Snow if (code >= 80 && code <= 82) return "rainy"; // Rain showers if (code >= 95 && code <= 99) return "thunderstorm"; // Thunderstorm return "cloud"; } function weatherDescriptionForCode(code) { if (code === 0) return "Clear sky"; if (code === 1) return "Mainly clear"; if (code === 2) return "Partly cloudy"; if (code === 3) return "Overcast"; if (code === 45 || code === 48) return "Fog"; if (code >= 51 && code <= 67) return "Drizzle"; if (code >= 71 && code <= 77) return "Snow"; if (code >= 80 && code <= 82) return "Rain showers"; if (code >= 95 && code <= 99) return "Thunderstorm"; return "Unknown"; } }