diff --git a/README.md b/README.md index 3493b39..d7a5ccb 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,22 @@

Weather

-

Beautiful lightweight weather app.

+ +

Beautiful lightweight weather app based on Python and Gtk

+ +
+

Support

+I hope you ❤️ the weather app, if you think it is worth supporting you can do so using below methods + +Buy Me A Coffee + +
+
+Donate to our Open Collective +
+ +--- +
diff --git a/data/icons/hicolor/scalable/weather_icons/wind.svg b/data/icons/hicolor/scalable/weather_icons/wind.svg new file mode 100644 index 0000000..56d7854 --- /dev/null +++ b/data/icons/hicolor/scalable/weather_icons/wind.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/data/icons/meson.build b/data/icons/meson.build index 6e6304f..cb2228e 100644 --- a/data/icons/meson.build +++ b/data/icons/meson.build @@ -32,6 +32,7 @@ install_data( join_paths(scalable_dir, 'partly-cloudy-day-fog.svg'), join_paths(scalable_dir, 'partly-cloudy-night-fog.svg'), join_paths(scalable_dir, 'rain.svg'), + join_paths(scalable_dir, 'wind.svg'), join_paths(scalable_dir, 'raindrop.svg'), join_paths(scalable_dir, 'raindrops.svg'), join_paths(scalable_dir, 'overcast-rain.svg'), diff --git a/src/Models.py b/src/Models.py index 4709fb4..69438ff 100644 --- a/src/Models.py +++ b/src/Models.py @@ -1,6 +1,4 @@ # Models for All weather data data -import time - class CurrentWeather: total_instances = 0 diff --git a/src/__init__.py b/src/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/backendAirPollution.py b/src/backendAirPollution.py index 264d6f6..03c08d5 100644 --- a/src/backendAirPollution.py +++ b/src/backendAirPollution.py @@ -21,7 +21,7 @@ def current_air_pollution(latitude: float, longitude: float, **kwargs): except requests.exceptions.RequestException as e: print(f"Error: {e}") - def _get_current_air_pollution(self,lat,lon): + def _get_current_air_pollution(self, lat, lon): current_args = [ "european_aqi", "us_aqi", diff --git a/src/backendWeather.py b/src/backendWeather.py index 7759ecb..5939247 100644 --- a/src/backendWeather.py +++ b/src/backendWeather.py @@ -1,40 +1,40 @@ -from typing import List import requests import datetime from gi.repository import Gio - base_url = "https://api.open-meteo.com/v1/forecast" -class Weather(): + +class Weather: """ See Documentation at: https://open-meteo.com/en/docs """ + def __init__(self) -> None: - global settings,measurement_type,temperature_unit,wind_speed_unit + global settings, measurement_type, temperature_unit, wind_speed_unit settings = Gio.Settings(schema_id="io.github.amit9838.weather") - measurement_type = settings.get_string('measure-type') + measurement_type = settings.get_string("measure-type") if measurement_type == "imperial": temperature_unit = "fahrenheit" wind_speed_unit = "mph" + # Current Weather ============================================= @staticmethod def current_weather(latitude: float, longitude: float, **kwargs): - url = base_url + f"?latitude={latitude}&longitude={longitude}" # Check for kwargs keyword parameters - if 'current' in kwargs: - current_fields = ",".join(kwargs.get('current')) - url = url+f"¤t={current_fields}" - + if "current" in kwargs: + current_fields = ",".join(kwargs.get("current")) + url = url + f"¤t={current_fields}" + if measurement_type == "imperial": url += f"&temperature_unit={temperature_unit}&wind_speed_unit={wind_speed_unit}" try: - url = url+f"&timeformat=unixtime" + url = url + f"&timeformat=unixtime" response = requests.get(url) response.raise_for_status() # Raise an exception if the request was unsuccessful data = response.json() @@ -42,7 +42,7 @@ def current_weather(latitude: float, longitude: float, **kwargs): except requests.exceptions.RequestException as e: print(f"Error: {e}") - def _get_current_weather(self,lat,lon): + def _get_current_weather(self, lat, lon): current_args = [ "temperature_2m", "relativehumidity_2m", @@ -53,27 +53,25 @@ def _get_current_weather(self,lat,lon): "weathercode", "surface_pressure", "windspeed_10m", - "winddirection_10m" + "winddirection_10m", ] - return self.current_weather(lat,lon,current = current_args) - + return self.current_weather(lat, lon, current=current_args) # Hourly Forecast ============================================== @staticmethod def forecast_hourly(latitude: float, longitude: float, **kwargs): - url = base_url + f"?latitude={latitude}&longitude={longitude}" # Check for kwargs keyword parameters - if 'hourly' in kwargs: - hourly_fields = ",".join(kwargs.get('hourly')) - url = url+f"&hourly={hourly_fields}" - + if "hourly" in kwargs: + hourly_fields = ",".join(kwargs.get("hourly")) + url = url + f"&hourly={hourly_fields}" + if measurement_type == "imperial": url += f"&temperature_unit={temperature_unit}&wind_speed_unit={wind_speed_unit}" try: - url = url+f"&timeformat=unixtime" + url = url + f"&timeformat=unixtime" response = requests.get(url) response.raise_for_status() # Raise an exception if the request was unsuccessful data = response.json() @@ -81,49 +79,50 @@ def forecast_hourly(latitude: float, longitude: float, **kwargs): except requests.exceptions.RequestException as e: print(f"Error: {e}") - - def _get_hourly_forecast(self,lat,lon): + def _get_hourly_forecast(self, lat, lon): hourly_args = [ - 'temperature_2m', - 'relativehumidity_2m', - 'dewpoint_2m', - 'apparent_temperature', - 'weathercode', - 'precipitation', - 'surface_pressure', - 'visibility', - 'windspeed_10m', - 'wind_direction_10m', - 'uv_index', - 'is_day' + "temperature_2m", + "relativehumidity_2m", + "dewpoint_2m", + "apparent_temperature", + "weathercode", + "precipitation", + "precipitation_probability", + "surface_pressure", + "visibility", + "windspeed_10m", + "wind_direction_10m", + "uv_index", + "is_day", ] today = datetime.datetime.today().date() - tomorrow = today+datetime.timedelta(days=1) - return self.forecast_hourly(lat, lon,hourly=hourly_args,start_date=today,end_date=tomorrow) - + tomorrow = today + datetime.timedelta(days=1) + return self.forecast_hourly( + lat, lon, hourly=hourly_args, start_date=today, end_date=tomorrow + ) # Forecast daily ==================================================== @staticmethod def forecast_daily(latitude: float, longitude: float, **kwargs): url = base_url + f"?latitude={latitude}&longitude={longitude}" - if 'daily' in kwargs: - hourly_fields = ",".join(kwargs.get('daily')) - url = url+f"&daily={hourly_fields}" + if "daily" in kwargs: + hourly_fields = ",".join(kwargs.get("daily")) + url = url + f"&daily={hourly_fields}" - if 'timezone' in kwargs: - url = url+f"&timezone={kwargs.get('timezone')}" - if 'start_date' in kwargs: - url = url+f"&start_date={kwargs.get('start_date')}" + if "timezone" in kwargs: + url = url + f"&timezone={kwargs.get('timezone')}" + if "start_date" in kwargs: + url = url + f"&start_date={kwargs.get('start_date')}" - if 'end_date' in kwargs: - url = url+f"&end_date={kwargs.get('end_date')}" + if "end_date" in kwargs: + url = url + f"&end_date={kwargs.get('end_date')}" if measurement_type == "imperial": url += f"&temperature_unit={temperature_unit}&wind_speed_unit={wind_speed_unit}" try: - url = url+f"&timeformat=unixtime" + url = url + f"&timeformat=unixtime" response = requests.get(url) response.raise_for_status() # Raise an exception if the request was unsuccessful data = response.json() @@ -131,7 +130,7 @@ def forecast_daily(latitude: float, longitude: float, **kwargs): except requests.exceptions.RequestException as e: print(f"Error: {e}") - def _get_daily_forecast(self,lat,lon): + def _get_daily_forecast(self, lat, lon): daily_args = [ "weathercode", "temperature_2m_max", @@ -143,4 +142,4 @@ def _get_daily_forecast(self,lat,lon): "windspeed_10m_max", ] - return self.forecast_daily(lat,lon,daily=daily_args,timezone="GMT") + return self.forecast_daily(lat, lon, daily=daily_args, timezone="GMT") diff --git a/src/constants.py b/src/constants.py index 5a013df..38ae811 100644 --- a/src/constants.py +++ b/src/constants.py @@ -63,7 +63,11 @@ "95n": icon_loc + "thunderstorms-rain.svg", "96n": icon_loc + "thunderstorms-night-overcast-snow.svg", "99n": icon_loc + "snowflake.svg", + "arrow": icon_loc + "arrow.svg", + "raindrop": icon_loc + "raindrop.svg", + "raindrops": icon_loc + "raindrops.svg", + "wind": icon_loc + "wind.svg", } bg_css = { diff --git a/src/css.py b/src/css.py index 7145f84..c7a5a24 100644 --- a/src/css.py +++ b/src/css.py @@ -137,7 +137,7 @@ } .custom_card_forecast_item{ - padding: .15rem 1.5rem; + padding: 0.15rem 1.5rem; border-radius: .7rem; } diff --git a/src/frontendForecast.py b/src/frontendForecast.py index ad70823..c32cfe9 100644 --- a/src/frontendForecast.py +++ b/src/frontendForecast.py @@ -89,6 +89,7 @@ def page_stacks(self, page_name): orientation=Gtk.Orientation.VERTICAL, margin_top=0, margin_bottom=0) scrolled_window.set_child(forecast_container) + # Get Tomorrow's data from hourly forecast tomorrow_date_time_list = [d_t for d_t in hourly_data.time.get("data") if int( datetime.fromtimestamp(d_t).strftime(r"%d")) == (datetime.today() + timedelta(days=1)).date().day] @@ -135,12 +136,41 @@ def page_stacks(self, page_name): icon_main.set_halign(Gtk.Align.END) icon_main.set_hexpand(True) icon_main.set_pixel_size(50) - icon_main.set_margin_end(60) + icon_main.set_margin_end(30) forecast_item_grid.attach(icon_main, 1, 0, 1, 1) + forecast_cond_grid = Gtk.Grid(valign=Gtk.Align.CENTER,margin_end = 20) + forecast_item_grid.attach(forecast_cond_grid, 2, 0, 1, 1) + + # prec probability + if page_name == 'tomorrow': + prec_probability = hourly_data.precipitation_probability.get("data")[i] + wind_probability = hourly_data.windspeed_10m.get("data")[i] + + + drop_icon = Gtk.Image().new_from_file(icons["raindrop"]) + drop_icon.set_pixel_size(25) + drop_icon.set_halign(Gtk.Align.START) + forecast_cond_grid.attach(drop_icon, 0, 0, 1, 1) + + prec_probability_label = Gtk.Label(label=f" {prec_probability}%", margin_top=5) + prec_probability_label.set_halign(Gtk.Align.START) + forecast_cond_grid.attach(prec_probability_label, 1, 0, 1, 1) + + # Wind speed probability + wind_icon = Gtk.Image().new_from_file(icons["wind"]) + wind_icon.set_pixel_size(25) + wind_icon.set_halign(Gtk.Align.START) + forecast_cond_grid.attach(wind_icon, 0, 1, 1, 1) + speed_unit= hourly_data.windspeed_10m.get("unit") + wind_probability_label = Gtk.Label(label=f" {wind_probability} {speed_unit}", margin_top=5) + forecast_cond_grid.attach(wind_probability_label, 1, 1, 1, 1) + + # Temp label grid temp_label_grid = Gtk.Grid(valign=Gtk.Align.CENTER) - forecast_item_grid.attach(temp_label_grid, 2, 0, 1, 1) + forecast_item_grid.attach(temp_label_grid, 3, 0, 1, 1) + # Max temp label ====== temp_max_text = hourly_data.temperature_2m.get("data")[i] @@ -149,7 +179,7 @@ def page_stacks(self, page_name): temp_max = Gtk.Label(label=f"{temp_max_text:.0f}° ", margin_start=10,) temp_max.set_css_classes(['text-3', 'bold-2']) - temp_label_grid.attach(temp_max, 0, 0, 1, 1) + temp_label_grid.attach(temp_max, 1, 0, 1, 1) # Min temp label ====== temp_min_text = hourly_data.temperature_2m.get("data")[i] @@ -158,4 +188,4 @@ def page_stacks(self, page_name): temp_min = Gtk.Label(label=f" {temp_min_text:.0f}°", margin_top=5) temp_min.set_css_classes(['light-4']) - temp_label_grid.attach(temp_min, 0, 1, 1, 1) + temp_label_grid.attach(temp_min, 1, 1, 1, 1) diff --git a/src/frontendHourlyDetails.py b/src/frontendHourlyDetails.py index 0633ac5..3643fb8 100644 --- a/src/frontendHourlyDetails.py +++ b/src/frontendHourlyDetails.py @@ -151,8 +151,6 @@ def create_stack_page(self, page_name): "Enjoy a rain-free day today!", "Umbrella status: resting. No precipitation in sight !", "No rain in sight today!" - - ] no_prec_label = Gtk.Label(label=no_prec_labels[random.randint(0,len(no_prec_labels)-1)]) no_prec_label.set_css_classes(["text-3a", "bold-3", "light-2"]) diff --git a/src/main.py b/src/main.py index e8e2007..523fade 100644 --- a/src/main.py +++ b/src/main.py @@ -1,6 +1,6 @@ # main.py # -# Copyright 2023 Amit +# Copyright 2024 Amit # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/src/meson.build b/src/meson.build index 8c919c1..0ddd3e7 100644 --- a/src/meson.build +++ b/src/meson.build @@ -28,7 +28,6 @@ configure_file( weather_sources = [ - '__init__.py', 'main.py', 'weather.py', 'weatherData.py', diff --git a/src/units.py b/src/units.py index 58aa201..eb4b46e 100644 --- a/src/units.py +++ b/src/units.py @@ -3,15 +3,3 @@ def get_measurement_type(): settings = Gio.Settings.new("io.github.amit9838.weather") return settings.get_string("measure-type") - -# Units and measurements -measurements = { - "metric":{"temp_unit":"°C", - "speed_unit":'km/h',"speed_mul":3.6, # convert speed from m/s to km/h - "dist_unit":'km',"dist_mul":0.001 - }, - "imperial":{"temp_unit":"°F", - "speed_unit":'mph',"speed_mul":1, #speed miles/hr - "dist_unit":'miles',"dist_mul":0.0006213712, - } - } diff --git a/src/utils.py b/src/utils.py index a750d1f..b96df63 100644 --- a/src/utils.py +++ b/src/utils.py @@ -2,20 +2,12 @@ import socket import json from datetime import datetime, timedelta, timezone -from gi.repository import Adw,Gio +from gi.repository import Adw, Gio current_weather_data = None air_pollution_data = None forecast_weather_data = None -def get_weather_data(): - return current_weather_data,air_pollution_data,forecast_weather_data - -def set_weather_data(current,air_pollution,forecast): - global current_weather_data, air_pollution_data,forecast_weather_data - current_weather_data = current - air_pollution_data = air_pollution - forecast_weather_data = forecast def check_internet_connection(): has_active_internet = False @@ -26,21 +18,26 @@ def check_internet_connection(): except OSError: return has_active_internet + def get_selected_city_coords(): settings = Gio.Settings.new("io.github.amit9838.weather") - selected_city = int(str(settings.get_value('selected-city'))) - added_cities = list(settings.get_value('added-cities')) - city_loc = added_cities[selected_city].split(',') - return city_loc[-2],city_loc[-1] #latitude,longitude + selected_city = int(str(settings.get_value("selected-city"))) + added_cities = list(settings.get_value("added-cities")) + city_loc = added_cities[selected_city].split(",") + return city_loc[-2], city_loc[-1] # latitude,longitude + -def create_toast(text,priority=0): +def create_toast(text, priority=0): toast = Adw.Toast.new(text) toast.set_priority(Adw.ToastPriority(priority)) return toast + def convert_to_local_time(timestamp, timezone_stamp): - hour_offset_from_utc = (timezone_stamp)/3600 - return datetime.fromtimestamp(timestamp,tz=timezone.utc) + timedelta(hours=hour_offset_from_utc) + hour_offset_from_utc = (timezone_stamp) / 3600 + return datetime.fromtimestamp(timestamp, tz=timezone.utc) + timedelta( + hours=hour_offset_from_utc + ) def get_cords(): @@ -60,28 +57,28 @@ def get_my_tz_offset_from_utc(): return f"Error: {str(e)}" -def get_tz_offset_by_cord(lat,lon): +def get_tz_offset_by_cord(lat, lon): url = f"https://api.geotimezone.com/public/timezone?latitude={lat}&longitude={lon}" res = requests.get(url) if res.status_code != 200: return 0 - + res = json.loads(res.text) if res.get("offset") is None: return 0 - + offset_arr = res.get("offset")[3:].split(":") offset_arr = [int(x) for x in offset_arr] - epoch_hr = abs(offset_arr[0])*3600 + epoch_hr = abs(offset_arr[0]) * 3600 epoch_s = 0 - + if len(offset_arr) > 1: - epoch_s = offset_arr[1]*60 - - epoch_offset = epoch_hr+epoch_s + epoch_s = offset_arr[1] * 60 + + epoch_offset = epoch_hr + epoch_s if offset_arr[0] < 0: epoch_offset *= -1 - + return epoch_offset diff --git a/src/weather.in b/src/weather.in index ad5d32c..f839422 100755 --- a/src/weather.in +++ b/src/weather.in @@ -2,7 +2,7 @@ # weather.in # -# Copyright 2023 Amit +# Copyright 2024 Amit # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/src/weather.py b/src/weather.py index ba0a04e..765428c 100644 --- a/src/weather.py +++ b/src/weather.py @@ -4,7 +4,7 @@ gi.require_version("Gtk", "4.0") gi.require_version("Adw", "1") -from gi.repository import Gtk, Adw, Gio, GLib +from gi.repository import Gtk, Adw, Gio # module import from .utils import create_toast,check_internet_connection @@ -107,26 +107,27 @@ def show_loader(self): return container_loader = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) - container_loader.set_margin_top(200) + container_loader.set_margin_top(250) container_loader.set_margin_bottom(300) + # Create loader loader = Gtk.Spinner() loader.set_margin_top(50) loader.set_margin_bottom(50) + loader.set_size_request(120, 120) + loader.set_css_classes(['loader']) container_loader.append(loader) loader_label = Gtk.Label(label=f"Getting Weather Data") - loader_label.set_css_classes(["text-1", "bold-2"]) + loader_label.set_css_classes(["text-2a", "bold-2"]) container_loader.append(loader_label) loader.start() - # loader = Gtk.Label(label=f"Loading…") - # loader.set_css_classes(["text-1", "bold-2"]) - loader.set_hexpand(True) - loader.set_vexpand(True) + # loader.set_hexpand(True) + # loader.set_vexpand(True) self.main_stack.add_named(container_loader, "loader") self.main_stack.set_visible_child_name("loader") @@ -211,8 +212,6 @@ def get_weather(self,reload_type=None,title = ""): self.settings.reset('added-cities') self.settings.reset('selected-city') - - child = self.main_stack.get_child_by_name('main_grid') if child is not None: self.main_stack.remove(child) @@ -317,7 +316,6 @@ def _refresh_weather(self,widget): thread.start() - # ============= Menu buttom methods ============== def _on_about_clicked(self, *args, **kwargs ): AboutWindow(application) diff --git a/src/windowAbout.py b/src/windowAbout.py index 654d89f..8188a8c 100644 --- a/src/windowAbout.py +++ b/src/windowAbout.py @@ -5,7 +5,7 @@ def AboutWindow(parent,*args): dialog = Adw.AboutWindow.new() - dialog.set_application_name(_("Weather")) + dialog.set_application_name("Weather") dialog.set_application_icon("io.github.amit9838.weather") dialog.set_version("1.0.1") dialog.set_developer_name("Amit Chaudhary") @@ -14,7 +14,7 @@ def AboutWindow(parent,*args): dialog.set_website("https://amit9838.github.io/weather/") dialog.set_issue_url("https://github.com/amit9838/weather/issues") # dialog.add_credit_section("Contributors", ["name url"]) - dialog.set_copyright(_("Copyright © 2023 Weather Developers")) + dialog.set_copyright(_("Copyright © 2024 Weather Developers")) dialog.set_developers(["Amit Chaudhary"]) # Translators: Please enter your credits here. (format: "Name https://example.com" or "Name ", no quotes) dialog.set_translator_credits(_("translator_credits")) diff --git a/src/windowLocations.py b/src/windowLocations.py index 1b8bde7..25cceae 100644 --- a/src/windowLocations.py +++ b/src/windowLocations.py @@ -4,7 +4,7 @@ gi.require_version('Gtk', '4.0') gi.require_version('Adw', '1') -from gi.repository import Gtk, Adw,Gio,GLib +from gi.repository import Gtk, Adw, GLib from .utils import create_toast from .backendFindCity import find_city diff --git a/website/index.html b/website/index.html index 92e7dd9..e851528 100644 --- a/website/index.html +++ b/website/index.html @@ -57,19 +57,23 @@

+

Features

+
+

Installation

@@ -98,6 +102,25 @@

About

GitHub

+
+

Support

+ I hope you ❤️ the weather app, if you think it is worth supporting you can do so using below methods +
+
+
+ Buy Me A Coffee + +
+
+ Donate to our Open Collective +
+
+
+