Advertisement
peachyontop

herthtreshrte

Apr 4th, 2025
10
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 14.64 KB | None | 0 0
  1. # Standard library imports
  2. from datetime import datetime
  3. from threading import Thread
  4.  
  5. # Third-party imports
  6. import requests
  7. from dateutil import parser
  8. from kivy.metrics import dp
  9. from kivy.clock import Clock
  10. from kivy.lang import Builder
  11. from kivy.utils import platform
  12. from kivy.uix.scrollview import ScrollView
  13. from kivy.uix.gridlayout import GridLayout
  14.  
  15. # KivyMD components
  16. from kivymd.app import MDApp
  17. from kivymd.uix.screen import MDScreen
  18. from kivymd.uix.boxlayout import MDBoxLayout
  19. from kivymd.uix.label import MDLabel
  20. from kivymd.uix.toolbar import MDTopAppBar
  21. from kivymd.uix.card import MDCard
  22. from kivymd.uix.progressbar import MDProgressBar
  23. from kivymd.uix.refreshlayout import MDScrollViewRefreshLayout
  24. from kivymd.icon_definitions import md_icons
  25.  
  26. # Android-specific imports
  27. try:
  28. from jnius import autoclass
  29. except ImportError:
  30. autoclass = None
  31.  
  32. Builder.load_string('''
  33. <WeatherScreen>:
  34. MDScrollViewRefreshLayout:
  35. id: refresh_layout
  36. refresh_callback: root.refresh
  37. root_layout: root
  38.  
  39. MDScrollView:
  40. MDBoxLayout:
  41. orientation: 'vertical'
  42. spacing: dp(20)
  43. padding: dp(20)
  44. adaptive_height: True
  45.  
  46. MDTopAppBar:
  47. title: "Weather Forecast"
  48. elevation: 0
  49. md_bg_color: app.theme_cls.primary_color
  50. specific_text_color: 1, 1, 1, 1
  51. left_action_items: [["arrow-left", lambda x: app.go_to_main_menu()]]
  52. right_action_items: [["refresh", lambda x: root.refresh()]]
  53.  
  54. MDCard:
  55. id: current_weather_card
  56. orientation: 'vertical'
  57. size_hint: 1, None
  58. height: dp(200)
  59. padding: dp(20)
  60. spacing: dp(10)
  61. elevation: 2
  62. md_bg_color: app.theme_cls.bg_dark
  63.  
  64. MDLabel:
  65. id: update_label
  66. text: "Updating..."
  67. theme_text_color: "Secondary"
  68. font_style: "Caption"
  69. halign: "center"
  70.  
  71. MDBoxLayout:
  72. spacing: dp(20)
  73. adaptive_size: True
  74. center_x: self.parent.center_x
  75.  
  76. MDLabel:
  77. id: weather_icon
  78. text: "weather-sunny"
  79. font_style: "Icon"
  80. size_hint: None, None
  81. size: dp(48), dp(48)
  82. theme_text_color: "Custom"
  83. text_color: 1, 0.8, 0, 1
  84.  
  85. MDLabel:
  86. id: temperature_label
  87. text: "--°C"
  88. font_style: "H2"
  89. halign: "center"
  90. bold: True
  91.  
  92. MDGridLayout:
  93. id: stats_grid
  94. cols: 4
  95. spacing: dp(10)
  96. adaptive_height: True
  97. size_hint_x: 1
  98.  
  99. MDCard:
  100. id: location_card
  101. size_hint: 1, None
  102. height: dp(100)
  103. padding: dp(20)
  104. elevation: 2
  105. md_bg_color: app.theme_cls.bg_dark
  106.  
  107. MDBoxLayout:
  108. MDLabel:
  109. text: "map-marker"
  110. font_style: "Icon"
  111. size_hint: None, None
  112. size: dp(32), dp(32)
  113. theme_text_color: "Primary"
  114.  
  115. MDLabel:
  116. id: location_label
  117. text: "Locating..."
  118. font_style: "Subtitle1"
  119. halign: "left"
  120. valign: "center"
  121. markup: True
  122.  
  123. MDCard:
  124. id: hourly_forecast_card
  125. orientation: 'vertical'
  126. size_hint: 1, None
  127. height: dp(200)
  128. padding: dp(20)
  129. spacing: dp(10)
  130. elevation: 2
  131. md_bg_color: app.theme_cls.bg_dark
  132.  
  133. MDLabel:
  134. text: "24h Forecast"
  135. font_style: "H6"
  136. bold: True
  137. theme_text_color: "Primary"
  138.  
  139. ScrollView:
  140. MDBoxLayout:
  141. id: hourly_forecast_container
  142. orientation: 'horizontal'
  143. spacing: dp(15)
  144. adaptive_width: True
  145. height: dp(100)
  146.  
  147. MDCard:
  148. id: details_card
  149. size_hint: 1, None
  150. height: dp(120)
  151. padding: dp(20)
  152. elevation: 2
  153. md_bg_color: app.theme_cls.bg_dark
  154.  
  155. GridLayout:
  156. cols: 3
  157. spacing: dp(20)
  158. adaptive_height: True
  159.  
  160. WeatherDetailItem:
  161. icon: "weather-windy"
  162. value: "-- km/h"
  163. label: "Wind"
  164.  
  165. WeatherDetailItem:
  166. icon: "water-percent"
  167. value: "--%"
  168. label: "Humidity"
  169.  
  170. WeatherDetailItem:
  171. icon: "weather-rainy"
  172. value: "-- mm"
  173. label: "Rain"
  174.  
  175. <WeatherDetailItem@MDBoxLayout>:
  176. orientation: 'vertical'
  177. spacing: dp(5)
  178. adaptive_size: True
  179. center_x: self.parent.center_x
  180.  
  181. MDLabel:
  182. text: root.icon
  183. font_style: "Icon"
  184. size_hint: None, None
  185. size: dp(24), dp(24)
  186. theme_text_color: "Primary"
  187. halign: "center"
  188.  
  189. MDLabel:
  190. text: root.value
  191. font_style: "H6"
  192. halign: "center"
  193. bold: True
  194.  
  195. MDLabel:
  196. text: root.label
  197. font_style: "Caption"
  198. theme_text_color: "Secondary"
  199. halign: "center"
  200. ''')
  201.  
  202.  
  203. class WeatherScreen(MDScreen):
  204. def on_enter(self):
  205. self.refresh()
  206.  
  207. def refresh(self):
  208. self.ids.update_label.text = "Updating..."
  209. self.ids.temperature_label.text = "--°C"
  210. self.ids.location_label.text = "Locating..."
  211. self.show_loading(True)
  212. Thread(target=self.get_location_and_fetch_weather, daemon=True).start()
  213.  
  214. def show_loading(self, show):
  215. for widget_id in ['current_weather_card', 'location_card', 'hourly_forecast_card', 'details_card']:
  216. self.ids[widget_id].opacity = 0.3 if show else 1
  217. self.ids.refresh_layout.refreshing = show
  218.  
  219. def get_location_and_fetch_weather(self):
  220. try:
  221. used_gps = False
  222. lat, lon = 48.4, 10.0 # Default coordinates
  223.  
  224. if platform == "android" and autoclass is not None:
  225. Context = autoclass('android.content.Context')
  226. PythonActivity = autoclass('org.kivy.android.PythonActivity')
  227. location_service = PythonActivity.mActivity.getSystemService(Context.LOCATION_SERVICE)
  228. location = location_service.getLastKnownLocation(location_service.GPS_PROVIDER)
  229.  
  230. if location:
  231. lat = location.getLatitude()
  232. lon = location.getLongitude()
  233. used_gps = True
  234.  
  235. self.fetch_weather(lat, lon, used_gps)
  236.  
  237. except Exception as e:
  238. print(f"Location error: {e}")
  239. self.fetch_weather(48.4, 10.0, False)
  240.  
  241. def fetch_weather(self, lat, lon, used_gps):
  242. try:
  243. url = (
  244. f"https://api.open-meteo.com/v1/forecast?"
  245. f"latitude={lat}&longitude={lon}&"
  246. f"current_weather=true&"
  247. f"hourly=temperature_2m,precipitation_probability,relativehumidity_2m&"
  248. f"daily=weathercode,temperature_2m_max,temperature_2m_min&"
  249. f"timezone=auto"
  250. )
  251.  
  252. response = requests.get(url, timeout=10)
  253. data = response.json()
  254. current = data.get("current_weather", {})
  255. hourly = data.get("hourly", {})
  256.  
  257. weather_data = {
  258. 'temp': current.get("temperature", "--"),
  259. 'wind': current.get("windspeed", "--"),
  260. 'code': current.get("weathercode", 0),
  261. 'time': current.get("time", ""),
  262. 'humidity': hourly.get("relativehumidity_2m", [0])[0],
  263. 'precipitation': hourly.get("precipitation_probability", [0])[0],
  264. 'hourly_temps': hourly.get("temperature_2m", [])[:24],
  265. 'hourly_times': hourly.get("time", [])[:24]
  266. }
  267.  
  268. Clock.schedule_once(lambda dt: self.update_ui(weather_data))
  269. self.update_hourly_forecast(weather_data['hourly_times'], weather_data['hourly_temps'])
  270. Thread(target=self.reverse_geocode, args=(lat, lon, used_gps), daemon=True).start()
  271.  
  272. except Exception as e:
  273. Clock.schedule_once(lambda dt: self.show_error(str(e)))
  274.  
  275. def update_ui(self, data):
  276. self.ids.temperature_label.text = f"{data['temp']}°C"
  277. self.ids.weather_icon.text = self.weather_code_to_icon(data['code'])
  278.  
  279. details = self.ids.details_card.children[0].children
  280. details[0].value = f"{data['wind']} km/h"
  281. details[1].value = f"{data['humidity']}%"
  282. details[2].value = f"{data['precipitation']}%"
  283.  
  284. try:
  285. update_time = parser.isoparse(data['time']).strftime("%H:%M")
  286. self.ids.update_label.text = f"Updated: {update_time}"
  287. except:
  288. self.ids.update_label.text = "Updated: Now"
  289.  
  290. def update_hourly_forecast(self, times, temps):
  291. container = self.ids.hourly_forecast_container
  292. container.clear_widgets()
  293.  
  294. max_temp = max(t for t in temps if isinstance(t, (int, float))) if temps else 1
  295. min_temp = min(t for t in temps if isinstance(t, (int, float))) if temps else 0
  296.  
  297. for time_str, temp in zip(times, temps):
  298. try:
  299. dt = parser.isoparse(time_str)
  300. hour = dt.strftime("%H:%M")
  301. except:
  302. hour = time_str[-5:]
  303.  
  304. box = MDBoxLayout(
  305. orientation='vertical',
  306. spacing=dp(5),
  307. size_hint=(None, None),
  308. size=(dp(60), dp(100))
  309. )
  310.  
  311. if isinstance(temp, (int, float)):
  312. normalized = ((temp - min_temp) / (max_temp - min_temp)) * 100 if max_temp != min_temp else 50
  313. bar = MDProgressBar(
  314. value=normalized,
  315. orientation='vertical',
  316. height=dp(50),
  317. size_hint_y=None
  318. )
  319. temp_text = f"{round(temp)}°"
  320. else:
  321. bar = MDProgressBar(value=0)
  322. temp_text = "--"
  323.  
  324. box.add_widget(bar)
  325. box.add_widget(MDLabel(
  326. text=temp_text,
  327. font_style="Caption",
  328. halign="center"
  329. ))
  330. box.add_widget(MDLabel(
  331. text=hour,
  332. font_style="Overline",
  333. theme_text_color="Secondary",
  334. halign="center"
  335. ))
  336. container.add_widget(box)
  337.  
  338. def reverse_geocode(self, lat, lon, used_gps):
  339. try:
  340. url = f"https://nominatim.openstreetmap.org/reverse?format=json&lat={lat}&lon={lon}"
  341. headers = {"User-Agent": "WeatherApp/1.0 ([email protected])"}
  342. response = requests.get(url, headers=headers, timeout=10)
  343. data = response.json()
  344.  
  345. location_parts = []
  346. address = data.get('address', {})
  347. if city := address.get('city', address.get('town')):
  348. location_parts.append(city)
  349. if state := address.get('state'):
  350. location_parts.append(state)
  351. if country := address.get('country'):
  352. location_parts.append(country)
  353.  
  354. location_text = ", ".join(location_parts) or "Unknown Location"
  355. gps_status = "📍 GPS" if used_gps else "📌 Default"
  356. full_text = f"{location_text}\n({lat:.2f}, {lon:.2f}) {gps_status}"
  357.  
  358. Clock.schedule_once(lambda dt: setattr(self.ids.location_label, 'text', full_text))
  359. except Exception as e:
  360. print(f"Geocode error: {e}")
  361. Clock.schedule_once(lambda dt: setattr(self.ids.location_label, 'text', "Location unavailable"))
  362.  
  363. def weather_code_to_icon(self, code):
  364. icon_map = {
  365. 0: "weather-sunny",
  366. 1: "weather-sunny",
  367. 2: "weather-partly-cloudy",
  368. 3: "weather-cloudy",
  369. 45: "weather-fog",
  370. 51: "weather-pouring",
  371. 61: "weather-rainy",
  372. 71: "weather-snowy",
  373. 80: "weather-rainy",
  374. 95: "weather-lightning"
  375. }
  376. return icon_map.get(code, "weather-cloudy-alert")
  377.  
  378. def show_error(self, message):
  379. self.ids.temperature_label.text = "Error"
  380. self.ids.update_label.text = message
  381. self.ids.location_label.text = "Check connection"
  382. self.show_loading(False)
  383.  
  384.  
  385. class WeatherApp(MDApp):
  386. def build(self):
  387. self.theme_cls.primary_palette = "Blue"
  388. self.theme_cls.theme_style = "Dark"
  389. return WeatherScreen()
  390.  
  391. def go_to_main_menu(self):
  392. print("Navigate to main menu")
  393.  
  394.  
  395. if __name__ == "__main__":
  396. WeatherApp().run()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement