Commit bb02053a authored by Nicolas Fella's avatar Nicolas Fella Committed by Han Young
Browse files

Simplify hours handling

Like it's done for days
parent 53c696cc
......@@ -10,8 +10,6 @@ set(kweather_SRCS
weatherforecastmanager.cpp
weatherlocationmodel.cpp
weatherlocation.cpp
weatherhourmodel.cpp
weatherhour.cpp
locationquerymodel.cpp
resources.qrc
)
......
......@@ -28,7 +28,6 @@
#include "locationquerymodel.h"
#include "version.h"
#include "weatherforecastmanager.h"
#include "weatherhourmodel.h"
#include "weatherlocation.h"
#include "weatherlocationmodel.h"
......@@ -73,10 +72,6 @@ Q_DECL_EXPORT int main(int argc, char *argv[])
engine.rootContext()->setContextProperty("settingsModel", &settings);
engine.rootContext()->setContextProperty("locationQueryModel", locationQueryModel);
// register QML types
qmlRegisterType<WeatherHour>("kweather", 1, 0, "WeatherHour");
qmlRegisterType<WeatherHourListModel>("kweather", 1, 0, "WeatherHourListModel");
qRegisterMetaType<KWeatherCore::Sunrise>();
// load setup wizard if first launch
......
......@@ -10,7 +10,6 @@ import QtQuick.Controls 2.4
import QtQuick.Layouts 1.2
import QtCharts 2.3
import org.kde.kirigami 2.13 as Kirigami
import kweather 1.0
import "backgrounds"
Kirigami.ScrollablePage {
......@@ -19,6 +18,7 @@ Kirigami.ScrollablePage {
property bool inView: false
property var weatherLocation
property var currentDay: dailyListView.currentItem.weather
property var currentHour: hourlyListView.currentItem.weather
property int maximumContentWidth: Kirigami.Units.gridUnit * 35
......@@ -72,7 +72,7 @@ Kirigami.ScrollablePage {
color: "white"
Layout.alignment: Qt.AlignLeft
horizontalAlignment: Text.AlignLeft
text: weatherLocation.hourListModel.currentForecast == null ? "0" : weatherLocation.hourListModel.currentForecast.temperatureRounded
text: page.currentHour.temperatureRounded
font.family: lightHeadingFont.name
}
Label {
......@@ -81,7 +81,7 @@ Kirigami.ScrollablePage {
color: "white"
Layout.alignment: Qt.AlignLeft
horizontalAlignment: Text.AlignLeft
text: weatherLocation.hourListModel.currentForecast == null ? "Unknown" : weatherLocation.hourListModel.currentForecast.weatherDescription
text: page.currentHour.weatherDescription
font.family: lightHeadingFont.name
}
Label {
......@@ -155,6 +155,10 @@ Kirigami.ScrollablePage {
weather: modelData
textColor: weatherLocation.cardTextColor
}
onCurrentIndexChanged: {
weatherLocation.selectedDay = currentIndex
}
}
}
......@@ -199,10 +203,11 @@ Kirigami.ScrollablePage {
}
contentItem: WeatherStrip {
model: weatherLocation.hourListModel
id: hourlyListView
model: weatherLocation.hourForecasts
delegate: WeatherHourDelegate {
weather: hourItem
weather: modelData
textColor: weatherLocation.cardTextColor
}
}
......
......@@ -10,7 +10,6 @@ import QtQuick.Controls 2.4
import QtQuick.Layouts 1.2
import QtQuick.Shapes 1.12
import org.kde.kirigami 2.11 as Kirigami
import kweather 1.0
Kirigami.ScrollablePage {
id: page
......@@ -20,6 +19,7 @@ Kirigami.ScrollablePage {
property var weatherLocation
property var currentDay: dailyListView.currentItem.weather
property var currentHour: hourlyListView.currentItem.weather
property bool inView: false
......@@ -46,7 +46,7 @@ Kirigami.ScrollablePage {
Layout.alignment: Qt.AlignHCenter
Kirigami.Icon {
id: weatherIcon
source: weatherLocation.hourListModel.currentForecast == null ? "weather-none-available" : weatherLocation.hourListModel.currentForecast.weatherIcon
source: page.currentHour.weatherIcon
Layout.preferredHeight: width
Layout.preferredWidth: page.width * 0.8 - headerText.width
Layout.maximumHeight: Kirigami.Theme.defaultFont.pointSize * 15
......@@ -63,12 +63,12 @@ Kirigami.ScrollablePage {
font.pointSize: Kirigami.Theme.defaultFont.pointSize * 3
font.weight: Font.Light
font.family: lightHeadingFont.name
text: weatherLocation.hourListModel.currentForecast == null ? "0" : weatherLocation.hourListModel.currentForecast.temperatureRounded
text: page.currentHour.temperatureRounded // TODO
}
Label {
font.pointSize: Kirigami.Theme.defaultFont.pointSize * 1.3
font.weight: Font.Bold
text: weatherLocation.hourListModel.currentForecast == null ? "Unknown" : weatherLocation.hourListModel.currentForecast.weatherDescription
text: page.currentHour.weatherDescription
}
Label {
color: Kirigami.Theme.disabledTextColor
......@@ -100,6 +100,7 @@ Kirigami.ScrollablePage {
}
WeatherStrip {
id: dailyListView
Layout.fillWidth: true
Layout.topMargin: Kirigami.Units.largeSpacing * 2
implicitHeight: Kirigami.Units.gridUnit * 8
......@@ -123,6 +124,10 @@ Kirigami.ScrollablePage {
weather: modelData
textColor: Kirigami.Theme.textColor
}
onCurrentIndexChanged: {
weatherLocation.selectedDay = currentIndex
}
}
// hourly view
......@@ -137,14 +142,15 @@ Kirigami.ScrollablePage {
}
WeatherStrip {
id: hourlyListView
implicitHeight: Kirigami.Units.gridUnit * 10.5
Layout.fillWidth: true
Layout.topMargin: Kirigami.Units.largeSpacing * 2
model: weatherLocation.hourListModel
model: weatherLocation.hourForecasts
delegate: WeatherHourDelegate {
weather: hourItem
weather: modelData
textColor: weatherLocation.cardTextColor
}
}
......
......@@ -10,7 +10,6 @@ import QtQuick.Controls 2.4
import QtQuick.Layouts 1.2
import QtCharts 2.3
import org.kde.kirigami 2.13 as Kirigami
import kweather 1.0
import "backgrounds"
Kirigami.Card {
......
......@@ -9,7 +9,6 @@ import QtQuick 2.15
import QtQuick.Controls 2.4
import QtQuick.Layouts 1.2
import org.kde.kirigami 2.12 as Kirigami
import kweather 1.0
Kirigami.ScrollablePage {
title: i18n("Locations")
......
......@@ -10,7 +10,6 @@ import QtQuick.Controls 2.4
import QtQuick.Layouts 1.2
import QtCharts 2.3
import org.kde.kirigami 2.13 as Kirigami
import kweather 1.0
import "backgrounds"
Kirigami.Card {
......
......@@ -10,7 +10,6 @@ import QtQuick.Controls 2.4
import QtQuick.Layouts 1.2
import QtCharts 2.3
import org.kde.kirigami 2.13 as Kirigami
import kweather 1.0
import "backgrounds"
Control {
......
......@@ -10,7 +10,6 @@ import QtQuick.Controls 2.4
import QtQuick.Layouts 1.2
import QtQuick.Shapes 1.12
import org.kde.kirigami 2.11 as Kirigami
import kweather 1.0
Rectangle {
implicitWidth: Kirigami.Units.gridUnit * 6
......@@ -25,7 +24,6 @@ Rectangle {
anchors.fill: parent
onClicked: {
dailyListView.currentIndex = index
weatherLocation.hourListModel.updateHourView(index) // change hour view
}
}
......
......@@ -10,14 +10,13 @@ import QtQuick.Controls 2.4
import QtQuick.Layouts 1.2
import QtQuick.Shapes 1.12
import org.kde.kirigami 2.11 as Kirigami
import kweather 1.0
Rectangle {
implicitWidth: Kirigami.Units.gridUnit * 5
implicitHeight: hourElement.height
color: "transparent"
property WeatherHour weather
property var weather
property color textColor
// actual hour display
......@@ -55,7 +54,7 @@ Rectangle {
}
Label {
color: settingsModel && settingsModel.forecastStyle === "Dynamic" ? KWeatherStyle.disabledTextColor : Kirigami.Theme.disabledTextColor
text: i18n("%1mm", weather.precipitation.toFixed(1))
text: i18n("%1mm", weather.precipitationAmount.toFixed(1))
}
}
......
......@@ -10,7 +10,6 @@ import QtQuick.Controls 2.4
import QtQuick.Layouts 1.2
import QtCharts 2.3
import org.kde.kirigami 2.13 as Kirigami
import kweather 1.0
import "backgrounds"
ListView {
......
/*
* Copyright 2020 2021 Han Young <hanyoung@protonmail.com>
* SPDX-FileCopyrightText: 2020 Devin Lin <espidev@gmail.com>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "weatherhour.h"
#include "global.h"
#include "weatherhourmodel.h"
WeatherHour::WeatherHour(WeatherHourListModel *parent)
: QObject(parent)
{
if (parent)
connect(parent, &WeatherHourListModel::weatherRefresh, this, &WeatherHour::refreshHoursFromForecasts);
}
WeatherHour::WeatherHour(KWeatherCore::WeatherForecast ptr, int day, int hour, WeatherHourListModel *parent)
: QObject(parent)
, m_forecast(std::move(ptr))
, m_day(day)
, m_hour(hour)
{
}
void WeatherHour::refreshHoursFromForecasts(KWeatherCore::WeatherForecast ptr)
{
m_forecast = std::move(ptr);
Q_EMIT propertyChanged();
}
QString WeatherHour::windSpeed() const
{
if (KWeatherSettings().speedUnits() == "kph")
return QString::number(hour().windSpeed(), 'g', 1) + "km/h";
else
return QString::number(hour().windSpeed() * 0.62, 'g', 1) + "mph";
}
const KWeatherCore::HourlyWeatherForecast &WeatherHour::hour() const
{
return m_forecast.dailyWeatherForecast().at(m_day).hourlyWeatherForecast().at(m_hour);
}
/*
* SPDX-FileCopyrightText: 2020 Han Young <hanyoung@protonmail.com>
* SPDX-FileCopyrightText: 2020 Devin Lin <espidev@gmail.com>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "global.h"
#include "kweathersettings.h"
#include <KWeatherCore/WeatherForecast>
#include <QObject>
class WeatherHourListModel;
class WeatherHour : public QObject
{
Q_OBJECT
Q_PROPERTY(QString windDirection READ windDirection NOTIFY propertyChanged)
Q_PROPERTY(QString weatherDescription READ weatherDescription NOTIFY propertyChanged)
Q_PROPERTY(QString weatherIcon READ weatherIcon NOTIFY propertyChanged)
Q_PROPERTY(qreal precipitation READ precipitation NOTIFY propertyChanged)
Q_PROPERTY(qreal fog READ fog NOTIFY propertyChanged)
Q_PROPERTY(QString windSpeed READ windSpeed NOTIFY propertyChanged)
Q_PROPERTY(QString temperature READ temperature NOTIFY propertyChanged)
Q_PROPERTY(QString temperatureRounded READ temperatureRounded NOTIFY propertyChanged)
Q_PROPERTY(qreal humidity READ humidity NOTIFY propertyChanged)
Q_PROPERTY(qreal pressure READ pressure NOTIFY propertyChanged)
Q_PROPERTY(QDateTime date READ date NOTIFY propertyChanged)
public:
WeatherHour(WeatherHourListModel *parent = nullptr);
explicit WeatherHour(KWeatherCore::WeatherForecast ptr, int day, int hour, WeatherHourListModel *parent = nullptr);
QString windDirection() const
{
return hour().windDirectionStr();
}
const QString &weatherDescription() const
{
return hour().weatherDescription();
}
const QString &weatherIcon() const
{
return hour().weatherIcon();
}
qreal precipitation() const
{
return hour().precipitationAmount();
}
qreal fog() const
{
return hour().fog();
}
QString windSpeed() const;
QString temperature() const
{
return QString::number(Kweather::convertTemp(hour().temperature()), 'f', 1) + "°";
}
QString temperatureRounded() const
{
return QString::number(qRound(Kweather::convertTemp(hour().temperature()))) + "°";
}
qreal humidity() const
{
return hour().humidity();
}
qreal pressure() const
{
return hour().pressure();
}
const QDateTime &date() const
{
return hour().date();
}
public Q_SLOTS:
void refreshHoursFromForecasts(KWeatherCore::WeatherForecast ptr);
Q_SIGNALS:
void propertyChanged();
private:
const KWeatherCore::HourlyWeatherForecast &hour() const;
KWeatherCore::WeatherForecast m_forecast;
int m_day = 0;
int m_hour = 0;
};
/*
* SPDX-FileCopyrightText: 2020 Han Young <hanyoung@protonmail.com>
* SPDX-FileCopyrightText: 2020 Devin Lin <espidev@gmail.com>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "weatherhourmodel.h"
#include "weatherlocation.h"
#include <QQmlEngine>
WeatherHourListModel::WeatherHourListModel(WeatherLocation *location)
: QAbstractListModel(location)
{
connect(location, &WeatherLocation::weatherRefresh, this, &WeatherHourListModel::refreshHoursFromForecasts);
}
int WeatherHourListModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
if (m_currentDay >= static_cast<int>(m_hours.size()) || m_currentDay < 0)
return 0;
return m_hours.at(m_currentDay).size();
}
QVariant WeatherHourListModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid() || index.row() < 0 || m_currentDay >= static_cast<int>(m_hours.size())
|| index.row() >= static_cast<int>(m_hours.at(m_currentDay).size())) {
return {};
}
if (role == Roles::HourItemRole) {
return QVariant::fromValue(m_hours.at(m_currentDay).at(index.row()));
}
return {};
}
QHash<int, QByteArray> WeatherHourListModel::roleNames() const
{
return {{Roles::HourItemRole, "hourItem"}};
}
WeatherHour *WeatherHourListModel::get(int index)
{
WeatherHour *ret;
if (index < 0 || m_currentDay >= static_cast<int>(m_hours.size()) || index >= static_cast<int>(m_hours.at(m_currentDay).size())) {
return nullptr;
} else {
ret = m_hours.at(m_currentDay).at(index);
}
// it's kind of dumb how much seems to be garbage collected by js...
// this fixes segfaults with scrolling with the hour view
QQmlEngine::setObjectOwnership(ret, QQmlEngine::CppOwnership);
return ret;
}
void WeatherHourListModel::refreshHoursFromForecasts(KWeatherCore::WeatherForecast forecast)
{
Q_EMIT layoutAboutToBeChanged();
// sync days
if (forecast.dailyWeatherForecast().size() > m_hours.size()) {
auto i = m_hours.size();
for (; i < forecast.dailyWeatherForecast().size(); i++) {
m_hours.push_back({});
const auto &hours = forecast.dailyWeatherForecast().at(i).hourlyWeatherForecast();
m_hours.back().reserve(hours.size());
for (auto j = 0; j < static_cast<int>(hours.size()); j++) {
m_hours.back().push_back(new WeatherHour(forecast, i, j, this));
}
}
} else if (forecast.dailyWeatherForecast().size() < m_hours.size()) {
auto i = m_hours.size() - forecast.dailyWeatherForecast().size();
for (; i > 0; i--) {
auto &hours = m_hours.back();
for (auto it = hours.begin(); it != hours.end(); it++)
(*it)->deleteLater();
m_hours.pop_back();
}
}
// sync hours
const auto &days = forecast.dailyWeatherForecast();
int dayIndex = 0;
for (const auto &day : days) {
const auto &newHours = day.hourlyWeatherForecast();
auto &oldHours = m_hours.at(dayIndex++);
if (newHours.size() > oldHours.size()) {
auto hourIndex = oldHours.size();
for (; hourIndex < newHours.size(); hourIndex++) {
auto *weatherHour = new WeatherHour(forecast, dayIndex, hourIndex, this);
QQmlEngine::setObjectOwnership(weatherHour, QQmlEngine::CppOwnership); // prevent segfaults from js garbage collecting
oldHours.push_back(weatherHour);
}
} else if (newHours.size() < oldHours.size()) {
auto j = oldHours.size() - newHours.size();
while (j--) {
oldHours.back()->deleteLater();
oldHours.pop_back();
}
}
}
m_currentDay = 0;
Q_EMIT weatherRefresh(forecast);
Q_EMIT layoutChanged();
Q_EMIT currentForecastChanged();
}
void WeatherHourListModel::updateHourView(int index)
{
Q_EMIT layoutAboutToBeChanged();
m_currentDay = index;
Q_EMIT layoutChanged();
}
void WeatherHourListModel::updateUi()
{
auto &day = m_hours.at(m_currentDay);
for (auto h : day) {
Q_EMIT h->propertyChanged();
}
Q_EMIT dataChanged(createIndex(0, 0), createIndex(m_hours.at(m_currentDay).size() - 1, 0));
}
WeatherHour *WeatherHourListModel::currentForecast() const
{
if (m_hours.empty() || m_hours.begin()->empty())
return nullptr;
return m_hours.begin()->front();
}
/*
* SPDX-FileCopyrightText: 2020 Han Young <hanyoung@protonmail.com>
* SPDX-FileCopyrightText: 2020 Devin Lin <espidev@gmail.com>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "weatherhour.h"
#include <KWeatherCore/WeatherForecast>
#include <QAbstractListModel>
#include <QObject>
class WeatherLocation;
class WeatherHour;
class WeatherHourListModel : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(WeatherHour *currentForecast READ currentForecast NOTIFY currentForecastChanged)
public:
explicit WeatherHourListModel(WeatherLocation *location = nullptr);
enum Roles { HourItemRole = Qt::UserRole };
int rowCount(const QModelIndex &parent) const override;
QVariant data(const QModelIndex &index, int role) const override;
QHash<int, QByteArray> roleNames() const override;
Q_INVOKABLE WeatherHour *get(int index);
Q_INVOKABLE void updateHourView(int index);
Q_INVOKABLE void updateUi();
WeatherHour *currentForecast() const;
public Q_SLOTS:
void refreshHoursFromForecasts(KWeatherCore::WeatherForecast forecast);
Q_SIGNALS:
void weatherRefresh(KWeatherCore::WeatherForecast ptr);
void currentForecastChanged();
private:
std::vector<std::vector<WeatherHour *>> m_hours;
int m_currentDay = 0;
};
......@@ -35,16 +35,24 @@ WeatherLocation::WeatherLocation(QString locationId,
, m_latitude(latitude)
, m_longitude(longitude)
{
m_weatherHourListModel = new WeatherHourListModel(this);
this->m_timer = new QTimer(this);
connect(m_timer, &QTimer::timeout, this, &WeatherLocation::updateCurrentDateTime);
this->m_timer->start(60 - QDateTime::currentDateTime().currentMSecsSinceEpoch() % 60);
// prevent segfaults from js garbage collection
QQmlEngine::setObjectOwnership(m_weatherHourListModel, QQmlEngine::CppOwnership);
m_lastUpdated = forecast.createdTime();
determineCurrentForecast();
connect(this, &WeatherLocation::selectedDayChanged, this, [this] {
m_hourForecasts.clear();
if (!m_forecast.dailyWeatherForecast().empty()) {
const auto hourForecasts = m_forecast.dailyWeatherForecast()[m_selectedDay].hourlyWeatherForecast();
for (const KWeatherCore::HourlyWeatherForecast &hour : hourForecasts) {
m_hourForecasts << QVariant::fromValue(hour);
}
}
Q_EMIT hourForecastsChanged();
});
updateSeries();
}
WeatherLocation *WeatherLocation::load(const QString &groupName)
......@@ -106,6 +114,15 @@ void WeatherLocation::updateData(KWeatherCore::WeatherForecast forecasts)
updateSeries();
m_lastUpdated = forecasts.createdTime();
m_hourForecasts.clear();
if (!m_forecast.dailyWeatherForecast().empty()) {
const auto hourForecasts = m_forecast.dailyWeatherForecast()[m_selectedDay].hourlyWeatherForecast();
for (const KWeatherCore::HourlyWeatherForecast &hour : hourForecasts) {
m_hourForecasts << QVariant::fromValue(hour);
}
}
Q_EMIT hourForecastsChanged();
emit weatherRefresh(m_forecast);
emit stopLoadingIndicator();
......@@ -214,6 +231,7 @@ void WeatherLocation::updateSeries()
{
m_vector.clear();
m_dayForecasts.clear();
m_hourForecasts.clear();
for (const KWeatherCore::DailyWeatherForecast &day : m_forecast.dailyWeatherForecast()) {
m_dayForecasts << QVariant::fromValue(day);
......
......@@ -6,10 +6,9 @@
*/
#pragma once
#include "weatherhourmodel.h"