├── .craft.ini ├── .flatpak-manifest.json ├── .flatpak-manifest.json.license ├── .gitignore ├── .gitlab-ci.yml ├── .gitlab-ci.yml.license ├── .kde-ci.yml ├── CMakeLists.txt ├── CMakePresets.json ├── CMakePresets.json.license ├── LICENSES ├── Apache-2.0.txt ├── BSD-3-Clause.txt ├── CC-BY-4.0.txt ├── CC0-1.0.txt ├── GPL-2.0-or-later.txt └── LGPL-2.1-or-later.txt ├── README.md ├── REUSE.toml ├── android ├── AndroidManifest.xml ├── build.gradle ├── ic_launcher-playstore.png └── res │ ├── drawable-v24 │ ├── ic_launcher_background.xml │ └── ic_launcher_foreground.xml │ ├── drawable │ ├── kweather.png │ └── splash.xml │ ├── mipmap-anydpi-v26 │ └── ic_launcher.xml │ ├── mipmap-hdpi │ └── ic_launcher.png │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ └── ic_launcher.png │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ └── mipmap-xxxhdpi │ └── ic_launcher.png ├── org.kde.kweather.appdata.xml ├── org.kde.kweather.desktop ├── org.kde.kweather.service.in ├── org.kde.kweather.service.in.license ├── org.kde.kweather.svg ├── org.kde.kweather.svg.license ├── po ├── ar │ └── kweather.po ├── ast │ └── kweather.po ├── az │ └── kweather.po ├── bg │ └── kweather.po ├── ca │ └── kweather.po ├── ca@valencia │ └── kweather.po ├── cs │ └── kweather.po ├── de │ └── kweather.po ├── el │ └── kweather.po ├── en_GB │ └── kweather.po ├── eo │ └── kweather.po ├── es │ └── kweather.po ├── et │ └── kweather.po ├── eu │ └── kweather.po ├── fi │ └── kweather.po ├── fr │ └── kweather.po ├── gl │ └── kweather.po ├── he │ └── kweather.po ├── hi │ └── kweather.po ├── hu │ └── kweather.po ├── ia │ └── kweather.po ├── is │ └── kweather.po ├── it │ └── kweather.po ├── ja │ └── kweather.po ├── ka │ └── kweather.po ├── ko │ └── kweather.po ├── lt │ └── kweather.po ├── lv │ └── kweather.po ├── nb │ └── kweather.po ├── nl │ └── kweather.po ├── nn │ └── kweather.po ├── pa │ └── kweather.po ├── pl │ └── kweather.po ├── pt │ └── kweather.po ├── pt_BR │ └── kweather.po ├── ru │ └── kweather.po ├── sa │ └── kweather.po ├── sk │ └── kweather.po ├── sl │ └── kweather.po ├── sv │ └── kweather.po ├── tr │ └── kweather.po ├── uk │ └── kweather.po ├── zh_CN │ └── kweather.po └── zh_TW │ └── kweather.po ├── snapcraft.yaml └── src ├── CMakeLists.txt ├── Messages.sh ├── formatter.cpp ├── formatter.h ├── global.h ├── kweathersettings.kcfg ├── kweathersettings.kcfgc ├── kweathersettings.kcfgc.license ├── locationquerymodel.cpp ├── locationquerymodel.h ├── main.cpp ├── plasmoid ├── CMakeLists.txt ├── hourlymodel.cpp ├── hourlymodel.h ├── kweather_1x4.cpp ├── kweather_1x4.h └── package │ ├── contents │ └── ui │ │ ├── LocationSelector.qml │ │ ├── WeatherContainer.qml │ │ └── main.qml │ ├── metadata.json │ └── metadata.json.license ├── qml ├── DefaultPage.qml ├── DynamicForecastPage.qml ├── FlatForecastPage.qml ├── FlatLocationForecast.qml ├── ForecastContainerPage.qml ├── InfoCard.qml ├── Main.qml ├── SetupWizard.qml ├── SunriseCard.qml ├── TemperatureChartCard.qml ├── WeatherDayDelegate.qml ├── WeatherHourDelegate.qml ├── WeatherStrip.qml ├── backgrounds │ ├── ClearDay.qml │ ├── ClearNight.qml │ ├── CloudyDay.qml │ ├── CloudyNight.qml │ ├── DynamicBackground.qml │ ├── Misty.qml │ ├── PartlyCloudyDay.qml │ ├── PartlyCloudyNight.qml │ ├── RainyDay.qml │ ├── RainyNight.qml │ ├── SnowyDay.qml │ ├── SnowyNight.qml │ └── components │ │ ├── Cloud.qml │ │ ├── Cloudy.qml │ │ ├── Rain.qml │ │ ├── Snow.qml │ │ ├── Stars.qml │ │ └── Sun.qml ├── components │ └── ListDelegate.qml ├── locationslist │ ├── AddLocationDialog.qml │ ├── AddLocationListView.qml │ ├── AddLocationPage.qml │ ├── LocationsListDialog.qml │ ├── LocationsListPage.qml │ └── LocationsListView.qml └── settings │ ├── PrecipitationUnitComboBox.qml │ ├── PressureUnitComboBox.qml │ ├── SettingsComponent.qml │ ├── SettingsDialog.qml │ ├── SettingsPage.qml │ ├── SettingsWindow.qml │ ├── SpeedUnitComboBox.qml │ └── TemperatureUnitComboBox.qml ├── resources ├── CMakeLists.txt ├── NotoSans-Light.ttf ├── NotoSans-Light.ttf.license ├── kweather.svg └── kweather.svg.license ├── temperaturechartdata.cpp ├── temperaturechartdata.h ├── weatherbackground.cpp ├── weatherbackground.h ├── weatherlocation.cpp ├── weatherlocation.h ├── weatherlocationlistmodel.cpp └── weatherlocationlistmodel.h /.craft.ini: -------------------------------------------------------------------------------- 1 | ; SPDX-FileCopyrightText: None 2 | ; SPDX-License-Identifier: CC0-1.0 3 | 4 | [BlueprintSettings] 5 | kde/libs.version=master 6 | libs/qt.qtMajorVersion=6 7 | -------------------------------------------------------------------------------- /.flatpak-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "org.kde.kweather", 3 | "branch": "master", 4 | "runtime": "org.kde.Platform", 5 | "runtime-version": "6.8", 6 | "sdk": "org.kde.Sdk", 7 | "command": "kweather", 8 | "tags": ["nightly"], 9 | "desktop-file-name-suffix": " (Nightly)", 10 | "finish-args": [ 11 | "--share=ipc", 12 | "--share=network", 13 | "--socket=fallback-x11", 14 | "--socket=wayland", 15 | "--device=dri", 16 | "--system-talk-name=org.freedesktop.GeoClue2" 17 | ], 18 | "separate-locales": false, 19 | 20 | "modules": [ 21 | { 22 | "name": "kweathercore", 23 | "config-opts": [ "-DBUILD_TESTING=OFF" ], 24 | "buildsystem": "cmake-ninja", 25 | "sources": [ { "type": "git", "url": "https://invent.kde.org/libraries/kweathercore.git", "branch": "master" } ] 26 | }, 27 | { 28 | "name": "kirigamiaddons", 29 | "config-opts": [ "-DBUILD_TESTING=OFF" ], 30 | "buildsystem": "cmake-ninja", 31 | "sources": [ { "type": "git", "url": "https://invent.kde.org/libraries/kirigami-addons.git", "branch": "master" } ] 32 | }, 33 | { 34 | "name": "kweather", 35 | "config-opts": [ "-DBUILD_PLASMOID=OFF" ], 36 | "buildsystem": "cmake-ninja", 37 | "builddir": true, 38 | "sources": [ { "type": "dir", "path": ".", "skip": [".git"] } ] 39 | } 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /.flatpak-manifest.json.license: -------------------------------------------------------------------------------- 1 | Copyright 2020 Han Young 2 | Copyright 2020 Devin Lin 3 | SPDX-License-Identifier: GPL-2.0-or-later 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2020 Han Young 3 | # Copyright 2020 Devin Lin 4 | # 5 | # SPDX-License-Identifier: GPL-2.0-or-later 6 | # 7 | 8 | /compile_commands.json 9 | .cache/ 10 | *.qmlc 11 | build*/ 12 | .kdev4/ 13 | .idea/ 14 | kweather.kdev4 15 | CMakeLists.txt.user 16 | 17 | /cmake-build-debug 18 | /test.sh 19 | /flatpak-build-desktop 20 | /.flatpak-builder 21 | /mobiletest.sh 22 | 23 | kweather.cflags 24 | kweather.config 25 | kweather.creator 26 | kweather.creator.user 27 | kweather.cxxflags 28 | kweather.files 29 | kweather.includes 30 | .clang-format 31 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | include: 2 | - project: sysadmin/ci-utilities 3 | file: 4 | - /gitlab-templates/reuse-lint.yml 5 | - /gitlab-templates/linux-qt6.yml 6 | - /gitlab-templates/linux-qt6-next.yml 7 | - /gitlab-templates/android-qt6.yml 8 | - /gitlab-templates/freebsd-qt6.yml 9 | - /gitlab-templates/flatpak.yml 10 | - /gitlab-templates/craft-android-qt6-apks.yml 11 | - /gitlab-templates/snap-snapcraft-lxd.yml 12 | - /gitlab-templates/clang-format.yml 13 | - /gitlab-templates/xml-lint.yml 14 | - /gitlab-templates/yaml-lint.yml 15 | -------------------------------------------------------------------------------- /.gitlab-ci.yml.license: -------------------------------------------------------------------------------- 1 | Copyright 2021 Han Young 2 | Copyright 2021 Devin Lin 3 | SPDX-License-Identifier: CC-BY-4.0 4 | -------------------------------------------------------------------------------- /.kde-ci.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: None 2 | # SPDX-License-Identifier: CC0-1.0 3 | 4 | Dependencies: 5 | - 'on': ['@all'] 6 | 'require': 7 | 'frameworks/extra-cmake-modules': '@latest-kf6' 8 | 'frameworks/kcoreaddons': '@latest-kf6' 9 | 'frameworks/kconfig': '@latest-kf6' 10 | 'frameworks/ki18n': '@latest-kf6' 11 | 'frameworks/kirigami': '@latest-kf6' 12 | 'frameworks/knotifications': '@latest-kf6' 13 | 'libraries/kirigami-addons': '@latest-kf6' 14 | 'libraries/kweathercore': '@latest-kf6' 15 | 16 | - 'on': ['Linux', 'FreeBSD'] 17 | 'require': 18 | 'plasma/libplasma': '@latest-kf6' 19 | 'frameworks/kcrash': '@latest-kf6' 20 | 21 | Options: 22 | require-passing-tests-on: ['@all'] 23 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # SPDX-FileCopyrightText: 2020 Han Young 3 | # SPDX-FileCopyrightText: 2020 Devin Lin 4 | # 5 | # SPDX-License-Identifier: GPL-2.0-or-later 6 | # 7 | cmake_minimum_required(VERSION 3.16) 8 | 9 | # KDE Applications version, managed by release script. 10 | set(RELEASE_SERVICE_VERSION_MAJOR "25") 11 | set(RELEASE_SERVICE_VERSION_MINOR "07") 12 | set(RELEASE_SERVICE_VERSION_MICRO "70") 13 | set(RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_VERSION_MINOR}.${RELEASE_SERVICE_VERSION_MICRO}") 14 | 15 | project(kweather VERSION ${RELEASE_SERVICE_VERSION}) 16 | 17 | set(QT_MIN_VERSION "6.5.0") 18 | set(KF_MIN_VERSION "6.12.0") 19 | 20 | set(CMAKE_CXX_STANDARD 17) 21 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 22 | 23 | option(BUILD_PLASMOID "Build the weather plasmoid" ON) 24 | 25 | include(FeatureSummary) 26 | 27 | find_package(ECM ${KF_MIN_VERSION} REQUIRED) 28 | 29 | # where to look first for cmake modules, before ${CMAKE_ROOT}/Modules/ is checked 30 | set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH}) 31 | 32 | include(ECMSetupVersion) 33 | include(ECMGenerateHeaders) 34 | include(ECMPoQmTools) 35 | include(ECMCheckOutboundLicense) 36 | include(ECMFindQmlModule) 37 | include(KDEInstallDirs) 38 | include(KDECMakeSettings) 39 | include(KDECompilerSettings NO_POLICY_SCOPE) 40 | include(KDEGitCommitHooks) 41 | include(KDEClangFormat) 42 | include(ECMQmlModule) 43 | include(ECMAddAndroidApk) 44 | include(ECMDeprecationSettings) 45 | 46 | ecm_set_disabled_deprecation_versions(QT 6.8.0 47 | KF 6.14.0 48 | ) 49 | 50 | # clang-format 51 | file(GLOB_RECURSE ALL_CLANG_FORMAT_SOURCE_FILES src/*.cpp src/*.h) 52 | kde_clang_format(${ALL_CLANG_FORMAT_SOURCE_FILES}) 53 | 54 | ecm_setup_version(${PROJECT_VERSION} 55 | VARIABLE_PREFIX KWEATHER 56 | VERSION_HEADER ${CMAKE_CURRENT_BINARY_DIR}/version.h 57 | ) 58 | 59 | find_package(Qt6 ${QT_MIN_VERSION} REQUIRED NO_MODULE COMPONENTS 60 | Core 61 | Quick 62 | Test 63 | Gui 64 | Svg 65 | QuickControls2 66 | Charts 67 | OpenGL 68 | ) 69 | find_package(KF6 ${KF_MIN_VERSION} REQUIRED COMPONENTS 70 | Config 71 | Kirigami 72 | I18n 73 | CoreAddons 74 | Notifications 75 | ) 76 | find_package(KWeatherCore 0.8.0 REQUIRED) 77 | 78 | find_package(KF6KirigamiAddons 0.11 REQUIRED) 79 | 80 | if (ANDROID) 81 | find_package(OpenSSL REQUIRED) 82 | else () 83 | find_package(Qt6 ${QT_MIN_VERSION} REQUIRED NO_MODULE COMPONENTS Widgets) 84 | find_package(KF6 ${KF_MIN_VERSION} REQUIRED COMPONENTS Crash) 85 | endif() 86 | 87 | if (NOT ANDROID AND BUILD_PLASMOID) 88 | find_package(Plasma REQUIRED) 89 | endif() 90 | 91 | ecm_find_qmlmodule(org.kde.kholidays 1.0) 92 | add_definitions(-DQT_NO_CONTEXTLESS_CONNECT) 93 | add_subdirectory(src) 94 | 95 | ki18n_install(po) 96 | 97 | if (ANDROID) 98 | install(FILES org.kde.kweather.svg DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/kirigami/breeze-internal/icons/) 99 | else() 100 | install(PROGRAMS org.kde.kweather.desktop DESTINATION ${KDE_INSTALL_APPDIR}) 101 | install(FILES org.kde.kweather.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR}) 102 | install(FILES org.kde.kweather.svg DESTINATION ${KDE_INSTALL_FULL_ICONDIR}/hicolor/scalable/apps/) 103 | 104 | # DBus 105 | configure_file(org.kde.kweather.service.in 106 | ${CMAKE_CURRENT_BINARY_DIR}/org.kde.kweather.service) 107 | install(FILES ${CMAKE_CURRENT_BINARY_DIR}/org.kde.kweather.service 108 | DESTINATION ${KDE_INSTALL_DBUSSERVICEDIR}) 109 | endif() 110 | 111 | feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) 112 | 113 | file(GLOB_RECURSE ALL_SOURCE_FILES src/*.cpp src/*.h src/*.qml) 114 | ecm_check_outbound_license(LICENSES GPL-2.0-only GPL-3.0-only FILES ${ALL_SOURCE_FILES}) 115 | 116 | kde_configure_git_pre_commit_hook(CHECKS CLANG_FORMAT) 117 | -------------------------------------------------------------------------------- /CMakePresets.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "configurePresets": [ 4 | { 5 | "name": "base", 6 | "displayName": "base preset", 7 | "generator": "Ninja", 8 | "binaryDir": "${sourceDir}/build-${presetName}", 9 | "installDir": "$env{KF6}", 10 | "hidden": true, 11 | "cacheVariables": { 12 | "BUILD_QCH": "ON" 13 | } 14 | }, 15 | { 16 | "name": "dev-mold", 17 | "displayName": "Build as debug + using mold linker", 18 | "cacheVariables": { 19 | "CMAKE_BUILD_TYPE": "Debug", 20 | "CMAKE_EXPORT_COMPILE_COMMANDS": "ON", 21 | "CMAKE_SHARED_LINKER_FLAGS": "-fuse-ld=mold" 22 | }, 23 | "inherits": [ 24 | "base" 25 | ] 26 | }, 27 | { 28 | "name": "dev", 29 | "displayName": "Build as debug", 30 | "cacheVariables": { 31 | "CMAKE_BUILD_TYPE": "Debug", 32 | "CMAKE_EXPORT_COMPILE_COMMANDS": "ON" 33 | }, 34 | "inherits": [ 35 | "base" 36 | ] 37 | }, 38 | { 39 | "name": "asan", 40 | "displayName": "Build with Asan support.", 41 | "cacheVariables": { 42 | "CMAKE_BUILD_TYPE": "Debug", 43 | "ECM_ENABLE_SANITIZERS" : "'address;undefined'" 44 | }, 45 | "inherits": [ 46 | "base" 47 | ] 48 | }, 49 | { 50 | "name": "dev-clang", 51 | "displayName": "dev-clang", 52 | "cacheVariables": { 53 | "CMAKE_BUILD_TYPE": "Debug", 54 | "CMAKE_EXPORT_COMPILE_COMMANDS": "ON" 55 | }, 56 | "environment": { 57 | "CXX": "clang++", 58 | "CCACHE_DISABLE": "ON" 59 | }, 60 | "inherits": [ 61 | "base" 62 | ] 63 | }, 64 | { 65 | "name": "unity", 66 | "displayName": "Build with CMake unity support.", 67 | "cacheVariables": { 68 | "CMAKE_BUILD_TYPE": "Debug", 69 | "USE_UNITY_CMAKE_SUPPORT": "ON" 70 | }, 71 | "inherits": [ 72 | "base" 73 | ] 74 | }, 75 | { 76 | "name": "release", 77 | "displayName": "Build as release mode.", 78 | "cacheVariables": { 79 | "CMAKE_BUILD_TYPE": "Release", 80 | "BUILD_TESTING": "OFF" 81 | }, 82 | "inherits": [ 83 | "base" 84 | ] 85 | }, 86 | { 87 | "name": "profile", 88 | "displayName": "profile", 89 | "cacheVariables": { 90 | "CMAKE_BUILD_TYPE": "RelWithDebInfo" 91 | }, 92 | "inherits": [ 93 | "base" 94 | ] 95 | }, 96 | { 97 | "name": "coverage", 98 | "displayName": "coverage", 99 | "cacheVariables": { 100 | "CMAKE_BUILD_TYPE": "Debug", 101 | "USE_UNITY_CMAKE_SUPPORT": "OFF", 102 | "BUILD_COVERAGE": "ON" 103 | }, 104 | "inherits": [ 105 | "base" 106 | ] 107 | }, 108 | { 109 | "name": "clazy", 110 | "displayName": "clazy", 111 | "cacheVariables": { 112 | "CMAKE_BUILD_TYPE": "Debug" 113 | }, 114 | "environment": { 115 | "CXX": "clazy", 116 | "CCACHE_DISABLE": "ON" 117 | }, 118 | "inherits": [ 119 | "base" 120 | ] 121 | }, 122 | { 123 | "name": "pch", 124 | "displayName": "pch", 125 | "cacheVariables": { 126 | "CMAKE_BUILD_TYPE": "Debug", 127 | "USE_PRECOMPILED_HEADERS": "ON", 128 | "BUILD_COVERAGE": "ON" 129 | }, 130 | "inherits": [ 131 | "base" 132 | ] 133 | } 134 | ], 135 | "buildPresets": [ 136 | { 137 | "name": "dev", 138 | "configurePreset": "dev" 139 | }, 140 | { 141 | "name": "dev-mold", 142 | "configurePreset": "dev-mold" 143 | }, 144 | { 145 | "name": "dev-clang", 146 | "configurePreset": "dev-clang" 147 | }, 148 | { 149 | "name": "pch", 150 | "configurePreset": "pch" 151 | }, 152 | { 153 | "name": "release", 154 | "configurePreset": "release" 155 | }, 156 | { 157 | "name": "unity", 158 | "configurePreset": "unity" 159 | }, 160 | { 161 | "name": "coverage", 162 | "configurePreset": "coverage" 163 | }, 164 | { 165 | "name": "asan", 166 | "configurePreset": "asan" 167 | }, 168 | { 169 | "name": "clazy", 170 | "configurePreset": "clazy", 171 | "environment": { 172 | "CLAZY_CHECKS" : "level0,level1,detaching-member,ifndef-define-typo,isempty-vs-count,qrequiredresult-candidates,reserve-candidates,signal-with-return-value,unneeded-cast,function-args-by-ref,function-args-by-value,returning-void-expression,no-ctor-missing-parent-argument,isempty-vs-count,qhash-with-char-pointer-key,raw-environment-function,qproperty-type-mismatch,old-style-connect,qstring-allocations,container-inside-loop,heap-allocated-small-trivial-type,inefficient-qlist,qstring-varargs,level2,detaching-member,heap-allocated-small-trivial-type,isempty-vs-count,qstring-varargs,qvariant-template-instantiation,raw-environment-function,reserve-candidates,signal-with-return-value,thread-with-slots,no-ctor-missing-parent-argument,no-missing-typeinfo", 173 | "CCACHE_DISABLE" : "ON" 174 | } 175 | } 176 | ], 177 | "testPresets": [ 178 | { 179 | "name": "dev", 180 | "configurePreset": "dev", 181 | "output": {"outputOnFailure": true}, 182 | "execution": {"noTestsAction": "error", "stopOnFailure": false} 183 | }, 184 | { 185 | "name": "asan", 186 | "configurePreset": "asan", 187 | "output": {"outputOnFailure": true}, 188 | "execution": {"noTestsAction": "error", "stopOnFailure": true} 189 | }, 190 | { 191 | "name": "unity", 192 | "configurePreset": "unity", 193 | "output": {"outputOnFailure": true}, 194 | "execution": {"noTestsAction": "error", "stopOnFailure": true} 195 | }, 196 | { 197 | "name": "coverage", 198 | "configurePreset": "coverage", 199 | "output": {"outputOnFailure": true}, 200 | "execution": {"noTestsAction": "error", "stopOnFailure": true} 201 | } 202 | ] 203 | } 204 | -------------------------------------------------------------------------------- /CMakePresets.json.license: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2021-2024 Laurent Montel 2 | # SPDX-License-Identifier: BSD-3-Clause 3 | -------------------------------------------------------------------------------- /LICENSES/BSD-3-Clause.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) . 2 | 3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 4 | 5 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 6 | 7 | 2. 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. 8 | 9 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 10 | 11 | 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 HOLDER 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. 12 | -------------------------------------------------------------------------------- /LICENSES/CC0-1.0.txt: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | Statement of Purpose 15 | 16 | The laws of most jurisdictions throughout the world automatically confer 17 | exclusive Copyright and Related Rights (defined below) upon the creator 18 | and subsequent owner(s) (each and all, an "owner") of an original work of 19 | authorship and/or a database (each, a "Work"). 20 | 21 | Certain owners wish to permanently relinquish those rights to a Work for 22 | the purpose of contributing to a commons of creative, cultural and 23 | scientific works ("Commons") that the public can reliably and without fear 24 | of later claims of infringement build upon, modify, incorporate in other 25 | works, reuse and redistribute as freely as possible in any form whatsoever 26 | and for any purposes, including without limitation commercial purposes. 27 | These owners may contribute to the Commons to promote the ideal of a free 28 | culture and the further production of creative, cultural and scientific 29 | works, or to gain reputation or greater distribution for their Work in 30 | part through the use and efforts of others. 31 | 32 | For these and/or other purposes and motivations, and without any 33 | expectation of additional consideration or compensation, the person 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 | is an owner of Copyright and Related Rights in the Work, voluntarily 36 | elects to apply CC0 to the Work and publicly distribute the Work under its 37 | terms, with knowledge of his or her Copyright and Related Rights in the 38 | Work and the meaning and intended legal effect of CC0 on those rights. 39 | 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be 41 | protected by copyright and related or neighboring rights ("Copyright and 42 | Related Rights"). Copyright and Related Rights include, but are not 43 | limited to, the following: 44 | 45 | i. the right to reproduce, adapt, distribute, perform, display, 46 | communicate, and translate a Work; 47 | ii. moral rights retained by the original author(s) and/or performer(s); 48 | iii. publicity and privacy rights pertaining to a person's image or 49 | likeness depicted in a Work; 50 | iv. rights protecting against unfair competition in regards to a Work, 51 | subject to the limitations in paragraph 4(a), below; 52 | v. rights protecting the extraction, dissemination, use and reuse of data 53 | in a Work; 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal 56 | protection of databases, and under any national implementation 57 | thereof, including any amended or successor version of such 58 | directive); and 59 | vii. other similar, equivalent or corresponding rights throughout the 60 | world based on applicable law or treaty, and any national 61 | implementations thereof. 62 | 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention 64 | of, applicable law, Affirmer hereby overtly, fully, permanently, 65 | irrevocably and unconditionally waives, abandons, and surrenders all of 66 | Affirmer's Copyright and Related Rights and associated claims and causes 67 | of action, whether now known or unknown (including existing as well as 68 | future claims and causes of action), in the Work (i) in all territories 69 | worldwide, (ii) for the maximum duration provided by applicable law or 70 | treaty (including future time extensions), (iii) in any current or future 71 | medium and for any number of copies, and (iv) for any purpose whatsoever, 72 | including without limitation commercial, advertising or promotional 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 | member of the public at large and to the detriment of Affirmer's heirs and 75 | successors, fully intending that such Waiver shall not be subject to 76 | revocation, rescission, cancellation, termination, or any other legal or 77 | equitable action to disrupt the quiet enjoyment of the Work by the public 78 | as contemplated by Affirmer's express Statement of Purpose. 79 | 80 | 3. Public License Fallback. Should any part of the Waiver for any reason 81 | be judged legally invalid or ineffective under applicable law, then the 82 | Waiver shall be preserved to the maximum extent permitted taking into 83 | account Affirmer's express Statement of Purpose. In addition, to the 84 | extent the Waiver is so judged Affirmer hereby grants to each affected 85 | person a royalty-free, non transferable, non sublicensable, non exclusive, 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 | maximum duration provided by applicable law or treaty (including future 89 | time extensions), (iii) in any current or future medium and for any number 90 | of copies, and (iv) for any purpose whatsoever, including without 91 | limitation commercial, advertising or promotional purposes (the 92 | "License"). The License shall be deemed effective as of the date CC0 was 93 | applied by Affirmer to the Work. Should any part of the License for any 94 | reason be judged legally invalid or ineffective under applicable law, such 95 | partial invalidity or ineffectiveness shall not invalidate the remainder 96 | of the License, and in such case Affirmer hereby affirms that he or she 97 | will not (i) exercise any of his or her remaining Copyright and Related 98 | Rights in the Work or (ii) assert any associated claims and causes of 99 | action with respect to the Work, in either case contrary to Affirmer's 100 | express Statement of Purpose. 101 | 102 | 4. Limitations and Disclaimers. 103 | 104 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 | surrendered, licensed or otherwise affected by this document. 106 | b. Affirmer offers the Work as-is and makes no representations or 107 | warranties of any kind concerning the Work, express, implied, 108 | statutory or otherwise, including without limitation warranties of 109 | title, merchantability, fitness for a particular purpose, non 110 | infringement, or the absence of latent or other defects, accuracy, or 111 | the present or absence of errors, whether or not discoverable, all to 112 | the greatest extent permissible under applicable law. 113 | c. Affirmer disclaims responsibility for clearing rights of other persons 114 | that may apply to the Work or any use thereof, including without 115 | limitation any person's Copyright and Related Rights in the Work. 116 | Further, Affirmer disclaims responsibility for obtaining any necessary 117 | consents, permissions or other rights required for any use of the 118 | Work. 119 | d. Affirmer understands and acknowledges that Creative Commons is not a 120 | party to this document and has no duty or obligation with respect to 121 | this CC0 or use of the Work. 122 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | # KWeather 8 | 9 | A convergent weather application for Plasma. 10 | 11 | Download on Flathub 12 | 13 | ![KWeather Main Window](https://cdn.kde.org/screenshots/kweather/kweather-desktop-dynamic.png) 14 | 15 | ## Features 16 | * Daily and hourly weather data that can be viewed in flat and dynamic views 17 | * Plasmoid that can be used on desktop and mobile 18 | 19 | ## Links 20 | * Project page: https://invent.kde.org/utilities/kweather 21 | * Issues: https://bugs.kde.org/describecomponents.cgi?product=kweather 22 | * Development channel: https://matrix.to/#/#plasmamobile:matrix.org 23 | 24 | ## Dependencies 25 | * KWeatherCore (https://invent.kde.org/libraries/kweathercore) 26 | * Kirigami 27 | * Kirigami Addons 28 | * Qt Quick Controls 29 | * Qt Quick Shapes 30 | 31 | ## Installing 32 | ``` 33 | mkdir build 34 | cd build 35 | cmake .. # add -DCMAKE_BUILD_TYPE=Release to compile for release 36 | make 37 | sudo make install 38 | ``` 39 | 40 | ## Online APIs used 41 | * api.met.no - Weather data 42 | * geonames.org - Coordinates -> Timezone 43 | * geoip.ubuntu.com - IP -> Coordinates 44 | 45 | ## Gallery 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /REUSE.toml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: None 2 | # SPDX-License-Identifier: CC0-1.0 3 | 4 | version = 1 5 | SPDX-PackageName = "KWeather" 6 | SPDX-PackageDownloadLocation = "https://invent.kde.org/plasma-mobile/kweather" 7 | 8 | [[annotations]] 9 | path = "android/ic_launcher-playstore.png" 10 | precedence = "aggregate" 11 | SPDX-FileCopyrightText = "2020 Gabriel Souza Franco" 12 | SPDX-License-Identifier = "CC-BY-4.0" 13 | 14 | [[annotations]] 15 | path = ["android/res/drawable/kweather.png", "android/res/mipmap-hdpi/ic_launcher.png", "android/res/mipmap-xhdpi/ic_launcher.png", "android/res/mipmap-xxhdpi/ic_launcher.png", "android/res/mipmap-xxxhdpi/ic_launcher.png"] 16 | precedence = "aggregate" 17 | SPDX-FileCopyrightText = "2020 Devin Lin " 18 | SPDX-License-Identifier = "CC-BY-4.0" 19 | 20 | [[annotations]] 21 | path = "android/res/mipmap-mdpi/ic_launcher.png" 22 | precedence = "aggregate" 23 | SPDX-FileCopyrightText = ["2020 Gabriel Souza Franco", "2020 Devin Lin "] 24 | SPDX-License-Identifier = "CC-BY-4.0" 25 | -------------------------------------------------------------------------------- /android/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-FileCopyrightText: 2018-2020 Volker Krause 3 | SPDX-FileCopyrightText: 2019 Nicolas Fella 4 | SPDX-FileCopyrightText: 2020 Gabriel Souza Franco 5 | SPDX-License-Identifier: BSD-3-Clause 6 | */ 7 | 8 | buildscript { 9 | repositories { 10 | google() 11 | jcenter() 12 | } 13 | 14 | dependencies { 15 | classpath 'com.android.tools.build:gradle:8.6.0' 16 | } 17 | } 18 | 19 | repositories { 20 | google() 21 | jcenter() 22 | } 23 | 24 | 25 | apply plugin: 'com.android.application' 26 | apply from: '../ecm-version.gradle' 27 | 28 | dependencies { 29 | implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar']) 30 | } 31 | 32 | android { 33 | /******************************************************* 34 | * The following variables: 35 | * - androidBuildToolsVersion, 36 | * - androidCompileSdkVersion 37 | * - qtAndroidDir - holds the path to qt android files 38 | * needed to build any Qt application 39 | * on Android. 40 | * 41 | * are defined in gradle.properties file. This file is 42 | * updated by QtCreator and androiddeployqt tools. 43 | * Changing them manually might break the compilation! 44 | *******************************************************/ 45 | 46 | compileSdkVersion androidCompileSdkVersion 47 | buildToolsVersion androidBuildToolsVersion 48 | ndkVersion androidNdkVersion 49 | 50 | // Extract native libraries from the APK 51 | packagingOptions.jniLibs.useLegacyPackaging true 52 | 53 | sourceSets { 54 | main { 55 | manifest.srcFile 'AndroidManifest.xml' 56 | java.srcDirs = [qtAndroidDir + '/src', 'src', 'java'] 57 | aidl.srcDirs = [qtAndroidDir + '/src', 'src', 'aidl'] 58 | res.srcDirs = [qtAndroidDir + '/res', 'res'] 59 | resources.srcDirs = ['src'] 60 | renderscript.srcDirs = ['src'] 61 | assets.srcDirs = ['assets'] 62 | jniLibs.srcDirs = ['libs'] 63 | } 64 | } 65 | 66 | compileOptions { 67 | sourceCompatibility JavaVersion.VERSION_1_8 68 | targetCompatibility JavaVersion.VERSION_1_8 69 | } 70 | 71 | lintOptions { 72 | abortOnError false 73 | } 74 | 75 | defaultConfig { 76 | minSdkVersion qtMinSdkVersion 77 | targetSdkVersion qtTargetSdkVersion 78 | applicationId "org.kde.kweather" 79 | namespace "org.kde.kweather" 80 | versionCode ecmVersionCode 81 | versionName ecmVersionName 82 | manifestPlaceholders = [versionName: ecmVersionName, versionCode: ecmVersionCode] 83 | } 84 | 85 | packagingOptions { 86 | exclude 'lib/*/*RemoteObjects*' 87 | exclude 'lib/*/*StateMachine*' 88 | exclude 'lib/*/*_imageformats_qico_*' 89 | exclude 'lib/*/*_imageformats_qicns_*' 90 | exclude 'lib/*/*_imageformats_qtga_*' 91 | exclude 'lib/*/*_imageformats_qtiff_*' 92 | exclude 'lib/*/*_qmltooling_*' 93 | } 94 | 95 | aaptOptions { 96 | // different syntax than above 97 | // see https://android.googlesource.com/platform/frameworks/base/+/refs/heads/pie-release/tools/aapt2/util/Files.h#90 98 | ignoreAssetsPattern '!ECM:!aclocal:!doc:!gtk-doc:!iso-codes:!man:!mime:!pkgconfig:!qlogging-categories5:!iso_15924.mo:!iso_3166-2.mo:!iso_3166-3.mo:!iso_4217.mo:!iso_639-2.mo:!iso_639-3.mo:!iso_639-5.mo:!kcodecs5_qt.qm:!kde5_xml_mimetypes.qm' 99 | } 100 | 101 | 102 | } 103 | -------------------------------------------------------------------------------- /android/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/kweather/74af6b044b0c03850c05fd12b8a8229fa62adcce/android/ic_launcher-playstore.png -------------------------------------------------------------------------------- /android/res/drawable-v24/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 5 | 11 | 13 | 15 | 16 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /android/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 5 | 11 | 13 | 17 | 18 | 24 | 25 | 26 | 27 | 28 | 29 | 31 | 32 | 38 | 39 | 40 | 41 | 42 | 43 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 59 | 60 | 66 | 67 | 68 | 69 | 70 | 71 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /android/res/drawable/kweather.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/kweather/74af6b044b0c03850c05fd12b8a8229fa62adcce/android/res/drawable/kweather.png -------------------------------------------------------------------------------- /android/res/drawable/splash.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /android/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /android/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/kweather/74af6b044b0c03850c05fd12b8a8229fa62adcce/android/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/kweather/74af6b044b0c03850c05fd12b8a8229fa62adcce/android/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/kweather/74af6b044b0c03850c05fd12b8a8229fa62adcce/android/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/kweather/74af6b044b0c03850c05fd12b8a8229fa62adcce/android/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/kweather/74af6b044b0c03850c05fd12b8a8229fa62adcce/android/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /org.kde.kweather.desktop: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2020 Han Young 3 | # Copyright 2020 Devin Lin 4 | # 5 | # SPDX-License-Identifier: GPL-2.0-or-later 6 | # 7 | 8 | [Desktop Entry] 9 | Name=Weather 10 | Name[ar]=الطقس 11 | Name[az]=Hava 12 | Name[bg]=Времето 13 | Name[ca]=Meteorologia 14 | Name[ca@valencia]=Meteorologia 15 | Name[cs]=Počasí 16 | Name[de]=Weather 17 | Name[el]=Καιρός 18 | Name[en_GB]=Weather 19 | Name[eo]=Weather 20 | Name[es]=Meteorología 21 | Name[et]=Ilmateade 22 | Name[eu]=Eguraldia 23 | Name[fi]=Weather 24 | Name[fr]=Météo 25 | Name[gl]=Meteoroloxía 26 | Name[he]=מזג אוויר 27 | Name[hi]=मौसम 28 | Name[hu]=Időjárás 29 | Name[ia]=Tempore meteorologic-Weather 30 | Name[is]=Veður 31 | Name[it]=Meteo 32 | Name[ka]=Weather 33 | Name[ko]=날씨 34 | Name[lt]=Orai 35 | Name[lv]=Laikapstākļi 36 | Name[nb]=Værmelding 37 | Name[nl]=Het weer 38 | Name[nn]=Vêrmelding 39 | Name[pa]=ਮੌਸਮ 40 | Name[pl]=Pogoda 41 | Name[pt]=Meteorologia 42 | Name[pt_BR]=Meteorologia 43 | Name[ro]=Vremea 44 | Name[ru]=Погода 45 | Name[sa]=वातावरणम्‌ 46 | Name[sk]=Počasie 47 | Name[sl]=Weather 48 | Name[sv]=Väder 49 | Name[tr]=Hava Durumu 50 | Name[uk]=Погода 51 | Name[x-test]=xxWeatherxx 52 | Name[zh_CN]=天气 53 | Name[zh_TW]=天氣 54 | Comment=View real-time weather forecasts and other information 55 | Comment[ar]=عرض تنبؤات الطقس في الوقت الحقيقي وغيرها من المعلومات 56 | Comment[az]=Hazırkı vaxt hava məlumatına və digər məlumatlara baxış 57 | Comment[bg]=Метеорологични прогнози в реално време и друга информация 58 | Comment[ca]=Visualitza previsions meteorològiques en temps real i altra informació 59 | Comment[ca@valencia]=Visualitza previsions meteorològiques en temps real i altra informació 60 | Comment[de]=Echtzeit-Wettervorschau und weitere Informationen anzeigen 61 | Comment[el]=Προβολή πρόγνωσης του καιρού σε πραγματικό χρόνο και άλλες πληροφορίες 62 | Comment[en_GB]=View real-time weather forecasts and other information 63 | Comment[eo]=Rigardi realtempajn veterprognozojn kaj aliajn informojn 64 | Comment[es]=Visualice previsiones meteorológicas y otra información en tiempo real 65 | Comment[eu]=Ikusi denbora errealeko eguraldi aurreikuspenak eta beste informazio batzuk 66 | Comment[fi]=Näytä tosiaikaiset sääennusteet ja muuta tietoa 67 | Comment[fr]=Afficher les prévisions météorologiques en temps réel et d'autres informations. 68 | Comment[gl]=Vexa a predición meteorolóxica e outra información en tempo real. 69 | Comment[he]=הצגת תחזיות מזג אוויר בזמן אמת ופרטים נוספים 70 | Comment[hi]=वास्तविक समय मौसम पूर्वानुमान और अन्य जानकारी देखें 71 | Comment[hu]=Valós idejű időjárás-előrejelzések és egyéb információk megtekintése 72 | Comment[ia]=Vista de previsiones de tempore in tempore real e altere information 73 | Comment[is]=Skoðaðu veðurspár í rauntíma og aðrar upplýsingar 74 | Comment[it]=Visualizza le previsioni meteo in tempo reale e altre informazioni 75 | Comment[ka]=ამინდის პროგნოზებისა და სხვა ინფორმაციის რეალურ დროში ნახვა 76 | Comment[ko]=실시간 일기 예보와 기타 정보 표시 77 | Comment[lt]=Rodyti tikralaikes orų prognozes ir kitą informaciją 78 | Comment[lv]=Iegūstiet reāllaika laikapstākļu prognozes un citu informāciju 79 | Comment[nb]=Vis værmeldinger og annen værinformasjon i sanntid 80 | Comment[nl]=Realtime weersverwachtingen en andere informatie bekijken 81 | Comment[nn]=Vis vêrmeldingar og annan vêrinformasjon i sanntid 82 | Comment[pa]=ਮੌਕੇ ਉੱਤੇ ਮੌਸਮ ਦੀਆਂ ਭਵਿੱਖਬਾਣੀਆਂ ਅਤੇ ਹੋਰ ਜਾਣਕਾਰੀਆਂ ਨੂੰ ਵੇਖੋ 83 | Comment[pl]=Oglądaj prognozę pogody w czasie rzeczywistym i inne dane 84 | Comment[pt]=Ver as previsões meteorológicas e outras informações em tempo-real 85 | Comment[pt_BR]=Visualizar previsões meteorológicas em tempo real e outras informações 86 | Comment[ro]=Vedeți prognoza vremii în timp real și alte informații 87 | Comment[ru]=Просмотр прогнозов погоды и другой информации в режиме реального времени 88 | Comment[sa]=वास्तविकसमयस्य मौसमस्य पूर्वानुमानं अन्यसूचनाः च पश्यन्तु 89 | Comment[sl]=Ogledujte si sprotne vremenske napovedi in ostale informacije 90 | Comment[sv]=Visa väderprognos och annan information i realtid 91 | Comment[tr]=Hava Durumu Tahmini Görüntüleyicisi 92 | Comment[uk]=Перегляд інтерактивних прогнозів погоди та інших даних 93 | Comment[x-test]=xxView real-time weather forecasts and other informationxx 94 | Comment[zh_CN]=查看实时天气预报和其他信息 95 | Comment[zh_TW]=查看即時天氣預報和其他資訊 96 | Exec=kweather 97 | Icon=org.kde.kweather 98 | Type=Application 99 | Terminal=false 100 | Categories=Qt;KDE;Utility; 101 | X-KDE-FormFactor=desktop;tablet;handset; 102 | -------------------------------------------------------------------------------- /org.kde.kweather.service.in: -------------------------------------------------------------------------------- 1 | [D-BUS Service] 2 | Name=org.kde.kweather 3 | Exec=@CMAKE_INSTALL_PREFIX@/bin/kweather --daemon 4 | -------------------------------------------------------------------------------- /org.kde.kweather.service.in.license: -------------------------------------------------------------------------------- 1 | Copyright 2020 Han Young 2 | SPDX-License-Identifier: GPL-2.0-or-later 3 | -------------------------------------------------------------------------------- /org.kde.kweather.svg.license: -------------------------------------------------------------------------------- 1 | Copyright 2020 Han Young 2 | Copyright 2020 Devin Lin 3 | SPDX-License-Identifier: GPL-2.0-or-later 4 | -------------------------------------------------------------------------------- /snapcraft.yaml: -------------------------------------------------------------------------------- 1 | # * Copyright 2024 Soumyadeep Ghosh (সৌম্যদীপ ঘোষ) 2 | # * Copyright 2023, 2024 Scarlett Moore 3 | # * SPDX-License-Identifier: GPL-2.0-or-later 4 | name: kweather 5 | confinement: strict 6 | grade: stable 7 | base: core24 8 | adopt-info: kweather 9 | 10 | apps: 11 | kweather: 12 | extensions: [kde-neon-6] 13 | common-id: org.kde.kweather 14 | desktop: usr/share/applications/org.kde.kweather.desktop 15 | command: usr/bin/kweather 16 | 17 | compression: lzo 18 | 19 | slots: 20 | session-dbus-interface: 21 | interface: dbus 22 | name: org.kde.kweather 23 | bus: session 24 | 25 | parts: 26 | libplasma: 27 | plugin: cmake 28 | source: https://invent.kde.org/plasma/libplasma/-/archive/v6.0.3/libplasma-v6.0.3.tar.gz 29 | cmake-parameters: 30 | - -DCMAKE_INSTALL_PREFIX=/usr 31 | - -DCMAKE_BUILD_TYPE=Release 32 | - -DBUILD_DESKTOPTHEMES=OFF 33 | - -DBUILD_TESTING=OFF 34 | build-packages: 35 | - libxcb-composite0-dev 36 | - libxcb-damage0-dev 37 | - libxcb-shape0-dev 38 | - libxcb-glx0-dev 39 | - libwayland-dev 40 | - doxygen 41 | - gettext 42 | - graphviz 43 | - pkg-config 44 | - libgl-dev 45 | - libgl1-mesa-dev 46 | - libgles-dev 47 | - libglvnd-dev 48 | - libglx-dev 49 | - libegl-dev 50 | - libsm-dev 51 | - docbook 52 | - docbook-xml 53 | - docbook-xsl 54 | build-environment: &build-environment 55 | - PYTHONPATH: ${CRAFT_STAGE}/lib/python3.12/site-packages:${CRAFT_STAGE}/usr/lib/python3/dist-packages 56 | - LD_LIBRARY_PATH: "/snap/mesa-2404/current/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR:$CRAFT_STAGE/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR:/snap/kde-qt6-core24-sdk/current/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libproxy:$LD_LIBRARY_PATH" 57 | 58 | kweathercore: 59 | plugin: cmake 60 | source: https://invent.kde.org/libraries/kweathercore.git 61 | source-tag: "v0.8.0" 62 | source-depth: 1 63 | cmake-parameters: 64 | - -DCMAKE_INSTALL_PREFIX=/usr 65 | - -DCMAKE_BUILD_TYPE=Release 66 | - -DBUILD_TESTING=OFF 67 | - -DKDE_SKIP_TEST_SETTINGS=ON 68 | build-environment: *build-environment 69 | 70 | kweather: 71 | after: 72 | - libplasma 73 | - kweathercore 74 | parse-info: 75 | - usr/share/metainfo/org.kde.kweather.appdata.xml 76 | plugin: cmake 77 | build-packages: 78 | - reuse 79 | - libvulkan-dev 80 | - libxkbcommon-dev 81 | source: . 82 | source-type: local 83 | cmake-parameters: 84 | - -DCMAKE_INSTALL_PREFIX=/usr 85 | - -DCMAKE_BUILD_TYPE=Release 86 | - -DENABLE_TESTING=OFF 87 | - -DBUILD_TESTING=OFF 88 | - -DKDE_SKIP_TEST_SETTINGS=ON 89 | prime: 90 | - -usr/lib/*/cmake/* 91 | - -usr/include/* 92 | - -usr/share/ECM/* 93 | - -usr/share/doc/* 94 | - -usr/share/man/* 95 | - -usr/share/icons/breeze-dark* 96 | - -usr/bin/X11 97 | - -usr/lib/gcc/$CRAFT_ARCH_TRIPLET_BUILD_FOR/6.0.0 98 | - -usr/lib/aspell/* 99 | - -usr/share/lintian 100 | build-environment: 101 | - CMAKE_PREFIX_PATH: $CRAFT_STAGE/usr:/snap/kf6-core24-sdk/current/usr${CMAKE_PREFIX_PATH:+:$CMAKE_PREFIX_PATH} 102 | - LD_LIBRARY_PATH: "/snap/mesa-2404/current/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR:$CRAFT_STAGE/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR:/snap/kde-qt6-core24-sdk/current/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libproxy:$LD_LIBRARY_PATH" 103 | 104 | gpu-2404: 105 | after: [kweather] 106 | source: https://github.com/canonical/gpu-snap.git 107 | plugin: dump 108 | override-prime: | 109 | craftctl default 110 | ${CRAFT_PART_SRC}/bin/gpu-2404-cleanup mesa-2404 111 | prime: 112 | - bin/gpu-2404-wrapper 113 | -------------------------------------------------------------------------------- /src/Messages.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | # SPDX-FileCopyrightText: 2020 Yuri Chornoivan 4 | # SPDX-License-Identifier: GPL-2.0-or-later 5 | 6 | $XGETTEXT `find . -name \*.cpp -o -name \*.h -o -name \*.qml` -o $podir/kweather.pot 7 | -------------------------------------------------------------------------------- /src/formatter.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Han Young 3 | * SPDX-FileCopyrightText: 2020 Devin Lin 4 | * SPDX-FileCopyrightText: 2021 Nicolas Fella 5 | * 6 | * SPDX-License-Identifier: GPL-2.0-or-later 7 | */ 8 | 9 | #include "formatter.h" 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #include "global.h" 16 | 17 | double Formatter::convertTemp(qreal temperature, const QString &unit) const 18 | { 19 | return KWeather::convertTemp(temperature, unit); 20 | } 21 | 22 | QString Formatter::formatTemperatureUnitDegrees(const QString &unit) const 23 | { 24 | if (KWeather::isCelsius(unit)) { 25 | return QStringLiteral("℃"); 26 | } else { 27 | return QStringLiteral("℉"); 28 | } 29 | } 30 | 31 | QString Formatter::formatTemperature(qreal temperature, const QString &unit) const 32 | { 33 | // only have decimals when in celsius 34 | if (KWeather::isCelsius(unit)) { 35 | QString formattedTemperature = QLocale().toString(KWeather::convertTemp(temperature, unit), 'f', 1); 36 | return ki18nc("A temperature", "%1°").subs(formattedTemperature).toString(); 37 | } else { 38 | return formatTemperatureRounded(temperature, unit); 39 | } 40 | } 41 | 42 | QString Formatter::formatTemperatureRounded(qreal temperature, const QString &unit) const 43 | { 44 | return i18nc("A temperature", "%1°", qRound(KWeather::convertTemp(temperature, unit))); 45 | } 46 | 47 | QString Formatter::formatWindSpeed(qreal speed, const QString &unit) const 48 | { 49 | QString formattedSpeed; 50 | 51 | if (unit == QLatin1String("kph")) { 52 | formattedSpeed = QLocale().toString(speed, 'f', 1); 53 | return ki18n("%1 km/h").subs(formattedSpeed).toString(); 54 | } else if (unit == QLatin1String("mph")) { 55 | formattedSpeed = QLocale().toString(speed * 0.62, 'f', 1); 56 | return ki18n("%1 mph").subs(formattedSpeed).toString(); 57 | } else { 58 | formattedSpeed = QLocale().toString(speed * 1000 / 3600, 'f', 1); 59 | return ki18n("%1 m/s").subs(formattedSpeed).toString(); 60 | } 61 | } 62 | 63 | QString Formatter::formatPressure(qreal pressure, const QString &unit) const 64 | { 65 | QString formattedPressure; 66 | 67 | if (unit == QLatin1String("hPa")) { 68 | formattedPressure = QLocale().toString(pressure, 'f', 1); 69 | return ki18n("%1 hPa").subs(formattedPressure).toString(); 70 | } else { 71 | formattedPressure = QLocale().toString(pressure * 0.7500638, 'f', 1); 72 | return ki18n("%1 mmHg").subs(formattedPressure).toString(); 73 | } 74 | } 75 | 76 | QString Formatter::formatPercent(qreal percentage) const 77 | { 78 | return i18nc("%1 represents percent value, % is the percent sign", "%1%", percentage); 79 | } 80 | 81 | QString Formatter::formatDouble(qreal number) const 82 | { 83 | return i18n("%1", QLocale().toString(number)); 84 | } 85 | 86 | QString Formatter::formatSunriseTime(QDateTime date, const QString &timeZone) const 87 | { 88 | if (!date.isValid()) { 89 | return i18nc("sunrise time not available", "-"); 90 | } 91 | auto time = date.toTimeZone(QTimeZone(timeZone.toUtf8())).time(); 92 | return time.toString(timeFormat()).toLower(); 93 | } 94 | 95 | QString Formatter::formatPrecipitation(qreal precipitation, const QString &unit) const 96 | { 97 | QString formattedPrecipitation; 98 | 99 | if (unit == QStringLiteral("in")) { 100 | formattedPrecipitation = QLocale().toString(precipitation * 0.03937008, 'f', 2); 101 | return ki18nc("in as inches", "%1 in").subs(formattedPrecipitation).toString(); 102 | } else { 103 | formattedPrecipitation = QLocale().toString(precipitation, 'f', 1); 104 | return ki18nc("mm as millimeters", "%1 mm").subs(formattedPrecipitation).toString(); 105 | } 106 | } 107 | 108 | QString Formatter::formatHourlyCardDelegateTime(QDateTime date, const QString &timeZone) const 109 | { 110 | auto time = date.toTimeZone(QTimeZone(timeZone.toUtf8())).time(); 111 | return time.toString(timeFormat()).toLower(); 112 | } 113 | 114 | QString Formatter::timeFormat() const 115 | { 116 | // Remove seconds from the time, since it's unnecessary in our strings 117 | return QLocale().timeFormat(QLocale::ShortFormat).replace(QStringLiteral(":ss"), QString{}); 118 | } 119 | 120 | #include "moc_formatter.cpp" 121 | -------------------------------------------------------------------------------- /src/formatter.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Han Young 3 | * SPDX-FileCopyrightText: 2020 Devin Lin 4 | * SPDX-FileCopyrightText: 2021 Nicolas Fella 5 | * 6 | * SPDX-License-Identifier: GPL-2.0-or-later 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | class Formatter : public QObject 16 | { 17 | Q_OBJECT 18 | QML_ELEMENT 19 | QML_SINGLETON 20 | public: 21 | Q_INVOKABLE double convertTemp(qreal temperature, const QString &unit) const; 22 | Q_INVOKABLE QString formatTemperatureUnitDegrees(const QString &unit) const; 23 | Q_INVOKABLE QString formatTemperature(qreal temperature, const QString &unit) const; 24 | Q_INVOKABLE QString formatTemperatureRounded(qreal temperature, const QString &unit) const; 25 | 26 | Q_INVOKABLE QString formatWindSpeed(qreal speed, const QString &unit) const; 27 | 28 | Q_INVOKABLE QString formatPressure(qreal pressure, const QString &unit) const; 29 | Q_INVOKABLE QString formatPercent(qreal percentage) const; 30 | Q_INVOKABLE QString formatDouble(qreal number) const; 31 | 32 | Q_INVOKABLE QString formatSunriseTime(QDateTime date, const QString &timeZone) const; 33 | Q_INVOKABLE QString formatPrecipitation(qreal precipitation, const QString &unit) const; 34 | Q_INVOKABLE QString formatHourlyCardDelegateTime(QDateTime date, const QString &timeZone) const; 35 | 36 | QString timeFormat() const; 37 | }; 38 | -------------------------------------------------------------------------------- /src/global.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Han Young 3 | * SPDX-FileCopyrightText: 2020 Devin Lin 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | */ 7 | #pragma once 8 | 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | #include "kweathersettings.h" 18 | 19 | namespace KWeather 20 | { 21 | static const QString WEATHER_LOCATIONS_CFG_GROUP = QStringLiteral("WeatherLocations"); 22 | 23 | static bool isCelsius(const QString &unit) 24 | { 25 | if (unit == QLatin1String("Use System Default")) { 26 | return (QLocale().measurementSystem() == QLocale::MetricSystem); 27 | } 28 | return unit == QLatin1String("Celsius"); 29 | }; 30 | 31 | static double convertTemp(double temp, const QString &unit) 32 | { 33 | if (KWeather::isCelsius(unit)) { 34 | return temp; 35 | } else { 36 | return temp * 1.8 + 32; 37 | } 38 | }; 39 | 40 | enum class WindDirection { 41 | N, 42 | NW, 43 | W, 44 | SW, 45 | S, 46 | SE, 47 | E, 48 | NE 49 | }; 50 | enum class Backend { 51 | NMI, 52 | OWM 53 | }; 54 | } 55 | -------------------------------------------------------------------------------- /src/kweathersettings.kcfg: -------------------------------------------------------------------------------- 1 | 2 | 8 | 12 | 13 | 14 | 15 | 16 | true 17 | 18 | 19 | 20 | Dynamic 21 | 22 | 23 | 24 | Use System Default 25 | 26 | 27 | 28 | kph 29 | 30 | 31 | 32 | hPa 33 | 34 | 35 | 36 | mm 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/kweathersettings.kcfgc: -------------------------------------------------------------------------------- 1 | File=kweathersettings.kcfg 2 | ClassName=KWeatherSettings 3 | Mutators=true 4 | DefaultValueGetters=true 5 | GenerateProperties=true 6 | ParentInConstructor=true 7 | Singleton=true 8 | -------------------------------------------------------------------------------- /src/kweathersettings.kcfgc.license: -------------------------------------------------------------------------------- 1 | Copyright 2020 Nicolas Fella 2 | Copyright 2020 Han Young 3 | Copyright 2020 Devin Lin 4 | SPDX-License-Identifier: GPL-2.0-or-later 5 | -------------------------------------------------------------------------------- /src/locationquerymodel.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020-2021 Han Young 3 | * SPDX-FileCopyrightText: 2020 Devin Lin 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | */ 7 | 8 | #include "locationquerymodel.h" 9 | #include "weatherlocationlistmodel.h" 10 | 11 | #include 12 | 13 | #include 14 | #include 15 | 16 | LocationQueryModel::LocationQueryModel() 17 | : inputTimer(new QTimer(this)) 18 | { 19 | inputTimer->setSingleShot(true); 20 | connect(inputTimer, &QTimer::timeout, this, &LocationQueryModel::setQuery); 21 | } 22 | 23 | int LocationQueryModel::rowCount(const QModelIndex &parent) const 24 | { 25 | Q_UNUSED(parent); 26 | return m_results.size(); 27 | } 28 | 29 | static QString buildResultName(const KWeatherCore::LocationQueryResult &result) 30 | { 31 | const auto &countryCode = result.countryCode(); 32 | if (result.subdivision() && countryCode == QLatin1String("US")) { 33 | return result.toponymName() + QLatin1String(", ") + *result.subdivision() + QLatin1String(" | ") + result.countryName(); 34 | } 35 | return result.toponymName() + QLatin1String(" | ") + result.countryName(); 36 | } 37 | 38 | QVariant LocationQueryModel::data(const QModelIndex &index, int role) const 39 | { 40 | if (!index.isValid()) 41 | return QVariant(); 42 | 43 | auto result = m_results.at(index.row()); 44 | 45 | if (role == NameRole) { 46 | return buildResultName(result); 47 | } 48 | 49 | return QVariant(); 50 | } 51 | 52 | QHash LocationQueryModel::roleNames() const 53 | { 54 | return {{NameRole, "name"}}; 55 | } 56 | 57 | KWeatherCore::LocationQueryResult LocationQueryModel::get(int index) 58 | { 59 | if (index < 0 || index >= static_cast(m_results.size())) 60 | return {}; 61 | return m_results.at(index); 62 | } 63 | 64 | void LocationQueryModel::textChanged(QString query, int timeout) 65 | { 66 | m_text = std::move(query); 67 | 68 | beginResetModel(); 69 | // clear results list 70 | m_results.clear(); 71 | 72 | endResetModel(); 73 | if (!m_text.isEmpty()) { // do not query nothing 74 | m_loading = true; 75 | m_networkError = false; 76 | Q_EMIT propertyChanged(); 77 | 78 | inputTimer->start(timeout); // make request once input stopped for 2 secs 79 | } 80 | } 81 | 82 | void LocationQueryModel::setQuery() 83 | { 84 | m_loading = true; 85 | m_networkError = false; 86 | Q_EMIT propertyChanged(); 87 | 88 | qDebug() << "start query"; 89 | 90 | auto reply = m_querySource.query(m_text); 91 | connect(reply, &KWeatherCore::LocationQueryReply::finished, this, [this, reply]() { 92 | m_loading = false; 93 | // Handling errors 94 | auto err = reply->error(); 95 | if (err == KWeatherCore::LocationQueryReply::NoError || err == KWeatherCore::LocationQueryReply::NotFound) { 96 | handleQueryResults(reply->result()); 97 | } else if (err == KWeatherCore::LocationQueryReply::NetworkError) { 98 | m_networkError = true; 99 | Q_EMIT propertyChanged(); 100 | } 101 | 102 | reply->deleteLater(); 103 | }); 104 | } 105 | 106 | void LocationQueryModel::addLocation(int index) 107 | { 108 | if (m_results.empty() || index < 0 || index >= static_cast(m_results.size())) 109 | return; // don't add location 110 | WeatherLocationListModel::inst()->addLocation(m_results.at(index)); 111 | } 112 | 113 | void LocationQueryModel::handleQueryResults(const std::vector &results) 114 | { 115 | qDebug() << "results arrived" << results.size(); 116 | beginResetModel(); 117 | // clear results list 118 | m_results.assign(results.begin(), results.end()); 119 | 120 | endResetModel(); 121 | } 122 | 123 | bool LocationQueryModel::loading() const 124 | { 125 | return m_loading; 126 | } 127 | bool LocationQueryModel::networkError() const 128 | { 129 | return m_networkError; 130 | } 131 | 132 | void LocationQueryModel::clearResults() 133 | { 134 | beginResetModel(); 135 | m_results.clear(); 136 | endResetModel(); 137 | m_loading = false; 138 | m_networkError = false; 139 | Q_EMIT propertyChanged(); 140 | } 141 | 142 | #include "moc_locationquerymodel.cpp" 143 | -------------------------------------------------------------------------------- /src/locationquerymodel.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Han Young 3 | * SPDX-FileCopyrightText: 2020 Devin Lin 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | 16 | class QTimer; 17 | class LocationQueryModel : public QAbstractListModel 18 | { 19 | Q_OBJECT 20 | QML_ELEMENT 21 | Q_PROPERTY(bool loading READ loading NOTIFY propertyChanged) 22 | Q_PROPERTY(bool networkError READ networkError NOTIFY propertyChanged) 23 | 24 | public: 25 | explicit LocationQueryModel(); 26 | enum Roles { 27 | NameRole = Qt::DisplayRole, 28 | }; 29 | 30 | int rowCount(const QModelIndex &parent) const override; 31 | QVariant data(const QModelIndex &index, int role) const override; 32 | QHash roleNames() const override; 33 | Q_INVOKABLE bool loading() const; 34 | Q_INVOKABLE bool networkError() const; 35 | Q_INVOKABLE void textChanged(QString query, int timeout = 2000); 36 | Q_INVOKABLE void addLocation(int index); 37 | Q_INVOKABLE KWeatherCore::LocationQueryResult get(int index); 38 | Q_INVOKABLE void clearResults(); 39 | void setQuery(); 40 | Q_SIGNALS: 41 | void propertyChanged(); 42 | void appendLocation(const KWeatherCore::LocationQueryResult &result); 43 | private Q_SLOTS: 44 | void handleQueryResults(const std::vector &results); 45 | 46 | private: 47 | bool m_loading = false, m_networkError = false; 48 | std::vector m_results; 49 | KWeatherCore::LocationQuery m_querySource; 50 | QTimer *const inputTimer; 51 | QString m_text; 52 | }; 53 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Han Young 3 | * SPDX-FileCopyrightText: 2020 Devin Lin 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #ifndef Q_OS_ANDROID 26 | #include 27 | #endif 28 | 29 | #include "kweathersettings.h" 30 | #include "version.h" 31 | #include "weatherlocation.h" 32 | 33 | class AbstractHourlyWeatherForecast; 34 | class AbstractDailyWeatherForecast; 35 | 36 | Q_DECL_EXPORT int main(int argc, char *argv[]) 37 | { 38 | // We always NEED QApplication, since we use QtCharts 39 | QApplication app(argc, argv); 40 | 41 | #ifdef Q_OS_ANDROID 42 | QQuickStyle::setStyle(QStringLiteral("org.kde.breeze")); 43 | #else 44 | // set default style 45 | if (qEnvironmentVariableIsEmpty("QT_QUICK_CONTROLS_STYLE")) { 46 | QQuickStyle::setStyle(QStringLiteral("org.kde.desktop")); 47 | } 48 | // if using org.kde.desktop, ensure we use kde style if possible 49 | if (qEnvironmentVariableIsEmpty("QT_QPA_PLATFORMTHEME")) { 50 | qputenv("QT_QPA_PLATFORMTHEME", "kde"); 51 | } 52 | #endif 53 | 54 | QQmlApplicationEngine engine; 55 | 56 | KLocalizedString::setApplicationDomain(QByteArrayLiteral("kweather")); 57 | 58 | KLocalization::setupLocalizedContext(&engine); 59 | KAboutData aboutData(QStringLiteral("kweather"), 60 | i18n("Weather"), 61 | QStringLiteral(KWEATHER_VERSION_STRING), 62 | i18n("A convergent weather application for Plasma"), 63 | KAboutLicense::GPL, 64 | i18n("© 2020-2024 Plasma Development Team")); 65 | aboutData.setBugAddress("https://bugs.kde.org/describecomponents.cgi?product=kweather"); 66 | aboutData.addAuthor(i18n("Han Young"), QString(), QStringLiteral("hanyoung@protonmail.com")); 67 | aboutData.addAuthor(i18n("Devin Lin"), QString(), QStringLiteral("espidev@gmail.com"), QStringLiteral("https://espi.dev")); 68 | KAboutData::setApplicationData(aboutData); 69 | 70 | #ifndef Q_OS_ANDROID 71 | KCrash::initialize(); 72 | #endif 73 | 74 | QCommandLineParser parser; 75 | aboutData.setupCommandLine(&parser); 76 | parser.process(app); 77 | aboutData.processCommandLine(&parser); 78 | 79 | engine.rootContext()->setContextProperty(QStringLiteral("settingsModel"), KWeatherSettings::self()); 80 | 81 | WeatherLocation *emptyWeatherLocation = new WeatherLocation(); 82 | engine.rootContext()->setContextProperty(QStringLiteral("emptyWeatherLocation"), emptyWeatherLocation); 83 | #ifdef Q_OS_ANDROID 84 | engine.rootContext()->setContextProperty(QStringLiteral("KWEATHER_IS_ANDROID"), true); 85 | #else 86 | engine.rootContext()->setContextProperty(QStringLiteral("KWEATHER_IS_ANDROID"), false); 87 | #endif 88 | 89 | // load setup wizard if first launch 90 | engine.loadFromModule("org.kde.kweather", "Main"); 91 | 92 | // required for X11 93 | app.setWindowIcon(QIcon::fromTheme(QStringLiteral("org.kde.kweather"))); 94 | 95 | if (engine.rootObjects().isEmpty()) { 96 | return -1; 97 | } 98 | 99 | return app.exec(); 100 | } 101 | -------------------------------------------------------------------------------- /src/plasmoid/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # SPDX-FileCopyrightText: 2020 Han Young 3 | # 4 | # SPDX-License-Identifier: GPL-2.0-or-later 5 | # 6 | 7 | # TODO: adapt "org.kde.plasma" here & elsewhere if needed (see README) 8 | add_definitions(-DTRANSLATION_DOMAIN=\"plasma_applet_org.kde.plasma.kweather_1x4\") 9 | 10 | set(kweather_1x4_SRCS 11 | kweather_1x4.cpp 12 | kweather_1x4.h 13 | hourlymodel.cpp 14 | hourlymodel.h 15 | ) 16 | add_library(plasma_applet_kweather_1x4 MODULE ${kweather_1x4_SRCS}) 17 | 18 | target_link_libraries(plasma_applet_kweather_1x4 19 | Qt::Gui 20 | Qt::Qml 21 | KWeatherCore 22 | Plasma::Plasma 23 | KF6::I18n 24 | kweatherLib) 25 | 26 | 27 | install(TARGETS plasma_applet_kweather_1x4 DESTINATION ${KDE_INSTALL_PLUGINDIR}/plasma/applets) 28 | 29 | plasma_install_package(package org.kde.plasma.kweather_1x4) 30 | -------------------------------------------------------------------------------- /src/plasmoid/hourlymodel.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-FileCopyrightText: 2021 HanYoung 3 | SPDX-License-Identifier: LGPL-2.1-or-later 4 | */ 5 | #include "hourlymodel.h" 6 | 7 | #include 8 | 9 | QVariant HourlyModel::data(const QModelIndex &index, int role) const 10 | { 11 | if (index.row() < 0 || index.row() >= rowCount(QModelIndex())) 12 | return {}; 13 | 14 | switch (role) { 15 | case Time: 16 | return QLocale().toString(getNthHour(index.row()).date(), QLocale::ShortFormat).toLower(); 17 | case Icon: 18 | return getNthHour(index.row()).weatherIcon(); 19 | case Description: 20 | return getNthHour(index.row()).weatherDescription(); 21 | case Temperature: 22 | return getNthHour(index.row()).temperature(); 23 | case Precipitation: 24 | return QString::number(getNthHour(index.row()).precipitationAmount()); 25 | default: 26 | return {}; 27 | } 28 | } 29 | int HourlyModel::rowCount(const QModelIndex &index) const 30 | { 31 | Q_UNUSED(index) 32 | if (m_location.dailyWeatherForecast().empty()) { 33 | return 0; 34 | } 35 | return 24; 36 | } 37 | QHash HourlyModel::roleNames() const 38 | { 39 | return {{Time, "time"}, {Icon, "weatherIcon"}, {Description, "description"}, {Temperature, "temperature"}, {Precipitation, "precipitation"}}; 40 | } 41 | void HourlyModel::loadForecast(KWeatherCore::WeatherForecast forecast) 42 | { 43 | beginResetModel(); 44 | m_location = std::move(forecast); 45 | endResetModel(); 46 | Q_EMIT reseted(); 47 | } 48 | const KWeatherCore::HourlyWeatherForecast &HourlyModel::getNthHour(int index) const 49 | { 50 | auto dayIndex{0}; 51 | auto hourIndex{0}; 52 | for (const auto &day : m_location.dailyWeatherForecast()) { 53 | if ((int)day.hourlyWeatherForecast().size() - 1 < index) { 54 | index -= day.hourlyWeatherForecast().size(); 55 | dayIndex++; 56 | } else { 57 | hourIndex = index; 58 | break; 59 | } 60 | } 61 | 62 | return m_location.dailyWeatherForecast().at(dayIndex).hourlyWeatherForecast().at(hourIndex); 63 | } 64 | 65 | const QString &HourlyModel::currentIcon() const 66 | { 67 | return getNthHour(0).weatherIcon(); 68 | } 69 | const QString &HourlyModel::currentDescription() const 70 | { 71 | return getNthHour(0).weatherDescription(); 72 | } 73 | QString HourlyModel::currentTemperature() const 74 | { 75 | return QString::number(getNthHour(0).temperature()); 76 | } 77 | void HourlyModel::openKWeather() 78 | { 79 | auto m_process = new QProcess(this); 80 | m_process->start(QStringLiteral("kweather"), QStringList()); 81 | } 82 | 83 | #include "moc_hourlymodel.cpp" 84 | -------------------------------------------------------------------------------- /src/plasmoid/hourlymodel.h: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-FileCopyrightText: 2021 HanYoung 3 | SPDX-License-Identifier: LGPL-2.1-or-later 4 | */ 5 | #ifndef HOURLYMODEL_H 6 | #define HOURLYMODEL_H 7 | 8 | #include 9 | 10 | #include 11 | 12 | class HourlyModel : public QAbstractListModel 13 | { 14 | Q_OBJECT 15 | Q_PROPERTY(QString currentIcon READ currentIcon NOTIFY reseted) 16 | Q_PROPERTY(QString currentTemperature READ currentTemperature NOTIFY reseted) 17 | Q_PROPERTY(QString currentDescription READ currentDescription NOTIFY reseted) 18 | public: 19 | enum HourlyRole { 20 | Time = Qt::UserRole + 1, 21 | Icon, 22 | Description, 23 | Temperature, 24 | Precipitation 25 | }; 26 | QVariant data(const QModelIndex &index, int role) const override; 27 | int rowCount(const QModelIndex &index) const override; 28 | QHash roleNames() const override; 29 | const QString ¤tIcon() const; 30 | QString currentTemperature() const; 31 | const QString ¤tDescription() const; 32 | Q_INVOKABLE void openKWeather(); 33 | public Q_SLOTS: 34 | void loadForecast(KWeatherCore::WeatherForecast); 35 | Q_SIGNALS: 36 | void reseted(); 37 | 38 | private: 39 | const KWeatherCore::HourlyWeatherForecast &getNthHour(int index) const; 40 | KWeatherCore::WeatherForecast m_location; 41 | }; 42 | 43 | #endif // HOURLYNMODEL_H 44 | -------------------------------------------------------------------------------- /src/plasmoid/kweather_1x4.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-FileCopyrightText: 2020 HanY 3 | SPDX-License-Identifier: LGPL-2.1-or-later 4 | */ 5 | #include "kweather_1x4.h" 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | #include "hourlymodel.h" 13 | #include "kweathersettings.h" 14 | 15 | KWeather_1x4::KWeather_1x4(QObject *parent, const KPluginMetaData &md, const QVariantList &args) 16 | : Plasma::Applet(parent, md, args) 17 | , m_hourlyModel(new HourlyModel()) 18 | , m_timer(new QTimer(this)) 19 | { 20 | qmlRegisterAnonymousType("HourlyModel", 1); 21 | auto config = KSharedConfig::openConfig(QStringLiteral("kweather/plasmoid")); 22 | auto group = config->group(QStringLiteral("general")); 23 | const QString locationID = group.readEntry("locationID"); 24 | if (!locationID.isEmpty()) { 25 | auto m_config = KWeatherSettings::self()->config()->group(QStringLiteral("WeatherLocations")); 26 | auto m_group = m_config.group(locationID); 27 | m_location = m_group.readEntry("locationName"); 28 | m_latitude = m_group.readEntry("latitude").toDouble(); 29 | m_longitude = m_group.readEntry("longitude").toDouble(); 30 | m_needLocation = false; 31 | update(); 32 | m_timer->setInterval(3600 * 1000); 33 | m_timer->start(); 34 | connect(m_timer, &QTimer::timeout, this, [this] { 35 | update(); 36 | }); 37 | } 38 | } 39 | 40 | void KWeather_1x4::update() 41 | { 42 | auto pendingForecast = m_source.requestData(m_latitude, m_longitude); 43 | connect(pendingForecast, &KWeatherCore::PendingWeatherForecast::finished, this, [this, pendingForecast] { 44 | m_forecast = pendingForecast->value(); 45 | pendingForecast->deleteLater(); 46 | m_hourlyModel->loadForecast(m_forecast); 47 | Q_EMIT updated(); 48 | }); 49 | } 50 | 51 | QStringList KWeather_1x4::locationsInSystem() 52 | { 53 | auto m_config = KWeatherSettings::self()->config()->group(QStringLiteral("WeatherLocations")); 54 | const QStringList groups = m_config.groupList(); 55 | QStringList list; 56 | list.reserve(groups.size()); 57 | 58 | std::transform(groups.begin(), groups.end(), std::back_inserter(list), [&m_config](const QString &loc) { 59 | return m_config.group(loc).readEntry("locationName"); 60 | }); 61 | return list; 62 | } 63 | void KWeather_1x4::setLocation(const QString &location) 64 | { 65 | auto m_config = KWeatherSettings::self()->config()->group(QStringLiteral("WeatherLocations")); 66 | const auto groups = m_config.groupList(); 67 | for (const auto &loc : groups) { 68 | auto m_group = m_config.group(loc); 69 | if (location == m_group.readEntry("locationName")) { 70 | m_location = location; 71 | auto config = KSharedConfig::openConfig(QStringLiteral("kweather/plasmoid")); 72 | auto group = config->group(QStringLiteral("general")); 73 | group.writeEntry("locationID", loc); 74 | m_latitude = m_group.readEntry("latitude").toDouble(); 75 | m_longitude = m_group.readEntry("longitude").toDouble(); 76 | update(); 77 | m_needLocation = false; 78 | Q_EMIT needLocationChanged(); 79 | Q_EMIT locationChanged(); 80 | 81 | group.sync(); 82 | break; 83 | } 84 | } 85 | } 86 | bool KWeather_1x4::hasForecast() const 87 | { 88 | return !m_forecast.dailyWeatherForecast().empty() && !m_forecast.dailyWeatherForecast().front().hourlyWeatherForecast().empty(); 89 | } 90 | const KWeatherCore::HourlyWeatherForecast &KWeather_1x4::getFirst() const 91 | { 92 | return m_forecast.dailyWeatherForecast().front().hourlyWeatherForecast().front(); 93 | } 94 | QString KWeather_1x4::location() const 95 | { 96 | return m_location; 97 | } 98 | qreal KWeather_1x4::humidity() const 99 | { 100 | if (hasForecast()) 101 | return getFirst().humidity(); 102 | else 103 | return 0; 104 | } 105 | QString KWeather_1x4::weatherIcon() const 106 | { 107 | if (hasForecast()) 108 | return getFirst().weatherIcon(); 109 | else 110 | return QStringLiteral("unknown"); 111 | } 112 | QString KWeather_1x4::desc() const 113 | { 114 | if (hasForecast()) 115 | return getFirst().weatherDescription(); 116 | else 117 | return {}; 118 | } 119 | qreal KWeather_1x4::temp() const 120 | { 121 | if (hasForecast()) 122 | return getFirst().temperature(); 123 | else 124 | return 0; 125 | } 126 | qreal KWeather_1x4::precipitation() const 127 | { 128 | if (hasForecast()) 129 | return getFirst().precipitationAmount(); 130 | else 131 | return 0; 132 | } 133 | 134 | K_PLUGIN_CLASS_WITH_JSON(KWeather_1x4, "package/metadata.json") 135 | 136 | #include "kweather_1x4.moc" 137 | 138 | #include "moc_kweather_1x4.cpp" 139 | -------------------------------------------------------------------------------- /src/plasmoid/kweather_1x4.h: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-FileCopyrightText: 2020 HanY 3 | SPDX-License-Identifier: LGPL-2.1-or-later 4 | */ 5 | 6 | #ifndef KWEATHER_1X4_H 7 | #define KWEATHER_1X4_H 8 | 9 | #include 10 | 11 | #include 12 | 13 | class HourlyModel; 14 | class QTimer; 15 | class KWeather_1x4 : public Plasma::Applet 16 | { 17 | Q_OBJECT 18 | Q_PROPERTY(bool needLocation READ needLocation NOTIFY needLocationChanged) 19 | Q_PROPERTY(QString location READ location NOTIFY locationChanged) 20 | Q_PROPERTY(qreal temp READ temp NOTIFY updated) 21 | Q_PROPERTY(QString desc READ desc NOTIFY updated) 22 | Q_PROPERTY(QString weatherIcon READ weatherIcon NOTIFY updated) 23 | Q_PROPERTY(qreal humidity READ humidity NOTIFY updated) 24 | Q_PROPERTY(qreal precipitation READ precipitation NOTIFY updated) 25 | Q_PROPERTY(HourlyModel *hourlyModel READ hourlyModel NOTIFY hourlyModelChanged) 26 | public: 27 | KWeather_1x4(QObject *parent, const KPluginMetaData &md, const QVariantList &args); 28 | QString location() const; 29 | QString desc() const; 30 | qreal temp() const; 31 | QString weatherIcon() const; 32 | qreal humidity() const; 33 | qreal precipitation() const; 34 | bool needLocation() const 35 | { 36 | return m_needLocation; 37 | } 38 | HourlyModel *hourlyModel() const 39 | { 40 | return m_hourlyModel; 41 | } 42 | 43 | Q_INVOKABLE QStringList locationsInSystem(); 44 | Q_INVOKABLE void setLocation(const QString &location); 45 | Q_SIGNALS: 46 | void locationChanged(); 47 | void updated(); 48 | void needLocationChanged(); 49 | void hourlyModelChanged(); 50 | 51 | private: 52 | void update(); 53 | bool hasForecast() const; 54 | const KWeatherCore::HourlyWeatherForecast &getFirst() const; 55 | 56 | bool m_needLocation = true; 57 | QString m_location; 58 | double m_latitude, m_longitude; 59 | KWeatherCore::WeatherForecast m_forecast; 60 | KWeatherCore::WeatherForecastSource m_source; 61 | 62 | HourlyModel *m_hourlyModel = nullptr; 63 | QTimer *m_timer = nullptr; 64 | }; 65 | 66 | #endif 67 | -------------------------------------------------------------------------------- /src/plasmoid/package/contents/ui/LocationSelector.qml: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-FileCopyrightText: 2021 HanY 3 | SPDX-License-Identifier: LGPL-2.1-or-later 4 | */ 5 | import QtQuick 6 | import QtQuick.Layouts 7 | import QtQuick.Controls 8 | import org.kde.plasma.plasmoid 9 | import org.kde.kirigami as Kirigami 10 | import org.kde.kirigamiaddons.delegates as Delegates 11 | 12 | Rectangle { 13 | id: container 14 | 15 | signal selected 16 | 17 | color: Kirigami.Theme.backgroundColor 18 | radius: 8 19 | 20 | Layout.preferredWidth: Kirigami.Units.gridUnit * 12 21 | Layout.preferredHeight: Kirigami.Units.gridUnit * 12 22 | 23 | ListView { 24 | id: listView 25 | 26 | anchors.fill: parent 27 | model: plasmoid.nativeInterface.locationsInSystem() 28 | 29 | delegate: Delegates.RoundedItemDelegate { 30 | text: modelData 31 | 32 | onClicked: { 33 | selected(); 34 | plasmoid.nativeInterface.setLocation(modelData); 35 | } 36 | } 37 | 38 | Label { 39 | anchors { 40 | bottom: parent.bottom 41 | bottomMargin: Kirigami.Units.gridUnit 42 | } 43 | 44 | color: Kirigami.Theme.disabledTextColor 45 | text: listView.count == 0 ? i18n("No location found on system, please add some in kweather") : i18n("Please select the location") 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/plasmoid/package/contents/ui/WeatherContainer.qml: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-FileCopyrightText: 2021 HanY 3 | SPDX-License-Identifier: LGPL-2.1-or-later 4 | */ 5 | import QtQuick 6 | import QtQuick.Layouts 7 | import QtQuick.Controls 8 | import org.kde.plasma.plasmoid 9 | import org.kde.kirigami as Kirigami 10 | 11 | Rectangle { 12 | id: container 13 | 14 | Layout.preferredWidth: Kirigami.Units.gridUnit * 12 15 | Layout.preferredHeight: Kirigami.Units.gridUnit * 12 16 | 17 | color: Kirigami.Theme.backgroundColor 18 | radius: 8 19 | 20 | MouseArea { 21 | anchors.fill: parent 22 | onDoubleClicked: window.showMaximized() 23 | } 24 | 25 | ColumnLayout { 26 | anchors.fill: parent 27 | RowLayout { 28 | Layout.fillWidth: true 29 | Label { 30 | text: Plasmoid.location 31 | color: Kirigami.Theme.textColor 32 | leftPadding: Kirigami.Units.smallSpacing 33 | font.bold: true 34 | font.pointSize: Kirigami.Theme.defaultFont.pointSize * 1.5 35 | } 36 | Label { 37 | id: temperatureLabel 38 | text: Plasmoid.temp + "°" 39 | color: Kirigami.Theme.activeTextColor 40 | font.pointSize: Kirigami.Theme.defaultFont.pointSize * 1.5 41 | } 42 | } 43 | RowLayout { 44 | Layout.fillWidth: true 45 | Kirigami.Icon { 46 | source: Plasmoid.weatherIcon 47 | } 48 | 49 | Label { 50 | text: Plasmoid.desc 51 | color: Kirigami.Theme.textColor 52 | font.pointSize: Kirigami.Theme.defaultFont.pointSize * 1.5 53 | } 54 | } 55 | Row { 56 | Kirigami.Icon { 57 | source: "speedometer" 58 | isMask: true 59 | color: Kirigami.Theme.textColor 60 | Layout.minimumHeight: Kirigami.Theme.defaultFont.pointSize * 2 61 | Layout.minimumWidth: Layout.minimumHeight * 1.5 62 | } 63 | Label { 64 | text: i18n("%1%", Plasmoid.humidity) 65 | color: Kirigami.Theme.textColor 66 | font.pointSize: Kirigami.Theme.defaultFont.pointSize * 1.5 67 | } 68 | } 69 | 70 | RowLayout { 71 | visible: Plasmoid.precipitation > 0.01 72 | Kirigami.Icon { 73 | source: "raindrop" 74 | isMask: true 75 | color: Kirigami.Theme.textColor 76 | Layout.preferredHeight: Kirigami.Units.iconSizes.medium 77 | Layout.preferredWidth: Kirigami.Units.iconSizes.medium 78 | } 79 | Label { 80 | text: i18n("%1mm", Plasmoid.precipitation.toFixed(1)) 81 | font.pointSize: Kirigami.Theme.defaultFont.pointSize * 1.5 82 | } 83 | } 84 | } 85 | 86 | Kirigami.AbstractApplicationWindow { 87 | id: window 88 | visible: false 89 | flags: Qt.FramelessWindowHint 90 | modality: Qt.WindowModal 91 | color: "transparent" 92 | MouseArea { 93 | anchors.fill: parent 94 | onClicked: window.close() 95 | } 96 | 97 | // TODO: content here 98 | Loader { 99 | active: window.active 100 | anchors.fill: parent 101 | sourceComponent: Item { 102 | id: detailedItem 103 | anchors.fill: parent 104 | Rectangle { 105 | width: Kirigami.Units.gridUnit * 21 106 | height: Kirigami.Units.gridUnit * 22 107 | radius: Kirigami.Units.gridUnit 108 | anchors.centerIn: parent 109 | RowLayout { 110 | anchors.fill: parent 111 | RowLayout { 112 | Layout.alignment: Qt.AlignHCenter 113 | ColumnLayout { 114 | Label { 115 | Layout.alignment: Qt.AlignHCenter 116 | font.pointSize: Kirigami.Theme.defaultFont.pointSize * 2 117 | font.weight: Font.Light 118 | text: Plasmoid.location 119 | } 120 | Kirigami.Icon { 121 | source: Plasmoid.hourlyModel.currentIcon 122 | Layout.preferredHeight: width 123 | Layout.preferredWidth: detailedItem.width * 0.8 - headerText.width 124 | Layout.maximumHeight: Kirigami.Theme.defaultFont.pointSize * 15 125 | Layout.maximumWidth: Kirigami.Theme.defaultFont.pointSize * 15 126 | Layout.minimumHeight: Kirigami.Theme.defaultFont.pointSize * 5 127 | Layout.minimumWidth: Kirigami.Theme.defaultFont.pointSize * 5 128 | smooth: true 129 | } 130 | Button { 131 | Layout.alignment: Qt.AlignHCenter 132 | text: i18n("Select Location") 133 | onClicked: locationSelectDialog.open() 134 | } 135 | Button { 136 | Layout.alignment: Qt.AlignHCenter 137 | text: i18n("Open KWeather") 138 | onClicked: Plasmoid.hourlyModel.openKWeather() 139 | } 140 | } 141 | // weather header 142 | ColumnLayout { 143 | id: headerText 144 | Label { 145 | font.pointSize: Kirigami.Theme.defaultFont.pointSize * 3 146 | font.weight: Font.Light 147 | text: Plasmoid.hourlyModel.currentTemperature + "°" 148 | } 149 | Label { 150 | font.pointSize: Kirigami.Theme.defaultFont.pointSize * 1.3 151 | font.weight: Font.Bold 152 | text: Plasmoid.hourlyModel.currentDescription 153 | } 154 | } 155 | } 156 | ListView { 157 | Layout.preferredHeight: Kirigami.Units.gridUnit * 16 158 | Layout.fillWidth: true 159 | model: Plasmoid.hourlyModel 160 | delegate: Kirigami.BasicListItem { 161 | label: time 162 | subtitle: temperature + "°" 163 | icon: weatherIcon 164 | } 165 | } 166 | } 167 | } 168 | Dialog { 169 | id: locationSelectDialog 170 | standardButtons: Dialog.Close 171 | title: i18n("Select Location") 172 | anchors.centerIn: parent 173 | width: Kirigami.Units.gridUnit * 12 174 | height: Kirigami.Units.gridUnit * 12 175 | LocationSelector { 176 | anchors.fill: parent 177 | onSelected: locationSelectDialog.close() 178 | } 179 | } 180 | } 181 | } 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/plasmoid/package/contents/ui/main.qml: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-FileCopyrightText: 2020 2021 HanY 3 | SPDX-License-Identifier: LGPL-2.1-or-later 4 | */ 5 | 6 | import QtQuick 2.1 7 | import QtQuick.Layouts 1.1 8 | import QtQuick.Controls 2.4 9 | import org.kde.plasma.plasmoid 2.0 10 | import org.kde.kirigami 2.11 as Kirigami 11 | 12 | PlasmoidItem { 13 | id: root 14 | 15 | Plasmoid.backgroundHints: "ShadowBackground" 16 | 17 | fullRepresentation: Loader { 18 | active: true 19 | source: root.plasmoid.needLocation ? "LocationSelector.qml" : "WeatherContainer.qml" 20 | } 21 | 22 | compactRepresentation: ColumnLayout { 23 | Kirigami.Icon { 24 | height: 20 25 | width: 20 26 | source: root.plasmoid.weatherIcon 27 | MouseArea { 28 | anchors.fill: parent 29 | onClicked: { 30 | plasmoid.expanded = !plasmoid.expanded; 31 | } 32 | } 33 | } 34 | Label { 35 | text: root.plasmoid.temp + "°" 36 | color: Kirigami.Theme.activeTextColor 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/plasmoid/package/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "KPlugin": { 3 | "Authors": [ 4 | { 5 | "Email": "hanyoung@protonmail.com", 6 | "Name": "HanY", 7 | "Name[ar]": "هان يونغ", 8 | "Name[az]": "HanY", 9 | "Name[bg]": "HanY", 10 | "Name[ca@valencia]": "HanY", 11 | "Name[ca]": "HanY", 12 | "Name[cs]": "HanY", 13 | "Name[de]": "HanY", 14 | "Name[en_GB]": "HanY", 15 | "Name[eo]": "HanY", 16 | "Name[es]": "HanY", 17 | "Name[eu]": "HanY", 18 | "Name[fi]": "HanY", 19 | "Name[fr]": "HanY", 20 | "Name[gl]": "HanY", 21 | "Name[he]": "HanY (האן י)", 22 | "Name[hi]": "हनी", 23 | "Name[hu]": "HanY", 24 | "Name[ia]": "HanY", 25 | "Name[is]": "HanY", 26 | "Name[it]": "HanY", 27 | "Name[ka]": "HanY", 28 | "Name[ko]": "HanY", 29 | "Name[lt]": "HanY", 30 | "Name[lv]": "HanY", 31 | "Name[nb]": "HanY", 32 | "Name[nl]": "HanY", 33 | "Name[nn]": "HanY", 34 | "Name[pa]": "ਹਾਂਯੇ", 35 | "Name[pl]": "HanY", 36 | "Name[pt]": "HanY", 37 | "Name[pt_BR]": "HanY", 38 | "Name[ru]": "HanY", 39 | "Name[sa]": "हनY", 40 | "Name[sk]": "HanY", 41 | "Name[sl]": "HanY", 42 | "Name[sv]": "HanY", 43 | "Name[tr]": "HanY", 44 | "Name[uk]": "HanY", 45 | "Name[x-test]": "xxHanYxx", 46 | "Name[zh_CN]": "HanY", 47 | "Name[zh_TW]": "HanY" 48 | } 49 | ], 50 | "Category": "Utilities", 51 | "Description": "KWeather_1x4 widget", 52 | "Description[ar]": "برنامج الطقس_1x4", 53 | "Description[az]": "KWeather_1x4 vidjeti", 54 | "Description[bg]": "KWeather_1x4 уиджет", 55 | "Description[ca@valencia]": "Giny de KWeather_1x4", 56 | "Description[ca]": "Giny del KWeather_1x4", 57 | "Description[cs]": "Widget KWeather_1x4", 58 | "Description[de]": "KWeather_1x4-Element", 59 | "Description[en_GB]": "KWeather_1x4 widget", 60 | "Description[eo]": "KWeather_1x4-fenestraĵo", 61 | "Description[es]": "Widget de KWeather_1x4", 62 | "Description[eu]": "KWeather_1x4 trepeta", 63 | "Description[fi]": "KWeather_1x4-sovelma", 64 | "Description[fr]": "Composant graphique pour KWeather_1 x 4 ", 65 | "Description[gl]": "Trebello KWeather_1x4.", 66 | "Description[he]": "וידג׳ט KWeather_1x4", 67 | "Description[hi]": "KWeather_1x4 विजेट", 68 | "Description[hu]": "KWeather_1x4 widget", 69 | "Description[ia]": "KWeather_1x4 widget", 70 | "Description[is]": "KWeather_1x4 græja", 71 | "Description[it]": "Oggetto KWeather_1x4", 72 | "Description[ka]": "KWeather_1x4 widget", 73 | "Description[ko]": "KWeather_1x4 위젯", 74 | "Description[lt]": "KWeather_1x4 valdiklis", 75 | "Description[lv]": "„KWeather_1x4“ logdaļa", 76 | "Description[nb]": "KWeather_1x4-element", 77 | "Description[nl]": "KWeather_1x4 widget", 78 | "Description[nn]": "KWeather_1x4-element", 79 | "Description[pa]": "ਕੇ-ਮੌਸਮ_1x4 ਵਿਜੈੱਟ", 80 | "Description[pl]": "Element interfejsu KWeather_1x4", 81 | "Description[pt]": "Elemento do KWeather_1x4", 82 | "Description[pt_BR]": "Widget do KWeather_1x4", 83 | "Description[ru]": "Виджет KWeather_1x4", 84 | "Description[sa]": "KWeather_1x4 विजेट", 85 | "Description[sk]": "KWeather_1x4 widget", 86 | "Description[sl]": "gradnik KWeather_1x4", 87 | "Description[sv]": "Kväder komponent 1x4", 88 | "Description[tr]": "K Hava_1 × 4 araç takımı", 89 | "Description[uk]": "Віджет KWeather_1x4", 90 | "Description[x-test]": "xxKWeather_1x4 widgetxx", 91 | "Description[zh_CN]": "KWeather 1x4 挂件", 92 | "Description[zh_TW]": "KWeather_1x4 元件", 93 | "EnabledByDefault": true, 94 | "Icon": "kweather", 95 | "Id": "org.kde.plasma.kweather_1x4", 96 | "License": "LGPL-2.1+", 97 | "Name": "KWeather_1x4", 98 | "Name[ar]": "الطقس_1x4", 99 | "Name[az]": "KWeather_1x4", 100 | "Name[bg]": "KWeather_1x4", 101 | "Name[ca@valencia]": "KWeather_1x4", 102 | "Name[ca]": "KWeather_1x4", 103 | "Name[cs]": "KWeather_1x4", 104 | "Name[de]": "KWeather_1x4", 105 | "Name[en_GB]": "KWeather_1x4", 106 | "Name[eo]": "KWeather_1x4", 107 | "Name[es]": "KWeather_1x4", 108 | "Name[eu]": "KWeather_1x4", 109 | "Name[fi]": "KWeather_1x4", 110 | "Name[fr]": "KWeather_1 x 4", 111 | "Name[gl]": "KWeather_1x4", 112 | "Name[he]": "KWeather_1x4", 113 | "Name[hi]": "केवेदर_1x4", 114 | "Name[hu]": "KWeather_1x4", 115 | "Name[ia]": "KWeather_1x4", 116 | "Name[is]": "KWeather_1x4", 117 | "Name[it]": "KWeather_1x4", 118 | "Name[ka]": "KWeather_1x4", 119 | "Name[ko]": "KWeather_1x4", 120 | "Name[lt]": "KWeather_1x4", 121 | "Name[lv]": "KWeather_1x4", 122 | "Name[nb]": "KWeather_1x4", 123 | "Name[nl]": "KWeather_1x4", 124 | "Name[nn]": "KWeather_1x4", 125 | "Name[pa]": "ਕੇ-ਮੌਸਮ_1x4", 126 | "Name[pl]": "KWeather_1x4", 127 | "Name[pt]": "KWeather_1x4", 128 | "Name[pt_BR]": "KWeather_1x4", 129 | "Name[ru]": "KWeather_1x4", 130 | "Name[sa]": "Kमौसम_१x४", 131 | "Name[sk]": "KWeather_1x4", 132 | "Name[sl]": "KWeather_1x4", 133 | "Name[sv]": "Kväder 1x4", 134 | "Name[tr]": "K Hava_1 × 4", 135 | "Name[uk]": "KWeather_1x4", 136 | "Name[x-test]": "xxKWeather_1x4xx", 137 | "Name[zh_CN]": "KWeather_1x4", 138 | "Name[zh_TW]": "KWeather_1x4", 139 | "ServiceTypes": [ 140 | "Plasma/Applet" 141 | ], 142 | "Version": "1.0", 143 | "Website": "https://kde.org/plasma-desktop/" 144 | }, 145 | "X-Plasma-API": "declarativeappletscript", 146 | "X-Plasma-MainScript": "ui/main.qml", 147 | "X-Plasma-Provides": [ 148 | "org.kde.plasma.weather" 149 | ] 150 | } 151 | -------------------------------------------------------------------------------- /src/plasmoid/package/metadata.json.license: -------------------------------------------------------------------------------- 1 | Copyright 2020 Han Young 2 | SPDX-License-Identifier: GPL-2.0-or-later 3 | -------------------------------------------------------------------------------- /src/qml/DefaultPage.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Han Young 3 | * SPDX-FileCopyrightText: 2020 Devin Lin 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | */ 7 | 8 | import QtQuick 9 | import QtQuick.Controls 10 | import QtQuick.Layouts 11 | 12 | import org.kde.kirigami as Kirigami 13 | 14 | import org.kde.kweather 15 | import org.kde.kweather.locations 16 | 17 | // page shown if there are no weather locations configured 18 | Kirigami.Page { 19 | id: root 20 | 21 | title: i18n("Forecast") 22 | 23 | property bool loading: false 24 | 25 | property int yTranslate: 0 26 | 27 | Connections { 28 | target: WeatherLocationListModel 29 | function onNetworkErrorCreatingDefault() { 30 | showPassiveNotification(i18n("Network error when obtaining current location")); 31 | loading = false; 32 | } 33 | function onSuccessfullyCreatedDefault() { 34 | switchToPage(getPage("Forecast"), 0); 35 | loading = false; 36 | } 37 | } 38 | 39 | actions: [ 40 | Kirigami.Action { 41 | visible: Kirigami.Settings.isMobile 42 | icon.name: "settings-configure" 43 | onTriggered: applicationWindow().openSettings() 44 | } 45 | ] 46 | 47 | Item { 48 | // empty list view to centre placeholdermessage 49 | anchors.fill: parent 50 | transform: Translate { 51 | y: yTranslate 52 | } 53 | BusyIndicator { 54 | anchors.centerIn: parent 55 | running: root.loading 56 | Layout.minimumWidth: Kirigami.Units.iconSizes.huge 57 | Layout.minimumHeight: width 58 | } 59 | 60 | ColumnLayout { 61 | visible: !root.loading 62 | anchors.centerIn: parent 63 | spacing: Kirigami.Units.gridUnit 64 | 65 | Kirigami.Icon { 66 | Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter 67 | source: "qrc:/resources/kweather.svg" 68 | implicitWidth: Kirigami.Units.iconSizes.enormous * 1.5 69 | implicitHeight: Kirigami.Units.iconSizes.enormous * 1.5 70 | } 71 | 72 | Kirigami.Heading { 73 | Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter 74 | text: i18n("Weather") 75 | type: Kirigami.Heading.Type.Primary 76 | horizontalAlignment: Qt.AlignHCenter 77 | } 78 | 79 | Button { 80 | Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter 81 | icon.name: "list-add" 82 | text: i18n("Add Location") 83 | onClicked: applicationWindow().openAddLocation() 84 | } 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/qml/FlatForecastPage.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Han Young 3 | * SPDX-FileCopyrightText: 2020 Devin Lin 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | */ 7 | 8 | import QtQuick 9 | import QtQuick.Controls 10 | import QtQuick.Layouts 11 | 12 | import org.kde.kirigami as Kirigami 13 | import org.kde.kweather 14 | 15 | SwipeView { 16 | id: forecastView 17 | property bool canGoLeft: currentIndex > 0 18 | property bool canGoRight: currentIndex < WeatherLocationListModel.count - 1 19 | 20 | anchors.fill: parent 21 | transform: Translate { 22 | y: yTranslate 23 | } 24 | 25 | function moveLeft() { 26 | currentIndex--; 27 | } 28 | function moveRight() { 29 | currentIndex++; 30 | } 31 | 32 | Repeater { 33 | id: forecastViewRepeater 34 | anchors.fill: parent 35 | 36 | // on mobile mode, for some reason, switching the type to dynamic and back to flat again gives an empty page unless we assign the model 37 | // after component creation 38 | Component.onCompleted: { 39 | model = Qt.binding(function () { 40 | return WeatherLocationListModel.locations; 41 | }); 42 | } 43 | delegate: FlatLocationForecast { 44 | weatherLocation: modelData 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/qml/FlatLocationForecast.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Han Young 3 | * SPDX-FileCopyrightText: 2020 Devin Lin 4 | * SPDX-FileCopyrightText: 2021 Nicolas Fella 5 | * 6 | * SPDX-License-Identifier: GPL-2.0-or-later 7 | */ 8 | 9 | import QtQuick 10 | import QtQuick.Controls 11 | import QtQuick.Layouts 12 | import QtQuick.Shapes 13 | 14 | import org.kde.kirigami as Kirigami 15 | import org.kde.kweather 16 | 17 | Kirigami.ScrollablePage { 18 | id: root 19 | 20 | background: null // transparent, since there is a page behind 21 | 22 | property WeatherLocation weatherLocation 23 | property var selectedDay: dailyListView.currentItem ? dailyListView.currentItem.weather : weatherLocation.dayForecasts[0] 24 | 25 | property bool inView: false 26 | 27 | // swipe down to refresh 28 | supportsRefreshing: true 29 | onRefreshingChanged: { 30 | if (refreshing) { 31 | weatherLocation.update(); 32 | } 33 | } 34 | 35 | Connections { 36 | target: root.weatherLocation 37 | ignoreUnknownSignals: true 38 | function onStopLoadingIndicator() { 39 | root.refreshing = false; 40 | 41 | // flat mode loads all locations at once, only show one notification for the current item 42 | if (root.ListView.isCurrentItem) { 43 | showPassiveNotification(i18n("Weather refreshed for %1", root.weatherLocation.name)); 44 | } 45 | } 46 | } 47 | 48 | // all elements are in a column 49 | ColumnLayout { 50 | spacing: 0 51 | RowLayout { 52 | Layout.alignment: Qt.AlignHCenter 53 | Kirigami.Icon { 54 | id: weatherIcon 55 | source: root.weatherLocation.currentHourForecast ? root.weatherLocation.currentHourForecast.weatherIcon : "weather-none-available" 56 | Layout.preferredHeight: width 57 | Layout.preferredWidth: root.width * 0.8 - headerText.width 58 | Layout.maximumHeight: Kirigami.Theme.defaultFont.pointSize * 15 59 | Layout.maximumWidth: Kirigami.Theme.defaultFont.pointSize * 15 60 | Layout.minimumHeight: Kirigami.Theme.defaultFont.pointSize * 5 61 | Layout.minimumWidth: Kirigami.Theme.defaultFont.pointSize * 5 62 | smooth: true 63 | } 64 | 65 | // weather header 66 | ColumnLayout { 67 | id: headerText 68 | RowLayout { 69 | spacing: 0 70 | Label { 71 | font.pointSize: Kirigami.Theme.defaultFont.pointSize * 3 72 | font.weight: Font.Light 73 | font.family: lightHeadingFont.name 74 | text: root.weatherLocation.currentHourForecast ? Math.round(Formatter.convertTemp(root.weatherLocation.currentHourForecast.temperature, settingsModel.temperatureUnits)) : "" 75 | } 76 | Label { 77 | Layout.alignment: Qt.AlignTop 78 | Layout.topMargin: Kirigami.Units.largeSpacing 79 | font.pointSize: Kirigami.Theme.defaultFont.pointSize * 1.5 80 | font.weight: Font.Light 81 | font.family: lightHeadingFont.name 82 | text: Formatter.formatTemperatureUnitDegrees(settingsModel.temperatureUnits) 83 | } 84 | } 85 | Label { 86 | font.pointSize: Kirigami.Theme.defaultFont.pointSize * 1.3 87 | font.weight: Font.Bold 88 | text: root.weatherLocation.currentHourForecast ? root.weatherLocation.currentHourForecast.weatherDescription : "" 89 | } 90 | Label { 91 | color: Kirigami.Theme.disabledTextColor 92 | Layout.topMargin: Kirigami.Units.largeSpacing 93 | font.pointSize: Kirigami.Theme.defaultFont.pointSize * 0.9 94 | text: i18n("Updated at %1", root.weatherLocation.lastUpdated) 95 | } 96 | } 97 | } 98 | 99 | PageIndicator { 100 | visible: Kirigami.Settings.isMobile 101 | Layout.topMargin: Kirigami.Units.largeSpacing 102 | Layout.alignment: Qt.AlignHCenter 103 | opacity: forecastView.count > 1 ? 1 : 0 104 | count: forecastView.count 105 | currentIndex: forecastView.currentIndex 106 | } 107 | 108 | // daily view 109 | Label { 110 | text: i18n("Daily") 111 | font.pointSize: Math.round(Kirigami.Theme.defaultFont.pointSize * 1.2) 112 | Layout.topMargin: Kirigami.Units.largeSpacing 113 | } 114 | 115 | Kirigami.Separator { 116 | Layout.fillWidth: true 117 | Layout.topMargin: Kirigami.Units.largeSpacing * 2 118 | } 119 | 120 | WeatherStrip { 121 | id: dailyListView 122 | selectable: true 123 | Layout.fillWidth: true 124 | Layout.topMargin: Kirigami.Units.largeSpacing * 2 125 | spacing: Kirigami.Units.largeSpacing 126 | 127 | highlightMoveDuration: 250 128 | highlightMoveVelocity: -1 129 | highlight: Rectangle { 130 | color: Kirigami.Theme.focusColor 131 | border { 132 | color: Kirigami.Theme.focusColor 133 | width: 1 134 | } 135 | radius: 4 136 | opacity: 0.3 137 | focus: true 138 | } 139 | 140 | model: root.weatherLocation.dayForecasts 141 | delegate: WeatherDayDelegate { 142 | weather: modelData 143 | textColor: Kirigami.Theme.textColor 144 | secondaryTextColor: Kirigami.Theme.disabledTextColor 145 | } 146 | 147 | onCurrentIndexChanged: { 148 | root.weatherLocation.selectedDay = currentIndex; 149 | } 150 | } 151 | 152 | // hourly view 153 | Label { 154 | Layout.topMargin: Kirigami.Units.largeSpacing * 2 155 | text: i18n("Hourly") 156 | font.pointSize: Math.round(Kirigami.Theme.defaultFont.pointSize * 1.2) 157 | } 158 | 159 | Kirigami.Separator { 160 | Layout.fillWidth: true 161 | Layout.topMargin: Kirigami.Units.largeSpacing * 2 162 | } 163 | 164 | WeatherStrip { 165 | id: hourlyListView 166 | selectable: false 167 | implicitHeight: Kirigami.Units.gridUnit * 10.5 168 | Layout.fillWidth: true 169 | Layout.topMargin: Kirigami.Units.largeSpacing * 2 170 | 171 | model: root.weatherLocation.hourForecasts 172 | 173 | delegate: WeatherHourDelegate { 174 | weather: modelData 175 | textColor: Kirigami.Theme.textColor 176 | secondaryTextColor: Kirigami.Theme.disabledTextColor 177 | } 178 | } 179 | 180 | InfoCard { 181 | Layout.fillWidth: true 182 | Layout.topMargin: Kirigami.Units.largeSpacing * 2 183 | } 184 | 185 | SunriseCard { 186 | Layout.fillWidth: true 187 | Layout.topMargin: Kirigami.Units.largeSpacing * 2 188 | selectedDay: root.selectedDay 189 | } 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /src/qml/ForecastContainerPage.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Han Young 3 | * SPDX-FileCopyrightText: 2020-2021 Devin Lin 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | */ 7 | 8 | import QtQuick 9 | import QtQuick.Controls 10 | import QtQuick.Layouts 11 | 12 | import org.kde.kirigami as Kirigami 13 | 14 | import org.kde.kweather 15 | 16 | Kirigami.Page { 17 | id: page 18 | topPadding: 0 19 | bottomPadding: 0 20 | rightPadding: 0 21 | leftPadding: 0 22 | 23 | title: { 24 | if (WeatherLocationListModel.locations.count == 0) { 25 | return i18n("Forecast"); 26 | } else if (settingsModel.forecastStyle === "Dynamic") { 27 | return ""; // provided by DynamicForecastPage 28 | } else if (WeatherLocationListModel.locations[loader.item.currentIndex]) { 29 | return WeatherLocationListModel.locations[loader.item.currentIndex].name; 30 | } else { 31 | return ""; 32 | } 33 | } 34 | 35 | globalToolBarStyle: (settingsModel.forecastStyle === "Dynamic" && pageStack.layers.depth <= 1) ? Kirigami.ApplicationHeaderStyle.None : Kirigami.ApplicationHeaderStyle.ToolBar 36 | 37 | property real yTranslate: 0 38 | 39 | function switchPageIndex(pageIndex) { 40 | loader.item.currentIndex = pageIndex; 41 | } 42 | 43 | // actions (only shown in flat view since the toolbar is hidden in dynamic view) 44 | actions: [ 45 | Kirigami.Action { 46 | icon.name: "find-location" 47 | text: i18n("Locations") 48 | onTriggered: applicationWindow().openLocationsList() 49 | }, 50 | Kirigami.Action { 51 | icon.name: "settings-configure" 52 | text: i18n("Settings") 53 | displayHint: Kirigami.Action.IconOnly 54 | onTriggered: applicationWindow().openSettings() 55 | }, 56 | Kirigami.Action { 57 | visible: !Kirigami.Settings.isMobile 58 | icon.name: "view-refresh" 59 | text: i18n("Refresh") 60 | displayHint: Kirigami.Action.IconOnly 61 | onTriggered: WeatherLocationListModel.locations[loader.item.currentIndex].update() 62 | } 63 | ] 64 | 65 | Loader { 66 | id: loader 67 | transform: Translate { 68 | y: yTranslate 69 | } 70 | anchors.fill: parent 71 | 72 | Component.onCompleted: loadStyle() 73 | function loadStyle() { 74 | setSource(settingsModel.forecastStyle === "Dynamic" ? "DynamicForecastPage.qml" : "FlatForecastPage.qml"); 75 | } 76 | 77 | Connections { 78 | target: settingsModel 79 | function onForecastStyleChanged() { 80 | loader.loadStyle(); 81 | } 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/qml/InfoCard.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Han Young 3 | * SPDX-FileCopyrightText: 2020 Devin Lin 4 | * SPDX-FileCopyrightText: 2021 Nicolas Fella 5 | * 6 | * SPDX-License-Identifier: GPL-2.0-or-later 7 | */ 8 | 9 | import QtQuick 10 | import QtQuick.Controls 11 | import QtQuick.Layouts 12 | import QtCharts 13 | 14 | import org.kde.kirigami as Kirigami 15 | import org.kde.kweather 16 | import org.kde.kweather.backgrounds 17 | 18 | Kirigami.AbstractCard { 19 | id: root 20 | topPadding: Kirigami.Units.gridUnit 21 | bottomPadding: Kirigami.Units.gridUnit 22 | leftPadding: Kirigami.Units.gridUnit 23 | rightPadding: Kirigami.Units.gridUnit 24 | 25 | property var textColor: Kirigami.Theme.textColor 26 | 27 | contentItem: Item { 28 | implicitHeight: column.height 29 | 30 | Column { 31 | id: column 32 | spacing: Kirigami.Units.largeSpacing * 2 33 | 34 | // precipitation 35 | RowLayout { 36 | spacing: Kirigami.Units.gridUnit 37 | Kirigami.Icon { 38 | source: "raindrop" 39 | implicitHeight: Kirigami.Units.iconSizes.medium 40 | implicitWidth: Kirigami.Units.iconSizes.medium 41 | color: settingsModel && settingsModel.forecastStyle === "Dynamic" ? weatherLocation.iconColor : Kirigami.Theme.textColor 42 | } 43 | Column { 44 | spacing: Kirigami.Units.smallSpacing 45 | Label { 46 | font.weight: Font.Bold 47 | text: i18n("Precipitation") 48 | color: root.textColor 49 | } 50 | Label { 51 | text: selectedDay == null ? "" : Formatter.formatPrecipitation(selectedDay.precipitation, settingsModel.precipitationUnits) 52 | color: root.textColor 53 | } 54 | } 55 | } 56 | 57 | // Humidity 58 | RowLayout { 59 | spacing: Kirigami.Units.gridUnit 60 | Kirigami.Icon { 61 | source: "temperature-normal-symbolic" 62 | implicitHeight: Kirigami.Units.iconSizes.medium 63 | implicitWidth: Kirigami.Units.iconSizes.medium 64 | color: settingsModel && settingsModel.forecastStyle === "Dynamic" ? weatherLocation.iconColor : Kirigami.Theme.textColor 65 | } 66 | Column { 67 | spacing: Kirigami.Units.smallSpacing 68 | Label { 69 | font.weight: Font.Bold 70 | text: i18n("Humidity") 71 | color: root.textColor 72 | } 73 | Label { 74 | text: selectedDay == null ? "" : Formatter.formatPercent(selectedDay.humidity.toFixed(1)) 75 | color: root.textColor 76 | } 77 | } 78 | } 79 | 80 | // Atmospheric pressure 81 | RowLayout { 82 | spacing: Kirigami.Units.gridUnit 83 | Kirigami.Icon { 84 | source: "speedometer" 85 | implicitHeight: Kirigami.Units.iconSizes.medium 86 | implicitWidth: Kirigami.Units.iconSizes.medium 87 | color: settingsModel && settingsModel.forecastStyle === "Dynamic" ? weatherLocation.iconColor : Kirigami.Theme.textColor 88 | } 89 | Column { 90 | spacing: Kirigami.Units.smallSpacing 91 | Label { 92 | font.weight: Font.Bold 93 | text: i18n("Pressure") 94 | color: root.textColor 95 | } 96 | Label { 97 | text: selectedDay == null ? "" : Formatter.formatPressure(selectedDay.pressure, settingsModel.pressureUnits) 98 | color: root.textColor 99 | } 100 | } 101 | } 102 | 103 | // UV Index 104 | RowLayout { 105 | spacing: Kirigami.Units.gridUnit 106 | Kirigami.Icon { 107 | source: "compass" 108 | implicitHeight: Kirigami.Units.iconSizes.medium 109 | implicitWidth: Kirigami.Units.iconSizes.medium 110 | color: settingsModel && settingsModel.forecastStyle === "Dynamic" ? weatherLocation.iconColor : Kirigami.Theme.textColor 111 | } 112 | Column { 113 | spacing: Kirigami.Units.smallSpacing 114 | Label { 115 | font.weight: Font.Bold 116 | text: i18n("UV Index") 117 | color: root.textColor 118 | } 119 | Label { 120 | text: selectedDay == null ? "" : Formatter.formatDouble(selectedDay.uvIndex.toFixed(1)) 121 | color: root.textColor 122 | } 123 | } 124 | } 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/qml/Main.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Han Young 3 | * SPDX-FileCopyrightText: 2020-2021 Devin Lin 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | */ 7 | 8 | import QtQuick 9 | import QtQuick.Controls 10 | import QtQuick.Layouts 11 | 12 | import org.kde.kirigami as Kirigami 13 | import org.kde.config as KConfig 14 | 15 | import org.kde.kweather.locations 16 | import org.kde.kweather.settings 17 | 18 | import org.kde.kweather 19 | 20 | Kirigami.ApplicationWindow { 21 | id: appwindow 22 | title: i18n("Weather") 23 | 24 | minimumWidth: Kirigami.Settings.isMobile ? 0 : 360 25 | minimumHeight: Kirigami.Settings.isMobile ? 0 : 360 26 | width: Kirigami.Settings.isMobile ? Kirigami.Units.gridUnit * 27 : Kirigami.Units.gridUnit * 40 27 | height: Kirigami.Settings.isMobile ? Kirigami.Units.gridUnit * 45 : Kirigami.Units.gridUnit * 35 28 | 29 | pageStack.globalToolBar.canContainHandles: true // move handles to toolbar 30 | pageStack.globalToolBar.style: Kirigami.ApplicationHeaderStyle.ToolBar // ensure toolbar style for mobile 31 | pageStack.globalToolBar.showNavigationButtons: Kirigami.ApplicationHeaderStyle.ShowBackButton 32 | pageStack.popHiddenPages: true 33 | 34 | pageStack.columnView.columnResizeMode: Kirigami.ColumnView.SingleColumn 35 | 36 | readonly property bool isWideScreen: width > 540 37 | 38 | readonly property bool isDialogOpen: settingsDialogLoader.isOpen || addLocationDialogLoader.isOpen || locationsListDialogLoader.isOpen 39 | 40 | KConfig.WindowStateSaver { 41 | configGroupName: "MainWindow" 42 | } 43 | 44 | Component.onCompleted: { 45 | // initial page 46 | switchToPage(getPage("Forecast"), 1); 47 | if (settingsModel.firstStartup) { 48 | setupWizardLoader.source = "SetupWizard.qml"; 49 | setupWizardLoader.item.open(); 50 | } 51 | } 52 | 53 | Loader { 54 | id: setupWizardLoader 55 | } 56 | 57 | Kirigami.PagePool { 58 | id: pagePool 59 | } 60 | 61 | // page switch animation 62 | NumberAnimation { 63 | id: anim 64 | from: 0 65 | to: 1 66 | duration: Kirigami.Units.longDuration * 2 67 | easing.type: Easing.InOutQuad 68 | } 69 | NumberAnimation { 70 | id: yAnim 71 | from: Kirigami.Units.gridUnit * 3 72 | to: 0 73 | duration: Kirigami.Units.longDuration * 3 74 | easing.type: Easing.OutQuint 75 | } 76 | 77 | function switchToPage(page, depth) { 78 | while (pageStack.layers.depth > 1) { 79 | pageStack.layers.pop(); 80 | } 81 | while (pageStack.currentItem !== page && pageStack.depth > depth) { 82 | pageStack.pop(); 83 | } 84 | 85 | if (pageStack.currentItem !== page) { 86 | runPageAnimations(page); 87 | pageStack.push(page); 88 | } 89 | } 90 | 91 | function pushPage(page, depth) { 92 | while (depth !== undefined && pageStack.depth > depth + 1) 93 | pageStack.pop(); 94 | runPageAnimations(page); 95 | pageStack.push(page); 96 | } 97 | 98 | function runPageAnimations(page) { 99 | yAnim.target = page; 100 | yAnim.properties = "yTranslate"; 101 | anim.target = page; 102 | anim.properties = "contentItem.opacity"; 103 | if (page.header) { 104 | anim.properties += ",header.opacity"; 105 | } 106 | yAnim.restart(); 107 | anim.restart(); 108 | } 109 | 110 | function getPage(name) { 111 | switch (name) { 112 | case "Forecast": 113 | return pagePool.loadPage(WeatherLocationListModel.count === 0 ? getPageUrl('', 'DefaultPage.qml') : getPageUrl('', 'ForecastContainerPage.qml')); 114 | case "Locations": 115 | return pagePool.loadPage("qrc:/qt/qml/org/kde/kweather/locations/qml/locationslist/LocationsListPage.qml"); 116 | case "Settings": 117 | return pagePool.loadPage(getPageUrl('settings', 'settings/SettingsPage.qml')); 118 | case "Default": 119 | return pagePool.loadPage(getPageUrl('', 'DefaultPage.qml')); 120 | } 121 | } 122 | 123 | function getPageUrl(module: string, file: string): string { 124 | if (module === '') { 125 | return `qrc:/qt/qml/org/kde/kweather/qml/${file}`; 126 | } 127 | return `qrc:/qt/qml/org/kde/kweather/${module}/qml/${file}`; 128 | } 129 | 130 | function openSettings() { 131 | if (isWideScreen) { 132 | settingsDialogLoader.active = true; 133 | if (Kirigami.Settings.isMobile) { 134 | // SettingsDialog 135 | settingsDialogLoader.item.open(); 136 | } else { 137 | // SettingsWindow 138 | settingsDialogLoader.item.close(); 139 | settingsDialogLoader.item.show(); 140 | } 141 | } else { 142 | applicationWindow().pushPage(getPage("Settings"), 0); 143 | } 144 | } 145 | 146 | function openLocationsList() { 147 | if (isWideScreen) { 148 | locationsListDialogLoader.active = true; 149 | locationsListDialogLoader.item.open(); 150 | } else { 151 | applicationWindow().pushPage(getPage("Locations"), 0); 152 | } 153 | } 154 | 155 | function openAddLocation() { 156 | if (isWideScreen) { 157 | addLocationDialogLoader.active = true; 158 | addLocationDialogLoader.item.open(); 159 | } else { 160 | applicationWindow().pageStack.push(getPageUrl('locations', 'locationslist/AddLocationPage.qml')); 161 | } 162 | } 163 | 164 | Connections { 165 | target: WeatherLocationListModel 166 | 167 | function onLocationsChanged() { 168 | // wwitch to default page if the count is zero 169 | if (WeatherLocationListModel.count === 0) { 170 | applicationWindow().switchToPage(applicationWindow().getPage("Default"), 0); 171 | } 172 | } 173 | } 174 | 175 | Loader { 176 | id: settingsDialogLoader 177 | property bool isOpen: item && item.visible 178 | active: false 179 | sourceComponent: Kirigami.Settings.isMobile ? Qt.createComponent("org.kde.kweather.settings", "SettingsDialog") : Qt.createComponent("org.kde.kweather.settings", "SettingsWindow") 180 | } 181 | 182 | Loader { 183 | id: addLocationDialogLoader 184 | property bool isOpen: item && item.visible 185 | active: false 186 | sourceComponent: AddLocationDialog {} 187 | } 188 | 189 | Loader { 190 | id: locationsListDialogLoader 191 | property bool isOpen: item && item.visible 192 | active: false 193 | sourceComponent: LocationsListDialog {} 194 | } 195 | 196 | FontLoader { 197 | id: lightHeadingFont 198 | source: "qrc:/resources/NotoSans-Light.ttf" 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /src/qml/SunriseCard.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Han Young 3 | * Copyright 2020 Devin Lin 4 | * SPDX-FileCopyrightText: 2021 Nicolas Fella 5 | * 6 | * SPDX-License-Identifier: GPL-2.0-or-later 7 | */ 8 | 9 | import QtQuick 10 | import QtQuick.Controls 11 | import QtQuick.Layouts 12 | import QtCharts 13 | 14 | import org.kde.kirigami as Kirigami 15 | import org.kde.kholidays 16 | import org.kde.kweather 17 | 18 | import org.kde.kweather.backgrounds 19 | 20 | Kirigami.AbstractCard { 21 | id: root 22 | topPadding: Kirigami.Units.gridUnit 23 | bottomPadding: Kirigami.Units.gridUnit 24 | leftPadding: Kirigami.Units.gridUnit 25 | rightPadding: Kirigami.Units.gridUnit 26 | 27 | required property var selectedDay 28 | property var textColor: Kirigami.Theme.textColor 29 | 30 | contentItem: Item { 31 | implicitHeight: sunsetColumn.height 32 | 33 | Column { 34 | id: sunsetColumn 35 | spacing: Kirigami.Units.largeSpacing * 2 36 | 37 | // Sunrise 38 | RowLayout { 39 | spacing: Kirigami.Units.gridUnit 40 | Kirigami.Icon { 41 | source: "weather-clear-symbolic" 42 | implicitHeight: Kirigami.Units.iconSizes.medium 43 | implicitWidth: Kirigami.Units.iconSizes.medium 44 | color: settingsModel && settingsModel.forecastStyle === "Dynamic" ? weatherLocation.iconColor : Kirigami.Theme.textColor 45 | } 46 | Column { 47 | spacing: Kirigami.Units.smallSpacing 48 | Label { 49 | font.weight: Font.Bold 50 | text: i18n("Sunrise") 51 | color: root.textColor 52 | } 53 | Label { 54 | text: Formatter.formatSunriseTime(SunRiseSet.utcSunrise(selectedDay.date, weatherLocation.latitude, weatherLocation.longitude), weatherLocation.timeZone) 55 | color: root.textColor 56 | } 57 | } 58 | } 59 | 60 | // Sunset 61 | RowLayout { 62 | spacing: Kirigami.Units.gridUnit 63 | Kirigami.Icon { 64 | source: "weather-clear-symbolic" 65 | implicitHeight: Kirigami.Units.iconSizes.medium 66 | implicitWidth: Kirigami.Units.iconSizes.medium 67 | color: settingsModel && settingsModel.forecastStyle === "Dynamic" ? weatherLocation.iconColor : Kirigami.Theme.textColor 68 | } 69 | Column { 70 | spacing: Kirigami.Units.smallSpacing 71 | Label { 72 | font.weight: Font.Bold 73 | text: i18n("Sunset") 74 | color: root.textColor 75 | } 76 | Label { 77 | text: Formatter.formatSunriseTime(SunRiseSet.utcSunset(selectedDay.date, weatherLocation.latitude, weatherLocation.longitude), weatherLocation.timeZone) 78 | color: root.textColor 79 | } 80 | } 81 | } 82 | 83 | // Moon phase 84 | RowLayout { 85 | spacing: Kirigami.Units.gridUnit 86 | Kirigami.Icon { 87 | source: "weather-clear-night-symbolic" 88 | implicitHeight: Kirigami.Units.iconSizes.medium 89 | implicitWidth: Kirigami.Units.iconSizes.medium 90 | color: settingsModel && settingsModel.forecastStyle === "Dynamic" ? weatherLocation.iconColor : Kirigami.Theme.textColor 91 | } 92 | Column { 93 | spacing: Kirigami.Units.smallSpacing 94 | Label { 95 | font.weight: Font.Bold 96 | text: i18n("Moon Phase") 97 | color: root.textColor 98 | } 99 | Label { 100 | text: Lunar.phaseNameAtDate(selectedDay.date) 101 | color: root.textColor 102 | } 103 | } 104 | } 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/qml/TemperatureChartCard.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Han Young 3 | * Copyright 2020 Devin Lin 4 | * SPDX-FileCopyrightText: 2021 Nicolas Fella 5 | * 6 | * SPDX-License-Identifier: GPL-2.0-or-later 7 | */ 8 | 9 | import QtQuick 10 | import QtQuick.Controls 11 | import QtQuick.Layouts 12 | import QtCharts 13 | import org.kde.kirigami as Kirigami 14 | import org.kde.kweather 15 | import org.kde.kweather.backgrounds 16 | 17 | Kirigami.AbstractCard { 18 | id: tempChartCard 19 | 20 | required property WeatherLocation location 21 | property var backgroundColor: location.cardBackgroundColor 22 | property var textColor: location.cardTextColor 23 | 24 | leftPadding: 0 25 | rightPadding: 0 26 | topPadding: 0 27 | bottomPadding: 0 28 | implicitHeight: Math.round(Kirigami.Units.gridUnit * 8.5) 29 | 30 | clip: true 31 | 32 | contentItem: ScrollView { 33 | id: scrollView 34 | contentHeight: -1 35 | contentWidth: page.maximumContentWidth 36 | 37 | Item { 38 | width: page.maximumContentWidth 39 | height: Math.round(Kirigami.Units.gridUnit * 8.5) 40 | 41 | TemperatureChartData { 42 | id: chartData 43 | weatherData: tempChartCard.location.dayForecasts 44 | } 45 | 46 | ChartView { 47 | id: chartView 48 | anchors.fill: parent 49 | margins.left: Kirigami.Units.largeSpacing 50 | margins.right: Kirigami.Units.largeSpacing 51 | margins.top: 0 52 | margins.bottom: Kirigami.Units.smallSpacing 53 | legend.visible: false 54 | antialiasing: true 55 | localizeNumbers: true 56 | animationOptions: ChartView.NoAnimation 57 | backgroundColor: tempChartCard.backgroundColor 58 | plotAreaColor: tempChartCard.backgroundColor 59 | 60 | height: tempChartCard.height 61 | width: Math.max(Kirigami.Units.gridUnit * 25, tempChartCard.width) 62 | 63 | SplineSeries { 64 | id: splineSeries 65 | axisX: DateTimeAxis { 66 | id: axisX 67 | tickCount: dailyListView.count 68 | format: "ddd" 69 | labelsColor: tempChartCard.textColor 70 | lineVisible: false 71 | gridLineColor: Qt.rgba(tempChartCard.textColor.r, tempChartCard.textColor.g, tempChartCard.textColor.b, 0.05) 72 | } 73 | axisY: ValueAxis { 74 | id: axisY 75 | visible: false 76 | 77 | min: chartData.minTempLimit 78 | max: chartData.maxTempLimit 79 | labelsColor: tempChartCard.textColor 80 | } 81 | name: i18n("temperature") 82 | pointLabelsVisible: true 83 | pointLabelsFormat: "@yPoint°" 84 | pointLabelsClipping: false 85 | pointLabelsColor: tempChartCard.textColor 86 | pointLabelsFont.pointSize: Kirigami.Theme.defaultFont.pointSize 87 | } 88 | 89 | Component.onCompleted: { 90 | chartData.initAxes(axisX, axisY); 91 | chartData.initSeries(chartView.series(0)); 92 | } 93 | Component.onDestruction: { 94 | // ensure that the series is a nullptr 95 | chartData.initSeries(null); 96 | } 97 | } 98 | 99 | // allow continuous mouse scrolling 100 | MouseArea { 101 | anchors.fill: parent 102 | property int scrollDist: Kirigami.Units.gridUnit * 2 103 | onWheel: wheel => { 104 | //check if mouse is scrolling up or down 105 | if (wheel.angleDelta.y < 0) { 106 | page.flickable.contentY += scrollDist; 107 | } else { 108 | page.flickable.contentY -= scrollDist; 109 | } 110 | } 111 | onPressed: mouse => mouse.accepted = false // forward mouse event 112 | onReleased: mouse => mouse.accepted = false // forward mouse event 113 | } 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/qml/WeatherDayDelegate.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Han Young 3 | * SPDX-FileCopyrightText: 2020 Devin Lin 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | */ 7 | 8 | import QtQuick 9 | import QtQuick.Controls 10 | import QtQuick.Layouts 11 | import QtQuick.Shapes 12 | import org.kde.kirigami as Kirigami 13 | import org.kde.kweather 14 | 15 | Item { 16 | id: root 17 | 18 | implicitWidth: Kirigami.Units.gridUnit * 6 19 | implicitHeight: dayElement.implicitHeight + Kirigami.Units.largeSpacing * 2 20 | 21 | // implicitHeight: Kirigami.Units.gridUnit * 8 22 | 23 | property var weather 24 | property color textColor 25 | property color secondaryTextColor 26 | property alias mouseArea: mouse 27 | 28 | MouseArea { 29 | id: mouse 30 | anchors.fill: parent 31 | onClicked: dailyListView.currentIndex = index 32 | } 33 | 34 | // actual day display 35 | ColumnLayout { 36 | id: dayElement 37 | anchors.top: parent.top 38 | anchors.left: parent.left 39 | anchors.right: parent.right 40 | anchors.leftMargin: Kirigami.Units.largeSpacing 41 | anchors.topMargin: Kirigami.Units.largeSpacing 42 | anchors.rightMargin: Kirigami.Units.largeSpacing 43 | spacing: Kirigami.Units.smallSpacing 44 | 45 | Label { 46 | Layout.fillWidth: true 47 | Layout.topMargin: Kirigami.Units.smallSpacing 48 | font.pointSize: Kirigami.Theme.defaultFont.pointSize * 1 49 | text: root.weather.date.toLocaleString(Qt.locale(), "ddd d").replace(".", "") 50 | color: root.textColor 51 | elide: Text.ElideRight 52 | } 53 | Kirigami.Icon { 54 | Layout.topMargin: Kirigami.Units.smallSpacing 55 | source: root.weather.weatherIcon 56 | implicitHeight: Kirigami.Units.iconSizes.medium 57 | implicitWidth: Kirigami.Units.iconSizes.medium 58 | } 59 | Row { 60 | Layout.topMargin: Kirigami.Units.smallSpacing 61 | spacing: Kirigami.Theme.defaultFont.pointSize * 0.6 62 | Label { 63 | id: highTemp 64 | font.pointSize: Kirigami.Theme.defaultFont.pointSize * 1.3 65 | text: Formatter.formatTemperatureRounded(root.weather.maxTemp, settingsModel.temperatureUnits) 66 | color: root.textColor 67 | } 68 | Label { 69 | anchors.baseline: highTemp.baseline 70 | color: root.secondaryTextColor 71 | font.pointSize: Kirigami.Theme.defaultFont.pointSize * 1 72 | text: Formatter.formatTemperatureRounded(root.weather.minTemp, settingsModel.temperatureUnits) 73 | } 74 | } 75 | Label { 76 | Layout.fillWidth: true 77 | Layout.topMargin: Kirigami.Units.smallSpacing 78 | text: root.weather.weatherDescription 79 | color: root.textColor 80 | wrapMode: Text.Wrap 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/qml/WeatherHourDelegate.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Han Young 3 | * SPDX-FileCopyrightText: 2020 Devin Lin 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | */ 7 | 8 | import QtQuick 9 | import QtQuick.Controls 10 | import QtQuick.Layouts 11 | import QtQuick.Shapes 12 | import org.kde.kirigami as Kirigami 13 | import org.kde.kweather 14 | 15 | Rectangle { 16 | id: root 17 | 18 | implicitWidth: Kirigami.Units.gridUnit * 5 19 | implicitHeight: hourElement.height 20 | color: "transparent" 21 | 22 | property var weather 23 | property color textColor 24 | property color secondaryTextColor 25 | property alias mouseArea: mouse 26 | 27 | MouseArea { 28 | id: mouse 29 | anchors.fill: parent 30 | } 31 | 32 | // actual hour display 33 | ColumnLayout { 34 | id: hourElement 35 | anchors.left: parent.left 36 | anchors.right: parent.right 37 | anchors.leftMargin: Kirigami.Units.largeSpacing 38 | anchors.rightMargin: Kirigami.Units.largeSpacing 39 | spacing: Kirigami.Units.smallSpacing 40 | 41 | Kirigami.Icon { 42 | source: root.weather.weatherIcon 43 | Layout.preferredHeight: Kirigami.Units.iconSizes.medium 44 | Layout.preferredWidth: Kirigami.Units.iconSizes.medium 45 | } 46 | Label { 47 | Layout.fillWidth: true 48 | text: Formatter.formatTemperature(root.weather.temperature, settingsModel.temperatureUnits) 49 | font.pointSize: Kirigami.Theme.defaultFont.pointSize * 1.3 50 | color: root.textColor 51 | elide: Text.ElideRight 52 | } 53 | Item { 54 | Layout.fillWidth: true 55 | implicitHeight: descriptionTextMetrics.implicitHeight 56 | 57 | Label { 58 | anchors.top: parent.top 59 | anchors.left: parent.left 60 | anchors.right: parent.right 61 | text: root.weather.weatherDescription 62 | color: root.textColor 63 | wrapMode: Text.Wrap 64 | elide: Text.ElideRight 65 | maximumLineCount: 2 66 | } 67 | 68 | // ensure that the height of the reserved space for the labels is always two text lines 69 | Label { 70 | id: descriptionTextMetrics 71 | anchors.top: parent.top 72 | anchors.left: parent.left 73 | anchors.right: parent.right 74 | visible: false 75 | text: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' 76 | color: root.textColor 77 | wrapMode: Text.Wrap 78 | maximumLineCount: 2 79 | } 80 | } 81 | 82 | // precipitation 83 | RowLayout { 84 | Kirigami.Icon { 85 | source: "raindrop" 86 | Layout.preferredHeight: Kirigami.Units.iconSizes.small 87 | Layout.preferredWidth: Kirigami.Units.iconSizes.small 88 | color: settingsModel && settingsModel.forecastStyle === "Dynamic" ? weatherLocation.iconColor : Kirigami.Theme.textColor 89 | isMask: true 90 | } 91 | Label { 92 | color: root.secondaryTextColor 93 | text: Formatter.formatPrecipitation(root.weather.precipitationAmount, settingsModel.precipitationUnits) 94 | } 95 | } 96 | 97 | // wind 98 | RowLayout { 99 | Kirigami.Icon { 100 | source: "arrow-right" 101 | Layout.preferredHeight: Kirigami.Units.iconSizes.small 102 | Layout.preferredWidth: Kirigami.Units.iconSizes.small 103 | color: settingsModel && settingsModel.forecastStyle === "Dynamic" ? weatherLocation.iconColor : Kirigami.Theme.textColor 104 | isMask: true 105 | } 106 | Label { 107 | color: root.secondaryTextColor 108 | text: Formatter.formatWindSpeed(root.weather.windSpeed, settingsModel.speedUnits) 109 | } 110 | } 111 | 112 | Label { 113 | Layout.fillWidth: true 114 | font.weight: Font.Bold 115 | text: Formatter.formatHourlyCardDelegateTime(root.weather.date, weatherLocation.timeZone) 116 | color: root.textColor 117 | wrapMode: Text.Wrap 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/qml/WeatherStrip.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Han Young 3 | * SPDX-FileCopyrightText: 2020 Devin Lin 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | */ 7 | 8 | import QtQuick 9 | import QtQuick.Controls 10 | import QtQuick.Layouts 11 | import QtCharts 12 | import org.kde.kirigami as Kirigami 13 | import org.kde.kweather.backgrounds 14 | 15 | ListView { 16 | id: root 17 | orientation: ListView.Horizontal 18 | implicitHeight: contentItem.childrenRect.height 19 | spacing: Kirigami.Units.largeSpacing * 3 20 | clip: true 21 | 22 | snapMode: ListView.SnapToItem 23 | 24 | property bool selectable: false 25 | 26 | // detect mouse hover 27 | HoverHandler { 28 | id: hoverMouseArea 29 | acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus 30 | } 31 | 32 | // left right mouse controls 33 | Button { 34 | icon.name: "arrow-left" 35 | anchors.left: parent.left 36 | anchors.verticalCenter: parent.verticalCenter 37 | visible: !Kirigami.Settings.isMobile && hoverMouseArea.hovered && (root.currentIndex > 0 || !root.atXBeginning) 38 | 39 | onClicked: { 40 | if (root.selectable) { 41 | root.decrementCurrentIndex(); 42 | } else { 43 | animateMove.to -= root.contentItem.children[0].width + root.spacing; 44 | animateMove.start(); 45 | } 46 | } 47 | } 48 | 49 | Button { 50 | icon.name: "arrow-right" 51 | anchors.right: parent.right 52 | anchors.verticalCenter: parent.verticalCenter 53 | visible: !Kirigami.Settings.isMobile && hoverMouseArea.hovered && (root.currentIndex < root.count - 1 || !root.atXEnd) 54 | 55 | onClicked: { 56 | if (root.selectable) { 57 | root.incrementCurrentIndex(); 58 | } else { 59 | animateMove.to += root.contentItem.children[0].width + root.spacing; 60 | animateMove.start(); 61 | } 62 | } 63 | } 64 | 65 | NumberAnimation on contentX { 66 | id: animateMove 67 | to: root.contentX 68 | duration: Kirigami.Units.longDuration 69 | easing.type: Easing.InOutQuad 70 | onFinished: root.returnToBounds() 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/qml/backgrounds/ClearDay.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Han Young 3 | * SPDX-FileCopyrightText: 2020 Devin Lin 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | */ 7 | import QtQuick 8 | import QtQuick.Controls 9 | import QtQuick.Layouts 10 | import QtQuick.Shapes 11 | import org.kde.kirigami as Kirigami 12 | import org.kde.kweather.backgrounds.components 13 | 14 | DynamicBackground { 15 | gradientColorTop: "#ffc107" 16 | gradientColorBottom: "#ffc107" 17 | sun: true 18 | } 19 | -------------------------------------------------------------------------------- /src/qml/backgrounds/ClearNight.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Han Young 3 | * SPDX-FileCopyrightText: 2020 Devin Lin 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | */ 7 | import QtQuick 8 | import QtQuick.Controls 9 | import QtQuick.Layouts 10 | import QtQuick.Shapes 11 | import org.kde.kirigami as Kirigami 12 | import org.kde.kweather.backgrounds.components 13 | 14 | DynamicBackground { 15 | gradientColorTop: "#455a64" 16 | gradientColorBottom: "#263238" 17 | stars: true 18 | } 19 | -------------------------------------------------------------------------------- /src/qml/backgrounds/CloudyDay.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Han Young 3 | * SPDX-FileCopyrightText: 2020 Devin Lin 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | */ 7 | import QtQuick 8 | import QtQuick.Controls 9 | import QtQuick.Layouts 10 | import QtQuick.Shapes 11 | import org.kde.kirigami as Kirigami 12 | import org.kde.kweather.backgrounds.components 13 | 14 | DynamicBackground { 15 | gradientColorTop: "#00bcd4" 16 | gradientColorBottom: "#24a3de" 17 | clouds: true 18 | cloudsColor: "#e0f7fa" 19 | } 20 | -------------------------------------------------------------------------------- /src/qml/backgrounds/CloudyNight.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Han Young 3 | * SPDX-FileCopyrightText: 2020 Devin Lin 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | */ 7 | import QtQuick 8 | import QtQuick.Controls 9 | import QtQuick.Layouts 10 | import QtQuick.Shapes 11 | import org.kde.kirigami as Kirigami 12 | import org.kde.kweather.backgrounds.components 13 | 14 | DynamicBackground { 15 | gradientColorTop: "#455a64" 16 | gradientColorBottom: "#263238" 17 | stars: true 18 | clouds: true 19 | cloudsColor: "#b0bec5" 20 | } 21 | -------------------------------------------------------------------------------- /src/qml/backgrounds/DynamicBackground.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Han Young 3 | * SPDX-FileCopyrightText: 2020 Devin Lin 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | */ 7 | import QtQuick 8 | import QtQuick.Controls 9 | import QtQuick.Layouts 10 | import QtQuick.Shapes 11 | import org.kde.kirigami as Kirigami 12 | import org.kde.kweather.backgrounds.components 13 | 14 | // parent of all weather background components 15 | Rectangle { 16 | color: "transparent" 17 | anchors.fill: parent 18 | opacity: 0 // animated intro 19 | 20 | // specify background colour 21 | property color gradientColorTop: "white" 22 | property color gradientColorBottom: "white" 23 | 24 | property bool sun: false 25 | 26 | property bool clouds: false 27 | property color cloudsColor: "white" 28 | 29 | property bool stars: false 30 | 31 | property bool rain: false 32 | 33 | property bool snow: false 34 | } 35 | -------------------------------------------------------------------------------- /src/qml/backgrounds/Misty.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Han Young 3 | * SPDX-FileCopyrightText: 2020 Devin Lin 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | */ 7 | import QtQuick 8 | import QtQuick.Controls 9 | import QtQuick.Layouts 10 | import QtQuick.Shapes 11 | import org.kde.kirigami as Kirigami 12 | import org.kde.kweather.backgrounds.components 13 | 14 | DynamicBackground { 15 | gradientColorTop: "#c2c5cb" 16 | gradientColorBottom: "#5b606b" 17 | clouds: true 18 | cloudsColor: "#dae0ec" 19 | } 20 | -------------------------------------------------------------------------------- /src/qml/backgrounds/PartlyCloudyDay.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Han Young 3 | * SPDX-FileCopyrightText: 2020 Devin Lin 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | */ 7 | import QtQuick 8 | import QtQuick.Controls 9 | import QtQuick.Layouts 10 | import QtQuick.Shapes 11 | import org.kde.kirigami as Kirigami 12 | import org.kde.kweather.backgrounds.components 13 | 14 | DynamicBackground { 15 | gradientColorTop: "#00bcd4" 16 | gradientColorBottom: "#24a3de" 17 | clouds: true 18 | cloudsColor: "#e0f7fa" 19 | } 20 | -------------------------------------------------------------------------------- /src/qml/backgrounds/PartlyCloudyNight.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Han Young 3 | * SPDX-FileCopyrightText: 2020 Devin Lin 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | */ 7 | import QtQuick 8 | import QtQuick.Controls 9 | import QtQuick.Layouts 10 | import QtQuick.Shapes 11 | import org.kde.kirigami as Kirigami 12 | import org.kde.kweather.backgrounds.components 13 | 14 | DynamicBackground { 15 | gradientColorTop: "#455a64" 16 | gradientColorBottom: "#263238" 17 | stars: true 18 | clouds: true 19 | cloudsColor: "#b0bec5" 20 | } 21 | -------------------------------------------------------------------------------- /src/qml/backgrounds/RainyDay.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Han Young 3 | * SPDX-FileCopyrightText: 2020 Devin Lin 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | */ 7 | import QtQuick 8 | import QtQuick.Controls 9 | import QtQuick.Layouts 10 | import QtQuick.Shapes 11 | import org.kde.kirigami as Kirigami 12 | import org.kde.kweather.backgrounds.components 13 | 14 | DynamicBackground { 15 | gradientColorTop: "#369ccb" 16 | gradientColorBottom: "#1A83B3" 17 | clouds: true 18 | cloudsColor: "white" 19 | rain: true 20 | } 21 | -------------------------------------------------------------------------------- /src/qml/backgrounds/RainyNight.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Han Young 3 | * SPDX-FileCopyrightText: 2020 Devin Lin 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | */ 7 | import QtQuick 8 | import QtQuick.Controls 9 | import QtQuick.Layouts 10 | import QtQuick.Shapes 11 | import org.kde.kirigami as Kirigami 12 | import org.kde.kweather.backgrounds.components 13 | 14 | DynamicBackground { 15 | gradientColorTop: "#455a64" 16 | gradientColorBottom: "#263238" 17 | clouds: true 18 | cloudsColor: "#b0bec5" 19 | rain: true 20 | } 21 | -------------------------------------------------------------------------------- /src/qml/backgrounds/SnowyDay.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Han Young 3 | * SPDX-FileCopyrightText: 2020 Devin Lin 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | */ 7 | import QtQuick 8 | import QtQuick.Controls 9 | import QtQuick.Layouts 10 | import QtQuick.Shapes 11 | import org.kde.kirigami as Kirigami 12 | import org.kde.kweather.backgrounds.components 13 | 14 | DynamicBackground { 15 | gradientColorTop: "#35d9ed" 16 | gradientColorBottom: "#24a3de" 17 | snow: true 18 | } 19 | -------------------------------------------------------------------------------- /src/qml/backgrounds/SnowyNight.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Han Young 3 | * SPDX-FileCopyrightText: 2020 Devin Lin 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | */ 7 | import QtQuick 8 | import QtQuick.Controls 9 | import QtQuick.Layouts 10 | import QtQuick.Shapes 11 | import org.kde.kirigami as Kirigami 12 | import org.kde.kweather.backgrounds.components 13 | 14 | DynamicBackground { 15 | gradientColorTop: "#455a64" 16 | gradientColorBottom: "#263238" 17 | snow: true 18 | } 19 | -------------------------------------------------------------------------------- /src/qml/backgrounds/components/Cloud.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Han Young 3 | * SPDX-FileCopyrightText: 2020 Devin Lin 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | */ 7 | import QtQuick 8 | import QtQuick.Controls 9 | import QtQuick.Layouts 10 | import QtQuick.Shapes 11 | import org.kde.kirigami as Kirigami 12 | 13 | Item { 14 | id: rootShape 15 | anchors.fill: parent 16 | 17 | property double maxRadiusChange 18 | property double maxCoordChange 19 | property double minRadius 20 | property double minX 21 | property double minY 22 | property double radius: minRadius 23 | property double centerX: minX 24 | property double centerY: minY 25 | property color color 26 | 27 | NumberAnimation on radius { 28 | duration: 4000 29 | running: true 30 | easing.type: Easing.InOutQuad 31 | onFinished: { 32 | to = minRadius + Math.random() * maxRadiusChange; 33 | restart(); 34 | } 35 | } 36 | 37 | NumberAnimation on centerX { 38 | duration: 2600 39 | running: true 40 | easing.type: Easing.InOutQuad 41 | onFinished: { 42 | to = minX + Math.random() * maxCoordChange; 43 | restart(); 44 | } 45 | } 46 | 47 | NumberAnimation on centerY { 48 | duration: 3400 49 | running: true 50 | easing.type: Easing.InOutQuad 51 | onFinished: { 52 | to = minY + Math.random() * maxCoordChange; 53 | restart(); 54 | } 55 | } 56 | 57 | Rectangle { 58 | radius: width / 2 59 | color: rootShape.color 60 | width: rootShape.radius * 2 61 | height: width 62 | x: centerX - width / 2 63 | y: centerY - width / 2 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/qml/backgrounds/components/Cloudy.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Han Young 3 | * SPDX-FileCopyrightText: 2020 Devin Lin 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | */ 7 | import QtQuick 8 | import QtQuick.Controls 9 | import QtQuick.Layouts 10 | import QtQuick.Shapes 11 | import org.kde.kirigami as Kirigami 12 | 13 | Item { 14 | id: baseRect 15 | opacity: 0.65 16 | anchors.fill: parent 17 | property color cloudColor: "white" 18 | property double unitSize: width / 100 // 100 is full width of screen 19 | property double heightUnitSize: height / 100 20 | 21 | Cloud { 22 | opacity: 0.2 23 | color: baseRect.cloudColor 24 | maxRadiusChange: unitSize * 3 25 | maxCoordChange: unitSize * 6 26 | minRadius: unitSize * 40 27 | minX: unitSize * 10 28 | minY: heightUnitSize * -3 29 | } 30 | Cloud { 31 | opacity: 0.3 32 | color: baseRect.cloudColor 33 | maxRadiusChange: unitSize * 2 34 | maxCoordChange: unitSize * 5 35 | minRadius: unitSize * 40 36 | minX: unitSize * 40 37 | minY: heightUnitSize * 20 38 | } 39 | Cloud { 40 | opacity: 0.2 41 | color: baseRect.cloudColor 42 | maxRadiusChange: unitSize * 3 43 | maxCoordChange: unitSize * 2 44 | minRadius: unitSize * 23 45 | minX: unitSize * 85 46 | minY: heightUnitSize * 7 47 | } 48 | Cloud { 49 | opacity: 0.2 50 | color: baseRect.cloudColor 51 | maxRadiusChange: unitSize * 2 52 | maxCoordChange: unitSize * 6 53 | minRadius: unitSize * 40 54 | minX: unitSize * 110 55 | minY: heightUnitSize * -6 56 | } 57 | Cloud { 58 | opacity: 0.5 59 | color: baseRect.cloudColor 60 | maxRadiusChange: unitSize * 3 61 | maxCoordChange: unitSize * 2 62 | minRadius: unitSize * 32 63 | minX: unitSize * 32 64 | minY: heightUnitSize * 6 65 | } 66 | Cloud { 67 | opacity: 0.6 68 | color: baseRect.cloudColor 69 | maxRadiusChange: unitSize * 3 70 | maxCoordChange: unitSize * 6 71 | minRadius: unitSize * 24 72 | minX: unitSize * 48 73 | minY: heightUnitSize * 10 74 | } 75 | Cloud { 76 | opacity: 0.5 77 | color: baseRect.cloudColor 78 | maxRadiusChange: unitSize * 5 79 | maxCoordChange: unitSize * 2 80 | minRadius: unitSize * 27 81 | minX: unitSize * 90 82 | minY: heightUnitSize * 17 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/qml/backgrounds/components/Rain.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Han Young 3 | * SPDX-FileCopyrightText: 2020 Devin Lin 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | */ 7 | import QtQuick 8 | import QtQuick.Layouts 9 | 10 | Canvas { 11 | id: mycanvas 12 | 13 | property var particles: [] 14 | 15 | renderStrategy: Canvas.Threaded 16 | onPaint: { 17 | var ctx = getContext("2d"); 18 | ctx.strokeStyle = 'rgba(255,255,255,0.5)'; 19 | ctx.lineWidth = 3; 20 | ctx.lineCap = 'round'; 21 | ctx.clearRect(0, 0, width, height); 22 | function draw() { 23 | for (var c = 0; c < particles.length; c++) { 24 | var p = particles[c]; 25 | ctx.beginPath(); 26 | ctx.moveTo(p.x, p.y); 27 | ctx.lineTo(p.x, p.y + p.l * p.ys); 28 | ctx.stroke(); 29 | } 30 | move(); 31 | } 32 | function move() { 33 | for (var b = 0; b < particles.length; b++) { 34 | var p = particles[b]; 35 | p.y += p.ys; 36 | if (p.x < 1 || p.x > width || p.y > height) { 37 | p.x = Math.random() * width; 38 | p.y = -20; 39 | } 40 | } 41 | } 42 | draw(); 43 | } 44 | Timer { 45 | id: animationTimer 46 | interval: 16 47 | running: true 48 | repeat: true 49 | onTriggered: parent.requestPaint() 50 | } 51 | 52 | Component.onCompleted: { 53 | var init = []; 54 | var maxParts = 80; 55 | for (var a = 0; a < maxParts; a++) { 56 | init.push({ 57 | x: Math.random() * width, 58 | y: Math.random() * height, 59 | l: 2 + Math.random() * 4, 60 | ys: Math.random() * 10 + 10 61 | }); 62 | } 63 | particles = []; 64 | for (var b = 0; b < maxParts; b++) { 65 | particles[b] = init[b]; 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/qml/backgrounds/components/Snow.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Han Young 3 | * SPDX-FileCopyrightText: 2020 Devin Lin 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | */ 7 | import QtQuick 8 | import QtQuick.Layouts 9 | 10 | Canvas { 11 | id: mycanvas 12 | 13 | property var particles: [] 14 | 15 | renderStrategy: Canvas.Threaded 16 | onPaint: { 17 | var ctx = getContext("2d"); 18 | ctx.strokeStyle = 'rgba(255,255,255,0.5)'; 19 | ctx.lineWidth = 3; 20 | ctx.lineCap = 'round'; 21 | ctx.clearRect(0, 0, width, height); 22 | function draw() { 23 | for (var c = 0; c < particles.length; c++) { 24 | var p = particles[c]; 25 | ctx.beginPath(); 26 | context.arc(p.x, p.y + p.ys, p.l, 0, 2 * Math.PI, false); 27 | context.fillStyle = Qt.rgba(255, 255, 255, p.a); 28 | context.fill(); 29 | } 30 | move(); 31 | } 32 | function move() { 33 | for (var b = 0; b < particles.length; b++) { 34 | var p = particles[b]; 35 | p.y += p.ys; 36 | if (p.x < 1 || p.x > width || p.y > height) { 37 | p.x = Math.random() * width; 38 | p.y = -20; 39 | } 40 | } 41 | } 42 | draw(); 43 | } 44 | Timer { 45 | id: animationTimer 46 | interval: 16 47 | running: true 48 | repeat: true 49 | onTriggered: parent.requestPaint() 50 | } 51 | 52 | Component.onCompleted: { 53 | var init = []; 54 | var maxParts = 80; 55 | for (var a = 0; a < maxParts; a++) { 56 | init.push({ 57 | x: Math.random() * width, 58 | y: Math.random() * height, 59 | l: 5 + Math.random() * 10, 60 | ys: Math.random(), 61 | a: 0.4 + Math.random() * 0.6 62 | }); 63 | } 64 | particles = []; 65 | for (var b = 0; b < maxParts; b++) { 66 | particles[b] = init[b]; 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/qml/backgrounds/components/Stars.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Han Young 3 | * SPDX-FileCopyrightText: 2020 Devin Lin 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | */ 7 | import QtQuick 8 | import QtQuick.Controls 9 | import QtQuick.Layouts 10 | import QtQuick.Shapes 11 | import org.kde.kirigami as Kirigami 12 | 13 | Item { 14 | id: rootItem 15 | anchors.fill: parent 16 | property double starRadius: 1 17 | 18 | property double opacityModifier: 0 19 | 20 | NumberAnimation on opacityModifier { 21 | running: true 22 | to: 10000 23 | duration: Math.max(10000) 24 | onFinished: { 25 | if (rootItem.opacityModifier === 0) { 26 | to = 10000; 27 | } else { 28 | to = 0; 29 | } 30 | restart(); 31 | } 32 | } 33 | 34 | Repeater { 35 | model: 30 36 | 37 | Item { 38 | id: shape 39 | anchors.fill: rootItem 40 | 41 | property double starModifier: { 42 | let num = 5000 * Math.random(); 43 | if (num < 1000) { 44 | return 1000; 45 | } else { 46 | return num; 47 | } 48 | } 49 | 50 | opacity: { 51 | let remainder = rootItem.opacityModifier % (2 * starModifier); 52 | if (remainder > starModifier) { 53 | // opacity is decreasing 54 | return (starModifier - (remainder % starModifier)) / starModifier; 55 | } else { 56 | // opacity is increasing 57 | return remainder / starModifier; 58 | } 59 | } 60 | 61 | Rectangle { 62 | color: "#fff9c4" 63 | height: starRadius * 2 64 | width: height 65 | radius: width / 2 66 | x: Math.random() * rootItem.width 67 | y: Math.random() * rootItem.height 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/qml/backgrounds/components/Sun.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Han Young 3 | * SPDX-FileCopyrightText: 2020-2021 Devin Lin 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | */ 7 | import QtQuick 8 | import QtQuick.Controls 9 | import QtQuick.Layouts 10 | import QtQuick.Shapes 11 | import org.kde.kirigami as Kirigami 12 | 13 | Rectangle { 14 | id: rootBackground 15 | color: "transparent" 16 | anchors.fill: parent 17 | 18 | property double unitSize: width / 100 // 100 is full width of screen 19 | property double innerRotation: 0 20 | property double outerRotation: 0 21 | property double outerOuterRotation: 0 22 | 23 | NumberAnimation on innerRotation { 24 | to: 360 25 | duration: 20000 26 | running: true 27 | onFinished: { 28 | innerRotation = 0; 29 | restart(); 30 | } 31 | } 32 | 33 | NumberAnimation on outerRotation { 34 | to: 360 35 | duration: 15000 36 | running: true 37 | onFinished: { 38 | outerRotation = 0; 39 | restart(); 40 | } 41 | } 42 | 43 | NumberAnimation on outerOuterRotation { 44 | to: 360 45 | duration: 12000 46 | running: true 47 | onFinished: { 48 | outerOuterRotation = 0; 49 | restart(); 50 | } 51 | } 52 | // outer outer sun 53 | Repeater { 54 | model: 6 55 | Rectangle { 56 | width: unitSize * 120 57 | height: width 58 | color: "#ffa000" 59 | opacity: 0.4 60 | border.width: 0 61 | rotation: outerOuterRotation + index * (90 / 6) 62 | x: rootBackground.width - unitSize * 10 - width / 2 63 | y: unitSize * 10 - height / 2 64 | antialiasing: true 65 | } 66 | } 67 | 68 | // outer sun 69 | Repeater { 70 | model: 5 71 | Rectangle { 72 | width: unitSize * 60 73 | height: width 74 | color: "#ff8f00" 75 | opacity: 0.5 76 | border.width: 0 77 | rotation: outerRotation + index * (90 / 5) 78 | x: rootBackground.width - unitSize * 10 - width / 2 79 | y: unitSize * 10 - height / 2 80 | antialiasing: true 81 | } 82 | } 83 | 84 | // inner sun 85 | Repeater { 86 | model: 3 87 | Rectangle { 88 | width: unitSize * 30 89 | height: width 90 | color: "#ff6f00" 91 | opacity: 0.5 92 | border.width: 0 93 | rotation: innerRotation + index * (90 / 3) 94 | x: rootBackground.width - unitSize * 10 - width / 2 95 | y: unitSize * 10 - height / 2 96 | antialiasing: true 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/qml/components/ListDelegate.qml: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Devin Lin 2 | // SPDX-License-Identifier: GPL-2.0-or-later 3 | 4 | import QtQuick 5 | import QtQuick.Controls 6 | import QtQuick.Layouts 7 | 8 | import org.kde.kirigami as Kirigami 9 | 10 | Control { 11 | id: root 12 | 13 | property bool showSeparator: false 14 | 15 | signal clicked 16 | signal rightClicked 17 | signal released 18 | signal pressAndHold 19 | 20 | property alias mouseArea: mouseArea 21 | 22 | leftPadding: Kirigami.Units.largeSpacing 23 | topPadding: Kirigami.Units.largeSpacing 24 | bottomPadding: Kirigami.Units.largeSpacing 25 | rightPadding: Kirigami.Units.largeSpacing 26 | 27 | hoverEnabled: true 28 | background: Rectangle { 29 | color: Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, mouseArea.pressed ? 0.2 : (!Kirigami.Settings.tabletMode && hoverHandler.hovered) ? 0.1 : 0) 30 | 31 | HoverHandler { 32 | id: hoverHandler 33 | } 34 | 35 | Kirigami.Separator { 36 | anchors.bottom: parent.bottom 37 | anchors.left: parent.left 38 | anchors.right: parent.right 39 | anchors.leftMargin: root.leftPadding 40 | anchors.rightMargin: root.rightPadding 41 | visible: root.showSeparator 42 | opacity: 0.5 43 | } 44 | } 45 | 46 | MouseArea { 47 | id: mouseArea 48 | anchors.fill: parent 49 | acceptedButtons: Qt.LeftButton | Qt.RightButton 50 | 51 | onPressAndHold: root.pressAndHold() 52 | onReleased: root.released() 53 | onClicked: mouse => { 54 | if (mouse.button === Qt.RightButton) { 55 | root.rightClicked(); 56 | } else if (mouse.button === Qt.LeftButton) { 57 | root.clicked(); 58 | } 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/qml/locationslist/AddLocationDialog.qml: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Devin Lin 2 | // SPDX-License-Identifier: GPL-2.0-or-later 3 | 4 | import QtQuick 5 | import QtQuick.Controls 6 | import QtQuick.Layouts 7 | import org.kde.kirigami as Kirigami 8 | 9 | Kirigami.Dialog { 10 | id: root 11 | 12 | standardButtons: Kirigami.Dialog.NoButton 13 | parent: applicationWindow().overlay 14 | title: i18n("Add Location") 15 | preferredHeight: Kirigami.Units.gridUnit * 20 16 | preferredWidth: Kirigami.Units.gridUnit * 20 17 | padding: 0 18 | 19 | onOpened: addLocationListView.focusRequested() 20 | 21 | AddLocationListView { 22 | id: addLocationListView 23 | clip: true 24 | onCloseRequested: root.close() 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/qml/locationslist/AddLocationListView.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Han Young 3 | * SPDX-FileCopyrightText: 2020-2022 Devin Lin 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | */ 7 | 8 | import QtQuick 9 | import QtQuick.Controls 10 | import QtQuick.Layouts 11 | import org.kde.kirigami as Kirigami 12 | 13 | import org.kde.kweather 14 | 15 | import org.kde.kweather.components 16 | 17 | ListView { 18 | id: root 19 | 20 | property string searchQuery: "" 21 | 22 | model: LocationQueryModel {} 23 | 24 | signal closeRequested 25 | signal focusRequested 26 | 27 | header: Control { 28 | width: root.width 29 | leftPadding: Kirigami.Units.largeSpacing 30 | rightPadding: Kirigami.Units.largeSpacing 31 | topPadding: Kirigami.Units.largeSpacing 32 | bottomPadding: Kirigami.Units.largeSpacing 33 | 34 | contentItem: RowLayout { 35 | spacing: Kirigami.Units.smallSpacing 36 | 37 | Kirigami.SearchField { 38 | id: search 39 | Layout.fillWidth: true 40 | 41 | Connections { 42 | target: root 43 | function onFocusRequested() { 44 | search.forceActiveFocus(); 45 | } 46 | } 47 | 48 | placeholderText: i18n("Search for a location…") 49 | onTextChanged: { 50 | root.searchQuery = text; 51 | root.model.textChanged(text); 52 | root.currentIndex = 0; 53 | } 54 | onEditingFinished: { 55 | if (root.searchQuery === text) { 56 | // no change 57 | return; 58 | } 59 | root.searchQuery = text; 60 | root.model.textChanged(text, 0); // when return is pressed, query immediately 61 | root.currentIndex = 0; 62 | } 63 | KeyNavigation.down: root 64 | } 65 | 66 | Button { 67 | id: searchButton 68 | icon.name: "search" 69 | implicitHeight: search.implicitHeight 70 | implicitWidth: height 71 | enabled: search.text !== "" 72 | width: height 73 | height: search.height 74 | onClicked: root.model.textChanged(root.searchQuery, 0) 75 | } 76 | } 77 | } 78 | 79 | // unable to connect message 80 | Kirigami.PlaceholderMessage { 81 | anchors.centerIn: parent 82 | anchors.left: parent.left 83 | anchors.right: parent.right 84 | anchors.margins: Kirigami.Units.largeSpacing 85 | 86 | icon.name: "network-disconnect" 87 | text: i18n("Unable to connect") 88 | visible: root.model.networkError 89 | } 90 | 91 | // default message (has not searched yet) 92 | Kirigami.PlaceholderMessage { 93 | anchors.centerIn: parent 94 | anchors.left: parent.left 95 | anchors.right: parent.right 96 | anchors.margins: Kirigami.Units.largeSpacing 97 | 98 | icon.name: "search" 99 | text: i18n("Search for a location") 100 | visible: !root.model.networkError && !root.model.loading && root.count == 0 && root.searchQuery == "" 101 | 102 | // The Mozilla location service has shutdown, and so in many cases geoclue stops working and hangs... 103 | // Disable this for now until we have a good way to use GPS or other geolocation services. 104 | // 105 | // helpfulAction: Kirigami.Action { 106 | // icon.name: "mark-location" 107 | // text: i18n("Add current location") 108 | // onTriggered: { 109 | // WeatherLocationListModel.requestCurrentLocation(); 110 | // root.closeRequested(); 111 | // let page = getPage("Forecast"); 112 | // switchToPage(page, 0); 113 | // if (page.loading !== undefined) { 114 | // getPage("Forecast").loading = true; 115 | // } 116 | // } 117 | // } 118 | } 119 | 120 | // no results 121 | Kirigami.PlaceholderMessage { 122 | anchors.centerIn: parent 123 | anchors.left: parent.left 124 | anchors.right: parent.right 125 | anchors.margins: Kirigami.Units.largeSpacing 126 | 127 | icon.name: "search" 128 | text: i18n("No results") 129 | visible: !root.model.networkError && !root.model.loading && root.count == 0 && root.searchQuery != "" 130 | } 131 | 132 | // loading results indicator 133 | BusyIndicator { 134 | anchors.centerIn: parent 135 | running: root.model.loading && root.count === 0 136 | Layout.minimumWidth: Kirigami.Units.iconSizes.huge 137 | Layout.minimumHeight: width 138 | } 139 | 140 | delegate: ListDelegate { 141 | width: root.width 142 | showSeparator: model.index != root.count - 1 143 | 144 | leftPadding: Kirigami.Units.largeSpacing 145 | rightPadding: Kirigami.Units.largeSpacing 146 | topPadding: Kirigami.Units.largeSpacing + Kirigami.Units.smallSpacing * 2 147 | bottomPadding: Kirigami.Units.largeSpacing + Kirigami.Units.smallSpacing * 2 148 | 149 | function apply() { 150 | root.model.addLocation(index); 151 | applicationWindow().getPage("Forecast").switchPageIndex(WeatherLocationListModel.count - 1); 152 | switchToPage(appwindow.getPage("Forecast"), 0); 153 | root.closeRequested(); 154 | } 155 | 156 | onClicked: apply() 157 | Keys.onPressed: event => { 158 | if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { 159 | apply(); 160 | event.accepted = true; 161 | } else { 162 | event.accepted = false; 163 | } 164 | } 165 | 166 | contentItem: RowLayout { 167 | spacing: Kirigami.Units.largeSpacing 168 | 169 | Label { 170 | Layout.fillWidth: true 171 | Layout.alignment: Qt.AlignVCenter 172 | text: model.name 173 | } 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /src/qml/locationslist/AddLocationPage.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Han Young 3 | * SPDX-FileCopyrightText: 2020-2021 Devin Lin 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | */ 7 | 8 | import QtQuick 9 | import QtQuick.Controls 10 | import QtQuick.Layouts 11 | import org.kde.kirigami as Kirigami 12 | 13 | Kirigami.ScrollablePage { 14 | id: root 15 | title: i18n("Add location") 16 | 17 | Component.onCompleted: addLocationListView.focusRequested() 18 | 19 | AddLocationListView { 20 | id: addLocationListView 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/qml/locationslist/LocationsListDialog.qml: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Devin Lin 2 | // SPDX-License-Identifier: GPL-2.0-or-later 3 | 4 | import QtQuick 5 | import QtQuick.Controls 6 | import QtQuick.Layouts 7 | import org.kde.kirigami as Kirigami 8 | 9 | Kirigami.Dialog { 10 | id: root 11 | 12 | standardButtons: Kirigami.Dialog.NoButton 13 | parent: applicationWindow().overlay 14 | title: i18n("Locations") 15 | preferredHeight: Kirigami.Units.gridUnit * 20 16 | preferredWidth: Kirigami.Units.gridUnit * 20 17 | padding: 0 18 | 19 | customFooterActions: [ 20 | Kirigami.Action { 21 | icon.name: "list-add" 22 | text: i18n("Add Location") 23 | onTriggered: { 24 | root.close(); 25 | applicationWindow().openAddLocation(); 26 | } 27 | } 28 | ] 29 | 30 | LocationsListView { 31 | onCloseRequested: root.close() 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/qml/locationslist/LocationsListPage.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Han Young 3 | * SPDX-FileCopyrightText: 2020-2022 Devin Lin 4 | * SPDX-FileCopyrightText: 2021 Nicolas Fella 5 | * 6 | * SPDX-License-Identifier: GPL-2.0-or-later 7 | */ 8 | 9 | import QtQuick 10 | import QtQuick.Controls 11 | import QtQuick.Layouts 12 | import org.kde.kirigami as Kirigami 13 | 14 | Kirigami.ScrollablePage { 15 | title: i18n("Locations") 16 | 17 | property real yTranslate: 0 18 | property int currentIndex: 0 19 | 20 | actions: [ 21 | Kirigami.Action { 22 | icon.name: "list-add" 23 | text: i18n("Add Location") 24 | onTriggered: applicationWindow().openAddLocation() 25 | } 26 | ] 27 | 28 | LocationsListView { 29 | transform: Translate { 30 | y: yTranslate 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/qml/settings/PrecipitationUnitComboBox.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020-2024 Devin Lin 2 | // SPDX-License-Identifier: GPL-2.0-or-later 3 | 4 | import QtQuick 5 | import QtQuick.Controls 6 | import org.kde.kirigami as Kirigami 7 | import org.kde.kirigamiaddons.formcard as FormCard 8 | 9 | FormCard.FormComboBoxDelegate { 10 | id: root 11 | 12 | text: i18n("Precipitation Units") 13 | currentIndex: indexOfValue(settingsModel.precipitationUnits) 14 | textRole: "name" 15 | valueRole: "value" 16 | onActivated: settingsModel.save() 17 | onCurrentValueChanged: settingsModel.precipitationUnits = currentValue 18 | 19 | model: ListModel { 20 | // we can't use i18n with ListElement 21 | Component.onCompleted: { 22 | append({ 23 | "name": i18nc("Millimeters", "mm"), 24 | "value": "mm" 25 | }); 26 | append({ 27 | "name": i18nc("Inches", "in"), 28 | "value": "in" 29 | }); 30 | // indexOfValue doesn't bind to model changes unfortunately, set currentIndex manually here 31 | root.currentIndex = root.indexOfValue(settingsModel.precipitationUnits); 32 | } 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/qml/settings/PressureUnitComboBox.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020-2024 Devin Lin 2 | // SPDX-License-Identifier: GPL-2.0-or-later 3 | 4 | import QtQuick 5 | import QtQuick.Controls 6 | import org.kde.kirigami as Kirigami 7 | import org.kde.kirigamiaddons.formcard as FormCard 8 | 9 | FormCard.FormComboBoxDelegate { 10 | id: root 11 | 12 | text: i18n("Pressure Units") 13 | currentIndex: indexOfValue(settingsModel.pressureUnits) 14 | textRole: "name" 15 | valueRole: "value" 16 | onActivated: settingsModel.save() 17 | onCurrentValueChanged: settingsModel.pressureUnits = currentValue 18 | 19 | model: ListModel { 20 | // we can't use i18n with ListElement 21 | Component.onCompleted: { 22 | append({ 23 | "name": i18nc("Hectopascal Pressure", "hPa"), 24 | "value": "hPa" 25 | }); 26 | append({ 27 | "name": i18nc("Millimetre of mercury", "mmHg"), 28 | "value": "mmHg" 29 | }); 30 | // indexOfValue doesn't bind to model changes unfortunately, set currentIndex manually here 31 | root.currentIndex = root.indexOfValue(settingsModel.pressureUnits); 32 | } 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/qml/settings/SettingsComponent.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Han Young 3 | * SPDX-FileCopyrightText: 2020-2022 Devin Lin 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | */ 7 | 8 | import QtQuick 9 | import QtQuick.Controls 10 | import QtQuick.Layouts 11 | import org.kde.kirigami as Kirigami 12 | import org.kde.kirigamiaddons.formcard as FormCard 13 | 14 | ColumnLayout { 15 | id: root 16 | 17 | property var dialog: null // dialog component if this is within a dialog 18 | 19 | signal closeRequested() 20 | 21 | spacing: 0 22 | 23 | // HACK: dialog switching requires some time between closing and opening 24 | Timer { 25 | id: dialogTimer 26 | 27 | property var dialog 28 | 29 | interval: 1 30 | onTriggered: { 31 | root.dialog.close(); 32 | dialog.open(); 33 | } 34 | } 35 | 36 | FormCard.FormHeader { 37 | title: i18n("General") 38 | } 39 | 40 | FormCard.FormCard { 41 | FormCard.FormComboBoxDelegate { 42 | id: forecastStyleDropdown 43 | 44 | text: i18n("Forecast Style") 45 | currentIndex: indexOfValue(settingsModel.forecastStyle) 46 | textRole: "name" 47 | valueRole: "value" 48 | onActivated: settingsModel.save() 49 | onCurrentValueChanged: settingsModel.forecastStyle = currentValue 50 | onClicked: { 51 | if (root.dialog && forecastStyleDropdown.mode === FormCard.FormComboBoxDelegate.Dialog) { 52 | dialogTimer.dialog = forecastStyleDropdown.dialog; 53 | dialogTimer.restart(); 54 | } 55 | } 56 | 57 | Connections { 58 | function onClosed() { 59 | if (root.dialog) 60 | root.dialog.open(); 61 | 62 | } 63 | 64 | target: forecastStyleDropdown.dialog 65 | } 66 | 67 | model: ListModel { 68 | // we can't use i18n with ListElement 69 | Component.onCompleted: { 70 | append({ 71 | "name": i18n("Flat"), 72 | "value": "Flat" 73 | }); 74 | append({ 75 | "name": i18n("Dynamic"), 76 | "value": "Dynamic" 77 | }); 78 | // indexOfValue doesn't bind to model changes unfortunately, set currentIndex manually here 79 | forecastStyleDropdown.currentIndex = forecastStyleDropdown.indexOfValue(settingsModel.forecastStyle); 80 | } 81 | } 82 | 83 | } 84 | 85 | FormCard.FormDelegateSeparator { 86 | above: forecastStyleDropdown 87 | below: aboutButton 88 | } 89 | 90 | FormCard.FormButtonDelegate { 91 | id: aboutButton 92 | 93 | text: i18n("About") 94 | onClicked: { 95 | applicationWindow().pageStack.push(Qt.createComponent("org.kde.kirigamiaddons.formcard", "AboutPage")); 96 | if (root.dialog) 97 | root.dialog.close(); 98 | 99 | } 100 | } 101 | 102 | } 103 | 104 | FormCard.FormHeader { 105 | title: i18n("Units") 106 | } 107 | 108 | FormCard.FormCard { 109 | TemperatureUnitComboBox { 110 | id: temperatureUnitsDropdown 111 | 112 | onClicked: { 113 | if (root.dialog && temperatureUnitsDropdown.mode === FormCard.FormComboBoxDelegate.Dialog) { 114 | dialogTimer.dialog = temperatureUnitsDropdown.dialog; 115 | dialogTimer.restart(); 116 | } 117 | } 118 | 119 | Connections { 120 | function onClosed() { 121 | if (root.dialog) 122 | root.dialog.open(); 123 | 124 | } 125 | 126 | target: temperatureUnitsDropdown.dialog 127 | } 128 | 129 | } 130 | 131 | FormCard.FormDelegateSeparator { 132 | above: temperatureUnitsDropdown 133 | below: speedUnitsDropdown 134 | } 135 | 136 | SpeedUnitComboBox { 137 | id: speedUnitsDropdown 138 | 139 | onClicked: { 140 | if (root.dialog && speedUnitsDropdown.mode === FormCard.FormComboBoxDelegate.Dialog) { 141 | dialogTimer.dialog = speedUnitsDropdown.dialog; 142 | dialogTimer.restart(); 143 | } 144 | } 145 | 146 | Connections { 147 | function onClosed() { 148 | if (root.dialog) 149 | root.dialog.open(); 150 | 151 | } 152 | 153 | target: speedUnitsDropdown.dialog 154 | } 155 | 156 | } 157 | 158 | FormCard.FormDelegateSeparator { 159 | above: speedUnitsDropdown 160 | below: pressureUnitsDropdown 161 | } 162 | 163 | PressureUnitComboBox { 164 | id: pressureUnitsDropdown 165 | 166 | onClicked: { 167 | if (root.dialog && pressureUnitsDropdown.mode === FormCard.FormComboBoxDelegate.Dialog) { 168 | dialogTimer.dialog = pressureUnitsDropdown.dialog; 169 | dialogTimer.restart(); 170 | } 171 | } 172 | 173 | Connections { 174 | function onClosed() { 175 | if (root.dialog) 176 | root.dialog.open(); 177 | 178 | } 179 | 180 | target: pressureUnitsDropdown.dialog 181 | } 182 | 183 | } 184 | 185 | FormCard.FormDelegateSeparator { 186 | above: pressureUnitsDropdown 187 | below: precipitationUnitsDropdown 188 | } 189 | 190 | // Precipitation 191 | PrecipitationUnitComboBox { 192 | id: precipitationUnitsDropdown 193 | 194 | onClicked: { 195 | if (root.dialog && precipitationUnitsDropdown.mode === FormCard.FormComboBoxDelegate.Dialog) { 196 | dialogTimer.dialog = precipitationUnitsDropdown.dialog; 197 | dialogTimer.restart(); 198 | } 199 | } 200 | 201 | Connections { 202 | function onClosed() { 203 | if (root.dialog) 204 | root.dialog.open(); 205 | 206 | } 207 | 208 | target: precipitationUnitsDropdown.dialog 209 | } 210 | 211 | } 212 | 213 | } 214 | 215 | } 216 | -------------------------------------------------------------------------------- /src/qml/settings/SettingsDialog.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022 Devin Lin 2 | // SPDX-License-Identifier: GPL-2.0-or-later 3 | 4 | import QtQuick 5 | import QtQuick.Controls as Controls 6 | import QtQuick.Layouts 7 | 8 | import org.kde.kirigami as Kirigami 9 | 10 | Kirigami.Dialog { 11 | id: root 12 | title: i18n("Settings") 13 | standardButtons: Kirigami.Dialog.NoButton 14 | 15 | preferredWidth: Kirigami.Units.gridUnit * 35 16 | 17 | Kirigami.Theme.inherit: false 18 | Kirigami.Theme.colorSet: Kirigami.Theme.Window 19 | 20 | Controls.Control { 21 | id: control 22 | leftPadding: 0 23 | rightPadding: 0 24 | topPadding: Kirigami.Units.gridUnit 25 | bottomPadding: Kirigami.Units.gridUnit 26 | 27 | background: Rectangle { 28 | Kirigami.Theme.inherit: false 29 | Kirigami.Theme.colorSet: Kirigami.Theme.Window 30 | color: Kirigami.Theme.backgroundColor 31 | } 32 | 33 | contentItem: SettingsComponent { 34 | dialog: root 35 | width: control.width 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/qml/settings/SettingsPage.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Han Young 3 | * SPDX-FileCopyrightText: 2020-2021 Devin Lin 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | */ 7 | 8 | import QtQuick 9 | import QtQuick.Controls 10 | import QtQuick.Layouts 11 | import org.kde.kirigami as Kirigami 12 | import org.kde.kirigamiaddons.formcard as FormCard 13 | 14 | FormCard.FormCardPage { 15 | id: root 16 | title: i18n("Settings") 17 | 18 | SettingsComponent {} 19 | } 20 | -------------------------------------------------------------------------------- /src/qml/settings/SettingsWindow.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022 Devin Lin 2 | // SPDX-License-Identifier: GPL-2.0-or-later 3 | 4 | import QtQuick 5 | import QtQuick.Controls as Controls 6 | import QtQuick.Layouts 7 | 8 | import org.kde.kirigami as Kirigami 9 | 10 | // A settings window is used on desktop when the app is widescreen. 11 | Kirigami.ApplicationWindow { 12 | id: root 13 | title: i18n("Settings") 14 | flags: Qt.WindowStaysOnTopHint 15 | 16 | height: Kirigami.Units.gridUnit * 26 17 | width: Kirigami.Units.gridUnit * 34 18 | 19 | Kirigami.Theme.inherit: false 20 | Kirigami.Theme.colorSet: Kirigami.Theme.Window 21 | 22 | pageStack.globalToolBar.style: Kirigami.ApplicationHeaderStyle.ToolBar 23 | pageStack.globalToolBar.showNavigationButtons: Kirigami.ApplicationHeaderStyle.ShowBackButton 24 | pageStack.columnView.columnResizeMode: Kirigami.ColumnView.SingleColumn 25 | pageStack.popHiddenPages: true 26 | 27 | pageStack.initialPage: Kirigami.ScrollablePage { 28 | topPadding: 0 29 | leftPadding: 0 30 | rightPadding: 0 31 | 32 | globalToolBarStyle: Kirigami.ApplicationHeaderStyle.None 33 | 34 | ColumnLayout { 35 | Kirigami.Separator { 36 | Layout.fillWidth: true 37 | } 38 | 39 | SettingsComponent { 40 | Layout.topMargin: Kirigami.Units.gridUnit 41 | Layout.fillWidth: true 42 | onCloseRequested: dialog.close() 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/qml/settings/SpeedUnitComboBox.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020-2024 Devin Lin 2 | // SPDX-License-Identifier: GPL-2.0-or-later 3 | 4 | import QtQuick 5 | import QtQuick.Controls 6 | import org.kde.kirigami as Kirigami 7 | import org.kde.kirigamiaddons.formcard as FormCard 8 | 9 | FormCard.FormComboBoxDelegate { 10 | id: root 11 | 12 | text: i18n("Speed Units") 13 | currentIndex: indexOfValue(settingsModel.speedUnits) 14 | textRole: "name" 15 | valueRole: "value" 16 | onActivated: settingsModel.save() 17 | onCurrentValueChanged: settingsModel.speedUnits = currentValue 18 | 19 | model: ListModel { 20 | // we can't use i18n with ListElement 21 | Component.onCompleted: { 22 | append({ 23 | "name": i18nc("kilometers per hour", "kph"), 24 | "value": "kph" 25 | }); 26 | append({ 27 | "name": i18nc("miles per hour", "mph"), 28 | "value": "mph" 29 | }); 30 | append({ 31 | "name": i18nc("meters per second", "m/s"), 32 | "value": "m/s" 33 | }); 34 | // indexOfValue doesn't bind to model changes unfortunately, set currentIndex manually here 35 | root.currentIndex = root.indexOfValue(settingsModel.speedUnits); 36 | } 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/qml/settings/TemperatureUnitComboBox.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020-2024 Devin Lin 2 | // SPDX-License-Identifier: GPL-2.0-or-later 3 | 4 | import QtQuick 5 | import QtQuick.Controls 6 | import org.kde.kirigami as Kirigami 7 | import org.kde.kirigamiaddons.formcard as FormCard 8 | 9 | FormCard.FormComboBoxDelegate { 10 | id: root 11 | 12 | text: i18n("Temperature Units") 13 | currentIndex: indexOfValue(settingsModel.temperatureUnits) 14 | textRole: "name" 15 | valueRole: "value" 16 | onActivated: settingsModel.save() 17 | onCurrentValueChanged: settingsModel.temperatureUnits = currentValue 18 | 19 | model: ListModel { 20 | // we can't use i18n with ListElement 21 | Component.onCompleted: { 22 | append({ 23 | "name": i18n("Use System Default"), 24 | "value": "Use System Default" 25 | }); 26 | append({ 27 | "name": i18n("Celsius"), 28 | "value": "Celsius" 29 | }); 30 | append({ 31 | "name": i18n("Fahrenheit"), 32 | "value": "Fahrenheit" 33 | }); 34 | // indexOfValue doesn't bind to model changes unfortunately, set currentIndex manually here 35 | root.currentIndex = root.indexOfValue(settingsModel.temperatureUnits); 36 | } 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/resources/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # SPDX-FileCopyrightText: 2023 Thiago Sueto 3 | # 4 | # SPDX-License-Identifier: GPL-2.0-or-later 5 | # 6 | 7 | qt_add_resources(kweather "kweatheresources" 8 | PREFIX "/resources" 9 | FILES 10 | "kweather.svg" 11 | "NotoSans-Light.ttf" 12 | ) 13 | -------------------------------------------------------------------------------- /src/resources/NotoSans-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/kweather/74af6b044b0c03850c05fd12b8a8229fa62adcce/src/resources/NotoSans-Light.ttf -------------------------------------------------------------------------------- /src/resources/NotoSans-Light.ttf.license: -------------------------------------------------------------------------------- 1 | Copyright 2020 Google Inc. 2 | SPDX-License-Identifier: Apache-2.0 3 | -------------------------------------------------------------------------------- /src/resources/kweather.svg.license: -------------------------------------------------------------------------------- 1 | Copyright 2020 Devin Lin 2 | SPDX-License-Identifier: CC-BY-4.0 3 | -------------------------------------------------------------------------------- /src/temperaturechartdata.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Han Young 3 | * SPDX-FileCopyrightText: 2020 Devin Lin 4 | * SPDX-FileCopyrightText: 2021 Nicolas Fella 5 | * 6 | * SPDX-License-Identifier: GPL-2.0-or-later 7 | */ 8 | 9 | #include "temperaturechartdata.h" 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include "global.h" 17 | 18 | QVariantList TemperatureChartData::weatherData() const 19 | { 20 | return m_weatherData; 21 | } 22 | 23 | void TemperatureChartData::setWeatherData(const QVariantList &weatherData) 24 | { 25 | m_weatherData = weatherData; 26 | Q_EMIT weatherDataChanged(); 27 | 28 | updateData(); 29 | } 30 | 31 | void TemperatureChartData::updateData() 32 | { 33 | if (!m_series) { 34 | return; 35 | } 36 | 37 | m_series->clear(); 38 | 39 | QList result; 40 | result.reserve(m_weatherData.size()); 41 | 42 | double minTemp = std::numeric_limits::max(); 43 | double maxTemp = std::numeric_limits::min(); 44 | 45 | for (const QVariant &dayVariant : std::as_const(m_weatherData)) { 46 | const auto day = dayVariant.value(); 47 | 48 | const double dayMinTemp = KWeather::convertTemp(day.minTemp(), KWeatherSettings::self()->temperatureUnits()); 49 | const double dayMaxTemp = KWeather::convertTemp(day.maxTemp(), KWeatherSettings::self()->temperatureUnits()); 50 | 51 | result.append(QPointF(day.date().startOfDay().toMSecsSinceEpoch(), dayMaxTemp)); 52 | minTemp = std::min(dayMinTemp, minTemp); 53 | maxTemp = std::max(dayMaxTemp, maxTemp); 54 | } 55 | 56 | // make enough room for the curve 57 | m_maxTempLimit = maxTemp + 5; 58 | m_minTempLimit = minTemp - 5; 59 | 60 | Q_EMIT maxTempLimitChanged(); 61 | Q_EMIT minTempLimitChanged(); 62 | 63 | m_series->replace(result); 64 | 65 | if (!m_weatherData.isEmpty()) { 66 | m_axisX->setRange(m_weatherData.first().value().date().startOfDay(), 67 | m_weatherData.last().value().date().startOfDay()); 68 | } 69 | } 70 | 71 | void TemperatureChartData::initSeries(QAbstractSeries *series) 72 | { 73 | m_series = static_cast(series); 74 | updateData(); 75 | } 76 | 77 | void TemperatureChartData::initAxes(QObject *axisX, QObject *axisY) 78 | { 79 | m_axisX = static_cast(axisX); 80 | m_axisY = static_cast(axisY); 81 | } 82 | 83 | double TemperatureChartData::maxTempLimit() const 84 | { 85 | return m_maxTempLimit; 86 | } 87 | double TemperatureChartData::minTempLimit() const 88 | { 89 | return m_minTempLimit; 90 | } 91 | 92 | #include "moc_temperaturechartdata.cpp" 93 | -------------------------------------------------------------------------------- /src/temperaturechartdata.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Han Young 3 | * SPDX-FileCopyrightText: 2020 Devin Lin 4 | * SPDX-FileCopyrightText: 2021 Nicolas Fella 5 | * 6 | * SPDX-License-Identifier: GPL-2.0-or-later 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | 13 | #include 14 | #include 15 | 16 | class QAbstractSeries; 17 | class QSplineSeries; 18 | class QDateTimeAxis; 19 | class QValueAxis; 20 | 21 | class TemperatureChartData : public QObject 22 | { 23 | Q_OBJECT 24 | QML_ELEMENT 25 | Q_PROPERTY(QVariantList weatherData READ weatherData WRITE setWeatherData NOTIFY weatherDataChanged) 26 | Q_PROPERTY(double maxTempLimit READ maxTempLimit NOTIFY maxTempLimitChanged) 27 | Q_PROPERTY(double minTempLimit READ minTempLimit NOTIFY minTempLimitChanged) 28 | 29 | public: 30 | QVariantList weatherData() const; 31 | void setWeatherData(const QVariantList &weatherData); 32 | 33 | double maxTempLimit() const; 34 | double minTempLimit() const; 35 | 36 | Q_INVOKABLE void initSeries(QAbstractSeries *series); 37 | Q_INVOKABLE void initAxes(QObject *axisX, QObject *axisY); 38 | 39 | Q_SIGNALS: 40 | void weatherDataChanged(); 41 | void maxTempLimitChanged(); 42 | void minTempLimitChanged(); 43 | 44 | private: 45 | void updateData(); 46 | 47 | QVariantList m_weatherData; 48 | double m_maxTempLimit = 100; 49 | double m_minTempLimit = 0; 50 | 51 | QSplineSeries *m_series = nullptr; 52 | QDateTimeAxis *m_axisX = nullptr; 53 | QValueAxis *m_axisY = nullptr; 54 | }; 55 | -------------------------------------------------------------------------------- /src/weatherbackground.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2022 Han Young 3 | * 4 | * SPDX-License-Identifier: GPL-2.0-or-later 5 | */ 6 | #pragma once 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | class BackgroundRenderer; 13 | 14 | class WeatherBackgroundRenderer : public QQuickFramebufferObject 15 | { 16 | Q_OBJECT 17 | QML_NAMED_ELEMENT(WeatherBackground) 18 | Q_PROPERTY(bool rain READ rain WRITE setRain NOTIFY rainChanged) 19 | Q_PROPERTY(bool cloud READ cloud WRITE setCloud NOTIFY cloudChanged) 20 | Q_PROPERTY(bool sun READ sun WRITE setSun NOTIFY sunChanged) 21 | Q_PROPERTY(bool star READ star WRITE setStar NOTIFY starChanged) 22 | Q_PROPERTY(bool snow READ snow WRITE setSnow NOTIFY snowChanged) 23 | Q_PROPERTY(QColor colorTop READ colorTop WRITE setColorTop NOTIFY colorTopChanged) 24 | Q_PROPERTY(QColor colorBottom READ colorBottom WRITE setColorBottom NOTIFY colorBottomChanged) 25 | Q_PROPERTY(QColor cloudColor READ cloudColor WRITE setCloudColor NOTIFY cloudColorChanged) 26 | public: 27 | explicit WeatherBackgroundRenderer(QQuickItem *parent = nullptr); 28 | Renderer *createRenderer() const override; 29 | bool rain() const; 30 | bool cloud() const; 31 | bool sun() const; 32 | bool star() const; 33 | bool snow() const; 34 | QColor colorTop() const; 35 | QColor colorBottom() const; 36 | QColor cloudColor() const; 37 | 38 | void setRain(bool); 39 | void setCloud(bool); 40 | void setSun(bool); 41 | void setStar(bool); 42 | void setSnow(bool); 43 | void setColorTop(const QColor &); 44 | void setColorBottom(const QColor &); 45 | void setCloudColor(const QColor &); 46 | 47 | Q_SIGNALS: 48 | void rainChanged(); 49 | void cloudChanged(); 50 | void sunChanged(); 51 | void starChanged(); 52 | void snowChanged(); 53 | void colorTopChanged(); 54 | void colorBottomChanged(); 55 | void cloudColorChanged(); 56 | 57 | private Q_SLOTS: 58 | void handleTimeout(); 59 | 60 | protected: 61 | friend class WeatherBackgroundContentRenderer; 62 | int minFPS = 60; 63 | int modifiedMinFPS = 60; 64 | 65 | private: 66 | QTimer *m_timer = nullptr; 67 | bool m_rain = false; 68 | bool m_cloud = false; 69 | bool m_sun = false; 70 | bool m_star = false; 71 | bool m_snow = false; 72 | 73 | QColor m_colourTop; 74 | QColor m_colourBottom; 75 | QColor m_cloudColor; 76 | }; 77 | 78 | class CloudsRenderer; 79 | class RainRenderer; 80 | class SunRenderer; 81 | class StarsRendererBase; 82 | class SnowRendererBase; 83 | class WeatherBackgroundContentRenderer : public QQuickFramebufferObject::Renderer, QOpenGLFunctions 84 | { 85 | public: 86 | void render() override; 87 | void synchronize(QQuickFramebufferObject *item) override; 88 | QOpenGLFramebufferObject *createFramebufferObject(const QSize &size) override; 89 | 90 | protected: 91 | friend class WeatherBackgroundRenderer; 92 | WeatherBackgroundContentRenderer(); 93 | WeatherBackgroundContentRenderer(const WeatherBackgroundContentRenderer &other) = delete; 94 | WeatherBackgroundContentRenderer(WeatherBackgroundContentRenderer &&other) = delete; 95 | WeatherBackgroundContentRenderer operator=(const WeatherBackgroundContentRenderer &other) = delete; 96 | 97 | private: 98 | CloudsRenderer *m_clouds = nullptr; 99 | RainRenderer *m_rain = nullptr; 100 | std::function dice; 101 | BackgroundRenderer *background; 102 | SunRenderer *m_sun = nullptr; 103 | StarsRendererBase *m_stars = nullptr; 104 | SnowRendererBase *m_snow = nullptr; 105 | 106 | float aspectRatio = 1; 107 | int m_minFPS = 60; 108 | bool synchronized = false; 109 | 110 | bool m_showRain = false; 111 | bool m_showCloud = false; 112 | bool m_showSun = false; 113 | bool m_showStar = false; 114 | bool m_showSnow = false; 115 | QColor m_colourTop; 116 | QColor m_colourBottom; 117 | QColor m_cloudColor; 118 | bool m_legacyMode = false; 119 | }; 120 | -------------------------------------------------------------------------------- /src/weatherlocation.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Han Young 3 | * SPDX-FileCopyrightText: 2020 Devin Lin 4 | * SPDX-FileCopyrightText: 2021 Nicolas Fella 5 | * 6 | * SPDX-License-Identifier: GPL-2.0-or-later 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include 24 | 25 | #include 26 | 27 | class WeatherLocation : public QObject 28 | { 29 | Q_OBJECT 30 | QML_ELEMENT 31 | QML_UNCREATABLE("") 32 | Q_PROPERTY(QString name READ locationName CONSTANT) 33 | Q_PROPERTY(float latitude READ latitude CONSTANT) 34 | Q_PROPERTY(float longitude READ longitude CONSTANT) 35 | Q_PROPERTY(QString lastUpdated READ lastUpdatedFormatted NOTIFY lastUpdatedChanged) 36 | Q_PROPERTY(QString currentTime READ currentTimeFormatted NOTIFY currentTimeChanged) 37 | Q_PROPERTY(QString currentDate READ currentDateFormatted NOTIFY currentDateChanged) 38 | Q_PROPERTY(QString timeZone READ timeZone NOTIFY timeZoneChanged) 39 | Q_PROPERTY(QVariant currentHourForecast READ currentHourForecast NOTIFY currentForecastChanged) 40 | 41 | Q_PROPERTY(QList dayForecasts READ dayForecasts NOTIFY dayForecastsChanged) 42 | Q_PROPERTY(QList hourForecasts READ hourForecasts NOTIFY hourForecastsChanged) 43 | Q_PROPERTY(int selectedDay READ selectedDay WRITE setSelectedDay NOTIFY selectedDayChanged) 44 | 45 | Q_PROPERTY(QString backgroundComponent READ backgroundComponent NOTIFY currentForecastChanged) 46 | Q_PROPERTY(QColor backgroundColor READ backgroundColor NOTIFY currentForecastChanged) 47 | Q_PROPERTY(QColor textColor READ textColor NOTIFY currentForecastChanged) 48 | Q_PROPERTY(QColor cardBackgroundColor READ cardBackgroundColor NOTIFY currentForecastChanged) 49 | Q_PROPERTY(QColor cardTextColor READ cardTextColor NOTIFY currentForecastChanged) 50 | Q_PROPERTY(QColor cardSecondaryTextColor READ cardSecondaryTextColor NOTIFY currentForecastChanged) 51 | Q_PROPERTY(QColor iconColor READ iconColor NOTIFY currentForecastChanged) 52 | Q_PROPERTY(bool darkTheme READ darkTheme NOTIFY currentForecastChanged) 53 | 54 | Q_PROPERTY(bool cloud READ cloud NOTIFY currentForecastChanged) 55 | Q_PROPERTY(bool sun READ sun NOTIFY currentForecastChanged) 56 | Q_PROPERTY(bool rain READ rain NOTIFY currentForecastChanged) 57 | Q_PROPERTY(bool star READ star NOTIFY currentForecastChanged) 58 | Q_PROPERTY(bool snow READ snow NOTIFY currentForecastChanged) 59 | Q_PROPERTY(QColor topColor READ topColor NOTIFY currentForecastChanged) 60 | Q_PROPERTY(QColor bottomColor READ bottomColor NOTIFY currentForecastChanged) 61 | Q_PROPERTY(QColor cloudColor READ cloudColor NOTIFY currentForecastChanged) 62 | 63 | public: 64 | explicit WeatherLocation(QString locationId, 65 | QString locationName, 66 | QString timeZone, 67 | float latitude = 0, 68 | float longitude = 0, 69 | KWeatherCore::WeatherForecast forecast = {}); 70 | explicit WeatherLocation(); // when creating empty locations, don't persist 71 | 72 | static WeatherLocation *load(const QString &groupName); 73 | 74 | void save(); 75 | Q_INVOKABLE void update(); 76 | void saveOrder(int index); // for restoring order of locations 77 | int index(); 78 | void deleteConfig(); 79 | 80 | const QString &locationId() const; 81 | const QString &locationName() const; 82 | const QString &timeZone() const; 83 | float latitude() const; 84 | float longitude() const; 85 | QString lastUpdatedFormatted() const; 86 | const QDateTime &lastUpdated() const; 87 | QString currentTimeFormatted() const; 88 | QString currentDateFormatted() const; 89 | QDateTime currentDateTime() const; 90 | const QString &backgroundComponent() const; 91 | const QColor &backgroundColor() const; 92 | const QColor &textColor() const; 93 | const QColor &cardBackgroundColor() const; 94 | const QColor &cardTextColor() const; 95 | const QColor &cardSecondaryTextColor() const; 96 | const QColor &topColor() const; 97 | const QColor &bottomColor() const; 98 | const QColor &cloudColor() const; 99 | bool rain() const; 100 | bool cloud() const; 101 | bool star() const; 102 | bool sun() const; 103 | bool snow() const; 104 | 105 | const QColor &iconColor() const; 106 | bool darkTheme() const; 107 | QList dayForecasts() const; 108 | QList hourForecasts() const; 109 | int selectedDay() const; 110 | void setSelectedDay(int selectedDay); 111 | QVariant currentHourForecast(); 112 | 113 | public Q_SLOTS: 114 | void updateData(KWeatherCore::WeatherForecast forecasts); 115 | 116 | Q_SIGNALS: 117 | void weatherRefresh(KWeatherCore::WeatherForecast forecasts); // sent when weather data is refreshed 118 | void currentForecastChanged(); 119 | void stopLoadingIndicator(); 120 | void currentTimeChanged(); 121 | void currentDateChanged(); 122 | void timeZoneChanged(); 123 | void dayForecastsChanged(); 124 | void hourForecastsChanged(); 125 | void selectedDayChanged(); 126 | void lastUpdatedChanged(); 127 | 128 | void chartListChanged(); 129 | 130 | private Q_SLOTS: 131 | void updateCurrentDateTime(); 132 | 133 | private: 134 | void determineCurrentForecast(); 135 | 136 | KWeatherCore::WeatherForecastSource m_source; 137 | KWeatherCore::WeatherForecast m_forecast; 138 | 139 | // background related fields 140 | QColor m_backgroundColor; 141 | QColor m_textColor; 142 | QColor m_cardBackgroundColor; 143 | QColor m_cardTextColor; 144 | QColor m_cardSecondaryTextColor; 145 | QColor m_iconColor; 146 | QString m_backgroundComponent = QStringLiteral("qrc:/qt/qml/org/kde/kweather/backgrounds/qml/backgrounds/ClearDay.qml"); 147 | bool m_isDarkTheme = false; 148 | bool m_rain = false; 149 | bool m_snow = false; 150 | bool m_sun = false; 151 | bool m_star = false; 152 | bool m_cloud = false; 153 | QColor m_cloudColor; 154 | QColor m_topColor; 155 | QColor m_bottomColor; 156 | 157 | QString m_locationName, m_locationId; 158 | QString m_timeZone; 159 | QDateTime m_lastUpdated; 160 | QTimer *m_timer = nullptr; 161 | float m_latitude, m_longitude; 162 | 163 | QList m_dayForecasts; 164 | QList m_hourForecasts; 165 | int m_selectedDay = 0; 166 | }; 167 | -------------------------------------------------------------------------------- /src/weatherlocationlistmodel.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020 Han Young 2 | // SPDX-FileCopyrightText: 2020-2022 Devin Lin 3 | // SPDX-License-Identifier: GPL-2.0-or-later 4 | 5 | #include "weatherlocationlistmodel.h" 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | #include "global.h" 16 | #include "kweathersettings.h" 17 | #include "weatherlocation.h" 18 | 19 | WeatherLocationListModel::WeatherLocationListModel(QObject *parent) 20 | : QAbstractListModel{parent} 21 | { 22 | load(); 23 | } 24 | 25 | WeatherLocationListModel *WeatherLocationListModel::create(QQmlEngine *, QJSEngine *) 26 | { 27 | QQmlEngine::setObjectOwnership(inst(), QQmlEngine::CppOwnership); 28 | return inst(); 29 | } 30 | 31 | WeatherLocationListModel *WeatherLocationListModel::inst() 32 | { 33 | static WeatherLocationListModel *singleton = new WeatherLocationListModel; 34 | return singleton; 35 | } 36 | 37 | void WeatherLocationListModel::load() 38 | { 39 | beginResetModel(); 40 | 41 | // load locations from kconfig 42 | auto config = KWeatherSettings::self()->config()->group(KWeather::WEATHER_LOCATIONS_CFG_GROUP); 43 | auto locations = config.groupList(); 44 | for (const auto &location : locations) { 45 | auto location_ptr = WeatherLocation::load(location); 46 | if (location_ptr) 47 | m_locations.push_back(location_ptr); 48 | } 49 | 50 | // sort locations by index, correcting any issues with the stored index 51 | 52 | QList sorted, unsorted; 53 | for (int i = 0; i < (int)m_locations.size(); ++i) { 54 | sorted.push_back(nullptr); 55 | } 56 | 57 | // loop through the initial locations and fill in the indicies in sorted 58 | for (auto loc : m_locations) { 59 | auto index = loc->index(); 60 | 61 | if (index < 0 || index >= (int)sorted.size() || sorted[index] != nullptr) { 62 | unsorted.push_back(loc); 63 | } else { 64 | sorted[index] = loc; 65 | } 66 | } 67 | // add unsorted locations in positions unfilled 68 | for (auto loc : unsorted) { 69 | for (int i = 0; i < (int)sorted.size(); ++i) { 70 | if (!sorted[i]) { 71 | sorted[i] = loc; 72 | break; 73 | } 74 | } 75 | } 76 | // move into original array 77 | for (int i = 0; i < (int)m_locations.size(); ++i) { 78 | m_locations[i] = sorted[i]; 79 | } 80 | 81 | Q_EMIT locationsChanged(); 82 | endResetModel(); 83 | } 84 | 85 | void WeatherLocationListModel::saveOrder() 86 | { 87 | auto i{0}; 88 | for (auto loc : m_locations) { 89 | loc->saveOrder(i); 90 | i++; 91 | } 92 | } 93 | 94 | int WeatherLocationListModel::count() const 95 | { 96 | return m_locations.size(); 97 | } 98 | 99 | int WeatherLocationListModel::rowCount(const QModelIndex &parent) const 100 | { 101 | if (parent.isValid()) { 102 | return 0; 103 | } 104 | return m_locations.size(); 105 | } 106 | 107 | QVariant WeatherLocationListModel::data(const QModelIndex &index, int role) const 108 | { 109 | if (!checkIndex(index)) { 110 | return QVariant(); 111 | } 112 | if (role != LocationRole) { 113 | return QVariant(); 114 | } 115 | 116 | auto *location = m_locations[index.row()]; 117 | return location ? QVariant::fromValue(location) : QVariant(); 118 | } 119 | 120 | QHash WeatherLocationListModel::roleNames() const 121 | { 122 | return {{LocationRole, "location"}}; 123 | } 124 | 125 | void WeatherLocationListModel::insert(int index, WeatherLocation *weatherLocation) 126 | { 127 | if ((index < 0) || (index > static_cast(m_locations.size()))) { 128 | return; 129 | } 130 | 131 | beginInsertRows(QModelIndex(), index, index); 132 | 133 | QQmlEngine::setObjectOwnership(weatherLocation, QQmlEngine::CppOwnership); 134 | m_locations.insert(m_locations.begin() + index, weatherLocation); 135 | 136 | Q_EMIT locationsChanged(); 137 | endInsertRows(); 138 | 139 | saveOrder(); 140 | weatherLocation->save(); 141 | } 142 | 143 | void WeatherLocationListModel::remove(int index) 144 | { 145 | if ((index < 0) || (index >= static_cast(m_locations.size()))) { 146 | return; 147 | } 148 | 149 | beginRemoveRows(QModelIndex(), index, index); 150 | 151 | auto location = m_locations.at(index); 152 | m_locations.erase(m_locations.begin() + index); 153 | location->deleteConfig(); 154 | location->deleteLater(); 155 | 156 | Q_EMIT locationsChanged(); 157 | endRemoveRows(); 158 | 159 | saveOrder(); 160 | } 161 | 162 | void WeatherLocationListModel::move(int oldIndex, int newIndex) 163 | { 164 | int locationsSize = m_locations.size(); 165 | if (oldIndex < 0 || oldIndex >= locationsSize || newIndex < 0 || newIndex >= locationsSize) { 166 | return; 167 | } 168 | if (newIndex > oldIndex) { 169 | ++newIndex; 170 | } 171 | 172 | beginMoveRows(QModelIndex(), oldIndex, oldIndex, QModelIndex(), newIndex); 173 | if (newIndex > oldIndex) { 174 | auto *location = m_locations.at(oldIndex); 175 | m_locations.insert(newIndex, location); 176 | m_locations.takeAt(oldIndex); 177 | } else { 178 | auto *location = m_locations.takeAt(oldIndex); 179 | m_locations.insert(newIndex, location); 180 | } 181 | endMoveRows(); 182 | Q_EMIT locationsChanged(); 183 | 184 | saveOrder(); 185 | } 186 | 187 | void WeatherLocationListModel::addLocation(const KWeatherCore::LocationQueryResult &ret) 188 | { 189 | const auto &locId = ret.geonameId(); 190 | const auto &locName = ret.toponymName(); 191 | auto lat = ret.latitude(); 192 | auto lon = ret.longitude(); 193 | 194 | // add location 195 | auto *location = new WeatherLocation(locId, locName, QString(), lat, lon); 196 | 197 | insert(m_locations.size(), location); 198 | } 199 | 200 | void WeatherLocationListModel::requestCurrentLocation() 201 | { 202 | static KWeatherCore::LocationQuery *geoPtr = nullptr; 203 | if (!geoPtr) { 204 | geoPtr = new KWeatherCore::LocationQuery(this); 205 | } 206 | 207 | auto reply = geoPtr->locate(); 208 | connect(reply, &KWeatherCore::LocationQueryReply::finished, this, [reply, this]() { 209 | reply->deleteLater(); 210 | if (reply->error() != KWeatherCore::LocationQueryReply::NoError) { 211 | Q_EMIT networkErrorCreatingDefault(); 212 | } else { 213 | addCurrentLocation(reply->result().at(0)); 214 | } 215 | }); 216 | } 217 | 218 | void WeatherLocationListModel::addCurrentLocation(const KWeatherCore::LocationQueryResult &ret) 219 | { 220 | auto location = new WeatherLocation(ret.geonameId(), ret.toponymName(), QString(), ret.latitude(), ret.longitude()); 221 | 222 | insert(0, location); 223 | Q_EMIT successfullyCreatedDefault(); 224 | } 225 | 226 | QList &WeatherLocationListModel::locations() 227 | { 228 | return m_locations; 229 | } 230 | 231 | #include "moc_weatherlocationlistmodel.cpp" 232 | -------------------------------------------------------------------------------- /src/weatherlocationlistmodel.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020 Han Young 2 | // SPDX-FileCopyrightText: 2020-2022 Devin Lin 3 | // SPDX-License-Identifier: GPL-2.0-or-later 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | #include "weatherlocation.h" 14 | 15 | class WeatherLocationListModel : public QAbstractListModel 16 | { 17 | Q_OBJECT 18 | QML_ELEMENT 19 | QML_SINGLETON 20 | Q_PROPERTY(int count READ count NOTIFY locationsChanged) 21 | Q_PROPERTY(QList locations READ locations NOTIFY locationsChanged) 22 | 23 | public: 24 | enum Roles { 25 | LocationRole = Qt::UserRole 26 | }; 27 | 28 | static WeatherLocationListModel *inst(); 29 | static WeatherLocationListModel *create(QQmlEngine *, QJSEngine *); 30 | 31 | void load(); 32 | void saveOrder(); 33 | 34 | int count() const; 35 | int rowCount(const QModelIndex &parent) const override; 36 | QVariant data(const QModelIndex &index, int role) const override; 37 | QHash roleNames() const override; 38 | 39 | Q_INVOKABLE void insert(int index, WeatherLocation *weatherLocation); 40 | Q_INVOKABLE void remove(int index); 41 | Q_INVOKABLE void move(int oldIndex, int newIndex); 42 | Q_INVOKABLE void requestCurrentLocation(); // invoked by UI 43 | 44 | QList &locations(); 45 | 46 | public Q_SLOTS: 47 | void addLocation(const KWeatherCore::LocationQueryResult &ret); 48 | 49 | Q_SIGNALS: 50 | void locationsChanged(); 51 | void networkErrorCreating(); // error creating a location 52 | void networkErrorCreatingDefault(); // error getting current location 53 | void successfullyCreatedDefault(); // successful in getting current location 54 | 55 | protected: 56 | explicit WeatherLocationListModel(QObject *parent = nullptr); 57 | 58 | private Q_SLOTS: 59 | void addCurrentLocation(const KWeatherCore::LocationQueryResult &ret); 60 | 61 | private: 62 | QList m_locations; 63 | }; 64 | --------------------------------------------------------------------------------