summaryrefslogtreecommitdiff
path: root/examples
diff options
context:
space:
mode:
authorIvan Solovev <ivan.solovev@qt.io>2021-08-17 16:34:59 +0200
committerIvan Solovev <ivan.solovev@qt.io>2021-08-24 09:44:12 +0200
commit6458b9b60a016bbe0ad6574a1965c49dcd9383f3 (patch)
tree682150248c93291a22a4577754024972f49ddc69 /examples
parent0d908c344c80cfcadb5ea1bb964bd4d8332d8803 (diff)
downloadqtlocation-6458b9b60a016bbe0ad6574a1965c49dcd9383f3.tar.gz
Introduce WeatherApi backend
This patch introduces the weather backend from weatherapi.com and provides the means to switch between backends at runtime. This will be done automatically in case one of the backends does not respond. As we currently use different backends, we had to update the QML part that is responsible for showing the icons. We no longer use the icons from web-site for the forecast, but use the same icon set as for the current weather. This required introducing methods to convert backend-specific weather icon information to a common format used in the application. While on it, a new type of weather icon was introduced (taken from the same icon set). Different weather backends provide weather forecast for different amount of days (due to license restrictions), so the QML code now makes use of the Repeater that dynamically changes the amount of shown days. Task-number: QTBUG-60467 Pick-to: 6.2 Change-Id: Ic9aa3a97ec440dddb38f06edfff8a8434724d118 Reviewed-by: Alex Blasche <alexander.blasche@qt.io>
Diffstat (limited to 'examples')
-rw-r--r--examples/positioning/weatherinfo/CMakeLists.txt2
-rw-r--r--examples/positioning/weatherinfo/appmodel.cpp58
-rw-r--r--examples/positioning/weatherinfo/appmodel.h4
-rw-r--r--examples/positioning/weatherinfo/components/BigForecastIcon.qml3
-rw-r--r--examples/positioning/weatherinfo/components/ForecastIcon.qml2
-rw-r--r--examples/positioning/weatherinfo/components/WeatherIcon.qml52
-rw-r--r--examples/positioning/weatherinfo/icons/qt_attribution.json2
-rw-r--r--examples/positioning/weatherinfo/icons/weather-showers-scattered.pngbin0 -> 76000 bytes
-rw-r--r--examples/positioning/weatherinfo/openweathermapbackend.cpp33
-rw-r--r--examples/positioning/weatherinfo/weatherapibackend.cpp266
-rw-r--r--examples/positioning/weatherinfo/weatherapibackend.h80
-rw-r--r--examples/positioning/weatherinfo/weatherinfo.pro6
-rw-r--r--examples/positioning/weatherinfo/weatherinfo.qml104
-rw-r--r--examples/positioning/weatherinfo/weatherinfo.qrc1
14 files changed, 480 insertions, 133 deletions
diff --git a/examples/positioning/weatherinfo/CMakeLists.txt b/examples/positioning/weatherinfo/CMakeLists.txt
index 01aed5ca..63e13830 100644
--- a/examples/positioning/weatherinfo/CMakeLists.txt
+++ b/examples/positioning/weatherinfo/CMakeLists.txt
@@ -26,6 +26,7 @@ qt_add_executable(weatherinfo
appmodel.cpp appmodel.h
providerbackend.cpp providerbackend.h
openweathermapbackend.cpp openweathermapbackend.h
+ weatherapibackend.cpp weatherapibackend.h
main.cpp
)
set_target_properties(weatherinfo PROPERTIES
@@ -62,6 +63,7 @@ qt_add_qml_module(weatherinfo
icons/weather-sunny-very-few-clouds.png
icons/weather-sunny.png
icons/weather-thundershower.png
+ icons/weather-showers-scattered.png
NO_RESOURCE_TARGET_PATH
)
diff --git a/examples/positioning/weatherinfo/appmodel.cpp b/examples/positioning/weatherinfo/appmodel.cpp
index 0d603b43..09c6cfbb 100644
--- a/examples/positioning/weatherinfo/appmodel.cpp
+++ b/examples/positioning/weatherinfo/appmodel.cpp
@@ -50,6 +50,7 @@
#include "appmodel.h"
#include "openweathermapbackend.h"
+#include "weatherapibackend.h"
#include <QGeoPositionInfoSource>
#include <QGeoPositionInfo>
@@ -232,7 +233,9 @@ public:
bool ready = false;
bool useGps = true;
WeatherDataCache m_dataCache;
- OpenWeatherMapBackend m_openWeatherBackend;
+ ProviderBackend *m_currentBackend = nullptr;
+ QList<ProviderBackend*> m_supportedBackends;
+ qsizetype m_currentBackendIndex = 0;
};
static void forecastAppend(QQmlListProperty<WeatherData> *prop, WeatherData *val)
@@ -269,8 +272,9 @@ AppModel::AppModel(QObject *parent) :
forecastAt,
forecastClear);
- connect(&d->m_openWeatherBackend, &ProviderBackend::weatherInformation,
- this, &AppModel::handleWeatherData);
+ d->m_supportedBackends.push_back(new OpenWeatherMapBackend(this));
+ d->m_supportedBackends.push_back(new WeatherApiBackend(this));
+ registerBackend(0);
//! [1]
d->src = QGeoPositionInfoSource::createDefaultSource(this);
@@ -348,6 +352,23 @@ void AppModel::handleWeatherData(const LocationInfo &location,
d->m_dataCache.addCacheElement(location, weatherDetails);
}
+void AppModel::switchToNextBackend()
+{
+ deregisterCurrentBackend();
+ registerBackend(d->m_currentBackendIndex + 1);
+ if (d->m_currentBackend) {
+ // repeat the query
+ if (d->useGps)
+ requestWeatherByCoordinates();
+ else
+ requestWeatherByCity();
+ } else {
+ qWarning("The application has iterated through all of the weather backends, "
+ "and none of them seems to respond now. Please wait until any of the "
+ "backends becomes available again.");
+ }
+}
+
bool AppModel::applyWeatherData(const QString &city, const QList<WeatherInfo> &weatherDetails)
{
// Check that we didn't get outdated weather data. The city should match,
@@ -394,8 +415,8 @@ void AppModel::requestWeatherByCoordinates()
const auto cacheResult = d->m_dataCache.getWeatherData(d->coord);
if (WeatherDataCache::isCacheResultValid(cacheResult))
applyWeatherData(cacheResult.first, cacheResult.second);
- else
- d->m_openWeatherBackend.requestWeatherInfo(d->coord);
+ else if (d->m_currentBackend)
+ d->m_currentBackend->requestWeatherInfo(d->coord);
}
void AppModel::requestWeatherByCity()
@@ -403,8 +424,31 @@ void AppModel::requestWeatherByCity()
const auto cacheResult = d->m_dataCache.getWeatherData(d->city);
if (WeatherDataCache::isCacheResultValid(cacheResult))
applyWeatherData(cacheResult.first, cacheResult.second);
- else
- d->m_openWeatherBackend.requestWeatherInfo(d->city);
+ else if (d->m_currentBackend)
+ d->m_currentBackend->requestWeatherInfo(d->city);
+}
+
+void AppModel::registerBackend(qsizetype index)
+{
+ if (index >= 0 && index < d->m_supportedBackends.size()) {
+ d->m_currentBackend = d->m_supportedBackends.at(index);
+ d->m_currentBackendIndex = index;
+ connect(d->m_currentBackend, &ProviderBackend::weatherInformation,
+ this, &AppModel::handleWeatherData);
+ connect(d->m_currentBackend, &ProviderBackend::errorOccurred,
+ this, &AppModel::switchToNextBackend);
+ }
+}
+
+void AppModel::deregisterCurrentBackend()
+{
+ if (d->m_currentBackend) {
+ disconnect(d->m_currentBackend, &ProviderBackend::weatherInformation,
+ this, &AppModel::handleWeatherData);
+ disconnect(d->m_currentBackend, &ProviderBackend::errorOccurred,
+ this, &AppModel::switchToNextBackend);
+ d->m_currentBackend = nullptr;
+ }
}
bool AppModel::hasValidCity() const
diff --git a/examples/positioning/weatherinfo/appmodel.h b/examples/positioning/weatherinfo/appmodel.h
index b9d48ef7..73a9d06a 100644
--- a/examples/positioning/weatherinfo/appmodel.h
+++ b/examples/positioning/weatherinfo/appmodel.h
@@ -163,6 +163,7 @@ private slots:
void positionUpdated(QGeoPositionInfo gpsPos);
void positionError(QGeoPositionInfoSource::Error e);
void handleWeatherData(const LocationInfo &location, const QList<WeatherInfo> &weatherDetails);
+ void switchToNextBackend();
//! [3]
signals:
@@ -176,6 +177,9 @@ private:
bool applyWeatherData(const QString &city, const QList<WeatherInfo> &weatherDetails);
void requestWeatherByCoordinates();
void requestWeatherByCity();
+ void registerBackend(qsizetype index);
+ void deregisterCurrentBackend();
+
AppModelPrivate *d;
diff --git a/examples/positioning/weatherinfo/components/BigForecastIcon.qml b/examples/positioning/weatherinfo/components/BigForecastIcon.qml
index 6d2b4c83..9fa854a0 100644
--- a/examples/positioning/weatherinfo/components/BigForecastIcon.qml
+++ b/examples/positioning/weatherinfo/components/BigForecastIcon.qml
@@ -55,7 +55,7 @@ Item {
property string topText: "20*"
property string bottomText: "Mostly cloudy"
- property string weatherIcon: "01d"
+ property string weatherIcon: "sunny"
property real smallSide: (current.width < current.height ? current.width : current.height)
Text {
@@ -71,7 +71,6 @@ Item {
WeatherIcon {
weatherIcon: current.weatherIcon
- useServerIcon: false
anchors.centerIn: parent
anchors.verticalCenterOffset: -15
width: current.smallSide
diff --git a/examples/positioning/weatherinfo/components/ForecastIcon.qml b/examples/positioning/weatherinfo/components/ForecastIcon.qml
index 7e485361..ecf9a463 100644
--- a/examples/positioning/weatherinfo/components/ForecastIcon.qml
+++ b/examples/positioning/weatherinfo/components/ForecastIcon.qml
@@ -54,7 +54,7 @@ Item {
id: top
property string topText: "Mon"
- property string weatherIcon: "01d"
+ property string weatherIcon: "sunny"
property string bottomText: "22*/23*"
Text {
diff --git a/examples/positioning/weatherinfo/components/WeatherIcon.qml b/examples/positioning/weatherinfo/components/WeatherIcon.qml
index 638a52fa..47832c0e 100644
--- a/examples/positioning/weatherinfo/components/WeatherIcon.qml
+++ b/examples/positioning/weatherinfo/components/WeatherIcon.qml
@@ -53,59 +53,11 @@ import QtQuick 2.0
Item {
id: container
- property string weatherIcon: "01d"
-
- //server icons are too small. we keep using the local images
- property bool useServerIcon: true
+ property string weatherIcon: "sunny"
Image {
id: img
- source: {
- if (useServerIcon)
- "http://openweathermap.org/img/w/" + container.weatherIcon + ".png"
- else {
- switch (weatherIcon) {
- case "01d":
- case "01n":
- "../icons/weather-sunny.png"
- break;
- case "02d":
- case "02n":
- "../icons/weather-sunny-very-few-clouds.png"
- break;
- case "03d":
- case "03n":
- "../icons/weather-few-clouds.png"
- break;
- case "04d":
- case "04n":
- "../icons/weather-overcast.png"
- break;
- case "09d":
- case "09n":
- "../icons/weather-showers.png"
- break;
- case "10d":
- case "10n":
- "../icons/weather-showers.png"
- break;
- case "11d":
- case "11n":
- "../icons/weather-thundershower.png"
- break;
- case "13d":
- case "13n":
- "../icons/weather-snow.png"
- break;
- case "50d":
- case "50n":
- "../icons/weather-fog.png"
- break;
- default:
- "../icons/weather-unknown.png"
- }
- }
- }
+ source: "../icons/weather-" + container.weatherIcon + ".png"
smooth: true
anchors.fill: parent
}
diff --git a/examples/positioning/weatherinfo/icons/qt_attribution.json b/examples/positioning/weatherinfo/icons/qt_attribution.json
index 525d3c90..23bbe386 100644
--- a/examples/positioning/weatherinfo/icons/qt_attribution.json
+++ b/examples/positioning/weatherinfo/icons/qt_attribution.json
@@ -23,6 +23,6 @@
"License": "Public Domain",
"Copyright": "Copyright Tango Project contributors",
- "Files": "weather-few-clouds.png weather-overcast.png weather-showers.png weather-storm.png weather-snow.png"
+ "Files": "weather-few-clouds.png weather-overcast.png weather-showers.png weather-storm.png weather-snow.png weather-showers-scattered.png"
}
]
diff --git a/examples/positioning/weatherinfo/icons/weather-showers-scattered.png b/examples/positioning/weatherinfo/icons/weather-showers-scattered.png
new file mode 100644
index 00000000..d3e5b166
--- /dev/null
+++ b/examples/positioning/weatherinfo/icons/weather-showers-scattered.png
Binary files differ
diff --git a/examples/positioning/weatherinfo/openweathermapbackend.cpp b/examples/positioning/weatherinfo/openweathermapbackend.cpp
index 94f2eec3..0a77579b 100644
--- a/examples/positioning/weatherinfo/openweathermapbackend.cpp
+++ b/examples/positioning/weatherinfo/openweathermapbackend.cpp
@@ -69,14 +69,43 @@ static QString niceTemperatureString(double t)
return QString::number(qRound(t - kZeroKelvin)) + QChar(0xB0);
}
+/*
+ Converts weather code to a string that will be used to show the icon.
+ The possible strings are based on the icon names. The icon name is built up
+ as follows:
+ weather-[mystring].png
+ where [mystring] is the value returned by this method.
+ Check resources for the full list of available icons.
+*/
+static QString weatherCodeToString(const QString &code)
+{
+ if (code == u"01d" || code == u"01n")
+ return "sunny";
+ else if (code == u"02d" || code == u"02n")
+ return "sunny-very-few-clouds";
+ else if (code == u"03d" || code == u"03n")
+ return "few-clouds";
+ else if (code == u"04d" || code == u"04n")
+ return "overcast";
+ else if (code == u"09d" || code == u"09n" || code == u"10d" || code == u"10n")
+ return "showers";
+ else if (code == u"11d" || code == u"11n")
+ return "thundershower";
+ else if (code == u"13d" || code == u"13n")
+ return "snow";
+ else if (code == u"50d" || code == u"50n")
+ return "fog";
+
+ return "sunny"; // default choice
+}
+
static void parseWeatherDescription(const QJsonObject &object, WeatherInfo &info)
{
const QJsonArray weatherArray = object.value(u"weather").toArray();
if (!weatherArray.isEmpty()) {
const QJsonObject obj = weatherArray.first().toObject();
info.m_weatherDescription = obj.value(u"description").toString();
- // TODO - convert to some common string
- info.m_weatherIconId = obj.value(u"icon").toString();
+ info.m_weatherIconId = weatherCodeToString(obj.value(u"icon").toString());
} else {
qCDebug(requestsLog, "An empty weather array is returned.");
}
diff --git a/examples/positioning/weatherinfo/weatherapibackend.cpp b/examples/positioning/weatherinfo/weatherapibackend.cpp
new file mode 100644
index 00000000..a98f7dbb
--- /dev/null
+++ b/examples/positioning/weatherinfo/weatherapibackend.cpp
@@ -0,0 +1,266 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "weatherapibackend.h"
+
+#include <QNetworkAccessManager>
+#include <QNetworkReply>
+#include <QUrlQuery>
+#include <QJsonDocument>
+#include <QJsonObject>
+#include <QJsonValue>
+#include <QJsonArray>
+#include <QLoggingCategory>
+
+Q_DECLARE_LOGGING_CATEGORY(requestsLog)
+
+static QString niceTemperatureString(double t)
+{
+ return QString::number(qRound(t)) + QChar(0xB0);
+}
+
+/*
+ Converts weather code to a string that will be used to show the icon.
+ The possible strings are based on the icon names. The icon name is built up
+ as follows:
+ weather-[mystring].png
+ where [mystring] is the value returned by this method.
+ Check resources for the full list of available icons.
+*/
+static QString weatherCodeToString(int code)
+{
+ switch (code) {
+ case 1000:
+ return "sunny";
+ case 1003:
+ return "sunny-very-few-clouds";
+ case 1006:
+ return "few-clouds";
+ case 1009:
+ return "overcast";
+ case 1030:
+ case 1135:
+ case 1147:
+ return "fog";
+ case 1063:
+ case 1072:
+ case 1150:
+ case 1153:
+ case 1168:
+ case 1171:
+ case 1180:
+ case 1183:
+ case 1186:
+ case 1189:
+ case 1198:
+ return "showers-scattered";
+ case 1066:
+ case 1069:
+ case 1114:
+ case 1117:
+ case 1210:
+ case 1213:
+ case 1216:
+ case 1219:
+ case 1222:
+ case 1225:
+ case 1237:
+ case 1255:
+ case 1258:
+ case 1261:
+ case 1264:
+ case 1279:
+ case 1282:
+ return "snow";
+ case 1087:
+ return "storm";
+ case 1192:
+ case 1195:
+ case 1201:
+ case 1240:
+ case 1243:
+ case 1246:
+ return "showers";
+ case 1204:
+ case 1207:
+ case 1249:
+ case 1252:
+ return "sleet";
+ case 1273:
+ case 1276:
+ return "thundershower";
+ default:
+ return "sunny";
+ }
+
+ return "sunny"; // default choice
+}
+
+static void parseWeatherDescription(const QJsonObject &object, WeatherInfo &info)
+{
+ const QJsonObject conditionObject = object.value(u"condition").toObject();
+ info.m_weatherDescription = conditionObject.value(u"text").toString();
+ info.m_weatherIconId = weatherCodeToString(conditionObject.value(u"code").toInt());
+}
+
+WeatherApiBackend::WeatherApiBackend(QObject *parent)
+ : ProviderBackend(parent),
+ m_networkManager(new QNetworkAccessManager(this)),
+ m_apiKey(QStringLiteral("8edde160c63c4be6b77101828211208"))
+{
+}
+
+void WeatherApiBackend::requestWeatherInfo(const QString &city)
+{
+ generateWeatherRequest(city, QGeoCoordinate());
+}
+
+void WeatherApiBackend::requestWeatherInfo(const QGeoCoordinate &coordinate)
+{
+ const QString coordinateStr =
+ QString("%1,%2").arg(coordinate.latitude()).arg(coordinate.longitude());
+ generateWeatherRequest(coordinateStr, coordinate);
+}
+
+void WeatherApiBackend::handleWeatherForecastReply(QNetworkReply *reply,
+ const QGeoCoordinate &coordinate)
+{
+ if (!reply) {
+ emit errorOccurred();
+ return;
+ }
+ bool parsed = false;
+ if (!reply->error()) {
+ QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
+ const QJsonObject documentObject = document.object();
+ const QJsonObject locationObject = documentObject.value(u"location").toObject();
+
+ // extract location name
+ LocationInfo location;
+ location.m_name = locationObject.value(u"name").toString();
+ if (coordinate.isValid())
+ location.m_coordinate = coordinate;
+ qCDebug(requestsLog) << "Got weather for" << location.m_name;
+
+ // extract current weather
+ WeatherInfo currentWeather;
+
+ const QJsonObject currentWeatherObject = documentObject.value(u"current").toObject();
+ const QJsonValue temperature = currentWeatherObject.value(u"temp_c");
+ if (temperature.isDouble())
+ currentWeather.m_temperature = niceTemperatureString(temperature.toDouble());
+
+ parseWeatherDescription(currentWeatherObject, currentWeather);
+
+ parsed = !location.m_name.isEmpty() && !currentWeather.m_temperature.isEmpty();
+
+ if (parsed) {
+ QList<WeatherInfo> weatherDetails;
+ weatherDetails << currentWeather;
+
+ // extract forecast details
+ const QJsonObject forecastObject = documentObject.value(u"forecast").toObject();
+ const QJsonArray forecastDays = forecastObject.value(u"forecastday").toArray();
+ for (qsizetype i = 0; i < forecastDays.size(); ++i) {
+ const QJsonObject dayInfo = forecastDays.at(i).toObject();
+ WeatherInfo info;
+
+ const QDateTime dt =
+ QDateTime::fromSecsSinceEpoch(dayInfo.value(u"date_epoch").toInteger());
+ info.m_dayOfWeek = dt.toString(u"ddd");
+
+ const QJsonObject dayObject = dayInfo.value(u"day").toObject();
+ const QJsonValue minTemp = dayObject.value(u"mintemp_c");
+ const QJsonValue maxTemp = dayObject.value(u"maxtemp_c");
+ if (minTemp.isDouble() && maxTemp.isDouble()) {
+ info.m_temperature = niceTemperatureString(minTemp.toDouble()) + QChar('/')
+ + niceTemperatureString(maxTemp.toDouble());
+ } else {
+ qCDebug(requestsLog, "Failed to parse min or max temperature.");
+ }
+
+ parseWeatherDescription(dayObject, info);
+
+ if (!info.m_temperature.isEmpty() && !info.m_weatherIconId.isEmpty())
+ weatherDetails.push_back(info);
+ }
+
+ emit weatherInformation(location, weatherDetails);
+ }
+ }
+ if (!parsed) {
+ emit errorOccurred();
+ if (reply->error())
+ qCDebug(requestsLog) << reply->errorString();
+ else
+ qCDebug(requestsLog, "Failed to parse weather JSON.");
+ }
+
+ reply->deleteLater();
+}
+
+void WeatherApiBackend::generateWeatherRequest(const QString &locationString,
+ const QGeoCoordinate &coordinate)
+{
+ QUrl url("https://api.weatherapi.com/v1/forecast.json");
+
+ QUrlQuery query;
+ query.addQueryItem(QStringLiteral("key"), m_apiKey);
+ query.addQueryItem(QStringLiteral("q"), locationString);
+ query.addQueryItem(QStringLiteral("days"), QStringLiteral("4"));
+ query.addQueryItem(QStringLiteral("aqi"), QStringLiteral("no"));
+ query.addQueryItem(QStringLiteral("alerts"), QStringLiteral("no"));
+
+ url.setQuery(query);
+
+ QNetworkReply *reply = m_networkManager->get(QNetworkRequest(url));
+ connect(reply, &QNetworkReply::finished, this,
+ [this, reply, coordinate]() { handleWeatherForecastReply(reply, coordinate); });
+}
diff --git a/examples/positioning/weatherinfo/weatherapibackend.h b/examples/positioning/weatherinfo/weatherapibackend.h
new file mode 100644
index 00000000..dce078a1
--- /dev/null
+++ b/examples/positioning/weatherinfo/weatherapibackend.h
@@ -0,0 +1,80 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef WEATHERAPIBACKEND_H
+#define WEATHERAPIBACKEND_H
+
+#include "providerbackend.h"
+
+QT_BEGIN_NAMESPACE
+class QNetworkAccessManager;
+class QNetworkReply;
+QT_END_NAMESPACE
+
+class WeatherApiBackend : public ProviderBackend
+{
+ Q_OBJECT
+public:
+ explicit WeatherApiBackend(QObject *parent = nullptr);
+
+ void requestWeatherInfo(const QString &city) override;
+ void requestWeatherInfo(const QGeoCoordinate &coordinate) override;
+
+private slots:
+ void handleWeatherForecastReply(QNetworkReply *reply, const QGeoCoordinate &coordinate);
+
+private:
+ void generateWeatherRequest(const QString &locationString, const QGeoCoordinate &coordinate);
+
+ QNetworkAccessManager *m_networkManager;
+ const QString m_apiKey;
+};
+
+#endif // WEATHERAPIBACKEND_H
diff --git a/examples/positioning/weatherinfo/weatherinfo.pro b/examples/positioning/weatherinfo/weatherinfo.pro
index b3ab37b2..eb88ca42 100644
--- a/examples/positioning/weatherinfo/weatherinfo.pro
+++ b/examples/positioning/weatherinfo/weatherinfo.pro
@@ -10,7 +10,8 @@ QML_IMPORT_MAJOR_VERSION = 1
SOURCES += main.cpp \
appmodel.cpp \
openweathermapbackend.cpp \
- providerbackend.cpp
+ providerbackend.cpp \
+ weatherapibackend.cpp
OTHER_FILES += weatherinfo.qml \
components/WeatherIcon.qml \
@@ -23,7 +24,8 @@ RESOURCES += weatherinfo.qrc
HEADERS += appmodel.h \
openweathermapbackend.h \
- providerbackend.h
+ providerbackend.h \
+ weatherapibackend.h
target.path = $$[QT_INSTALL_EXAMPLES]/positioning/weatherinfo
INSTALLS += target
diff --git a/examples/positioning/weatherinfo/weatherinfo.qml b/examples/positioning/weatherinfo/weatherinfo.qml
index 730ae754..62bbe7d7 100644
--- a/examples/positioning/weatherinfo/weatherinfo.qml
+++ b/examples/positioning/weatherinfo/weatherinfo.qml
@@ -75,9 +75,9 @@ Item {
]
//! [1]
AppModel {
- id: model
+ id: appModel
onReadyChanged: {
- if (model.ready)
+ if (appModel.ready)
window.state = "ready"
else
window.state = "loading"
@@ -113,7 +113,7 @@ Item {
color: "lightgrey"
Text {
- text: (model.hasValidCity ? model.city : "Unknown location") + (model.useGps ? " (GPS)" : "")
+ text: (appModel.hasValidCity ? appModel.city : "Unknown location") + (appModel.useGps ? " (GPS)" : "")
anchors.fill: parent
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
@@ -122,22 +122,22 @@ Item {
MouseArea {
anchors.fill: parent
onClicked: {
- if (model.useGps) {
- model.useGps = false
- model.city = "Brisbane"
+ if (appModel.useGps) {
+ appModel.useGps = false
+ appModel.city = "Brisbane"
} else {
- switch (model.city) {
+ switch (appModel.city) {
case "Brisbane":
- model.city = "Oslo"
+ appModel.city = "Oslo"
break
case "Oslo":
- model.city = "Helsinki"
+ appModel.city = "Helsinki"
break
case "Helsinki":
- model.city = "New York"
+ appModel.city = "New York"
break
case "New York":
- model.useGps = true
+ appModel.useGps = true
break
}
}
@@ -152,21 +152,21 @@ Item {
width: main.width -12
height: 2 * (main.height - 25 - 12) / 3
- weatherIcon: (model.hasValidWeather
- ? model.weather.weatherIcon
- : "01d")
+ weatherIcon: (appModel.hasValidWeather
+ ? appModel.weather.weatherIcon
+ : "sunny")
//! [3]
- topText: (model.hasValidWeather
- ? model.weather.temperature
+ topText: (appModel.hasValidWeather
+ ? appModel.weather.temperature
: "??")
- bottomText: (model.hasValidWeather
- ? model.weather.weatherDescription
+ bottomText: (appModel.hasValidWeather
+ ? appModel.weather.weatherDescription
: "No weather data")
MouseArea {
anchors.fill: parent
onClicked: {
- model.refreshWeather()
+ appModel.refreshWeather()
}
}
//! [4]
@@ -180,58 +180,26 @@ Item {
width: main.width - 12
height: (main.height - 25 - 24) / 3
- property real iconWidth: iconRow.width / 4 - 10
+ property int daysCount: appModel.forecast.length
+ property real iconWidth: (daysCount > 0) ? (iconRow.width / daysCount) - 10
+ : iconRow.width
property real iconHeight: iconRow.height
- ForecastIcon {
- id: forecast1
- width: iconRow.iconWidth
- height: iconRow.iconHeight
-
- topText: (model.hasValidWeather ?
- model.forecast[0].dayOfWeek : "??")
- bottomText: (model.hasValidWeather ?
- model.forecast[0].temperature : "??/??")
- weatherIcon: (model.hasValidWeather ?
- model.forecast[0].weatherIcon : "01d")
- }
- ForecastIcon {
- id: forecast2
- width: iconRow.iconWidth
- height: iconRow.iconHeight
-
- topText: (model.hasValidWeather ?
- model.forecast[1].dayOfWeek : "??")
- bottomText: (model.hasValidWeather ?
- model.forecast[1].temperature : "??/??")
- weatherIcon: (model.hasValidWeather ?
- model.forecast[1].weatherIcon : "01d")
- }
- ForecastIcon {
- id: forecast3
- width: iconRow.iconWidth
- height: iconRow.iconHeight
-
- topText: (model.hasValidWeather ?
- model.forecast[2].dayOfWeek : "??")
- bottomText: (model.hasValidWeather ?
- model.forecast[2].temperature : "??/??")
- weatherIcon: (model.hasValidWeather ?
- model.forecast[2].weatherIcon : "01d")
- }
- ForecastIcon {
- id: forecast4
- width: iconRow.iconWidth
- height: iconRow.iconHeight
-
- topText: (model.hasValidWeather ?
- model.forecast[3].dayOfWeek : "??")
- bottomText: (model.hasValidWeather ?
- model.forecast[3].temperature : "??/??")
- weatherIcon: (model.hasValidWeather ?
- model.forecast[3].weatherIcon : "01d")
+ Repeater {
+ model: appModel.forecast
+ ForecastIcon {
+ id: forecast1
+ width: iconRow.iconWidth
+ height: iconRow.iconHeight
+
+ topText: (appModel.hasValidWeather ?
+ modelData.dayOfWeek : "??")
+ bottomText: (appModel.hasValidWeather ?
+ modelData.temperature : "??/??")
+ weatherIcon: (appModel.hasValidWeather ?
+ modelData.weatherIcon : "sunny")
+ }
}
-
}
}
}
diff --git a/examples/positioning/weatherinfo/weatherinfo.qrc b/examples/positioning/weatherinfo/weatherinfo.qrc
index 7b79dbea..1578fa9d 100644
--- a/examples/positioning/weatherinfo/weatherinfo.qrc
+++ b/examples/positioning/weatherinfo/weatherinfo.qrc
@@ -16,5 +16,6 @@
<file>icons/weather-sunny-very-few-clouds.png</file>
<file>icons/weather-sunny.png</file>
<file>icons/weather-thundershower.png</file>
+ <file>icons/weather-showers-scattered.png</file>
</qresource>
</RCC>