Advertisement
peachyontop

esfrgdfsbv

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