Skip to content

Commit 7c36205

Browse files
committed
Refactor Weather component and add AQI functionality
1 parent 9de48d7 commit 7c36205

File tree

7 files changed

+133
-26
lines changed

7 files changed

+133
-26
lines changed

src/screens/Weather/Main/Weather.tsx

Lines changed: 52 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ import { StatusBar, View } from 'react-native'
1010
import { ScrollView } from 'react-native-gesture-handler'
1111
import { useDerivedValue, useSharedValue, withTiming } from 'react-native-reanimated'
1212
import { useSafeAreaInsets } from 'react-native-safe-area-context'
13-
import { getWeather } from '../api'
14-
import type { Weather } from '../types'
13+
import { getAQI, getWeather } from '../api'
14+
import type { AQI, Weather } from '../types'
1515
import { calculatePressurePercentage, getAQIStatus, getRainStatus, getVisibilityStatusString } from '../utils'
16-
import AQI from './components/AQI'
16+
import AirQualityIndex from './components/AirQualityIndex'
1717
import Cloudiness from './components/Cloudiness'
1818
import DailyForecast from './components/DailyForecast'
1919
import FeelsLike, { getFeelsLikeStatusString } from './components/FeelsLike'
@@ -53,36 +53,39 @@ export default function WeatherScreen({ navigation }: NavProp) {
5353
// eslint-disable-next-line react-hooks/exhaustive-deps
5454
}, [currentWeather?.current.weather[0]!.icon])
5555

56-
const { isPending, data, mutate } = useMutation({
56+
const { isPending, error, data, mutate } = useMutation({
5757
mutationKey: ['currentWeather'],
5858
mutationFn: () => fetchResult(),
5959
onError: (err) => console.log(err),
60-
onSuccess: setCurrentWeather,
60+
onSuccess: (d) => {
61+
setCurrentWeather(d)
62+
setLastUpdated(new Date().getTime())
63+
},
6164
})
6265
const w = data || currentWeather
6366

67+
useEffect(() => {
68+
console.log(currentCity)
69+
}, [currentCity])
70+
6471
useEffect(() => {
6572
if (currentCity) mutate()
6673
// eslint-disable-next-line react-hooks/exhaustive-deps
6774
}, [currentCity])
6875

69-
const bottom = useSafeAreaInsets().bottom
70-
const top = useSafeAreaInsets().top
71-
const height = H + bottom + top
72-
const width = W
73-
7476
const fetchResult = useCallback(async (): Promise<Weather> => {
7577
const now = new Date().getTime()
7678
if (now - lastUpdated > weatherCacheTime) {
77-
setLastUpdated(now)
78-
console.log('fetching weather')
7979
return (await getWeather(currentCity?.lat || 0, currentCity?.lon || 0)) as Weather
8080
}
81-
console.log('using cache')
8281
return currentWeather
83-
// eslint-disable-next-line react-hooks/exhaustive-deps
8482
}, [currentWeather, lastUpdated, weatherCacheTime, currentCity])
8583

84+
const bottom = useSafeAreaInsets().bottom
85+
const top = useSafeAreaInsets().top
86+
const height = H + bottom + top
87+
const width = W
88+
8689
return (
8790
<>
8891
<StatusBar backgroundColor='transparent' barStyle={'light-content'} />
@@ -107,12 +110,45 @@ export default function WeatherScreen({ navigation }: NavProp) {
107110
}
108111

109112
function Boxes({ w, theme }: { w: Weather; theme: Theme }) {
113+
const { currentCity, lastUpdatedAQI, currentAQI, setLastUpdatedAQI, weatherCacheTime, setCurrentAQI } = weatherStore(
114+
(state) => ({
115+
currentCity: state.currentCity,
116+
currentUnit: state.temperatureUnit,
117+
lastUpdatedAQI: state.lastUpdatedAQI,
118+
currentAQI: state.currentAQI,
119+
setCurrentWeather: state.setCurrentWeather,
120+
setLastUpdatedAQI: state.setLastUpdatedAQI,
121+
weatherCacheTime: state.weatherCacheTime,
122+
setCurrentAQI: state.setCurrentAQI,
123+
}),
124+
)
125+
126+
const { mutate } = useMutation({
127+
mutationKey: ['currentWeatherAQI'],
128+
mutationFn: () => fetchResult(),
129+
onError: (err) => console.log(err),
130+
onSuccess: (d: AQI) => {
131+
setCurrentAQI(d)
132+
},
133+
})
134+
const aqiStatus = useMemo(() => getAQIStatus(currentAQI?.list?.[0]?.main?.aqi || 0), [currentAQI])
110135
const pressurePercent = useMemo(() => calculatePressurePercentage(w), [w])
111136
const feelsLikeStatus = useMemo(() => getFeelsLikeStatusString(w?.current.feels_like || 0, w?.current.temp || 0), [w])
112137
const visibilityStatus = useMemo(() => getVisibilityStatusString(w?.current.visibility || 10000), [w])
113-
const aqiStatus = useMemo(() => getAQIStatus(140), [])
114138
const rainStatus = useMemo(() => getRainStatus(w?.daily[0]?.rain), [w])
115139

140+
const fetchResult = useCallback(async (): Promise<AQI> => {
141+
const now = new Date().getTime()
142+
if (now - lastUpdatedAQI > weatherCacheTime) {
143+
setLastUpdatedAQI(now)
144+
return (await getAQI(currentCity?.lat || 0, currentCity?.lon || 0)) as AQI
145+
}
146+
return currentAQI
147+
}, [lastUpdatedAQI, weatherCacheTime, currentAQI, setLastUpdatedAQI, currentCity?.lat, currentCity?.lon])
148+
149+
// eslint-disable-next-line react-hooks/exhaustive-deps
150+
useEffect(() => mutate, [currentCity])
151+
116152
return (
117153
<View style={{ flexDirection: 'row', justifyContent: 'space-between', rowGap: 12 }} className='flex-wrap px-4'>
118154
<FeelsLike theme={theme} feelsLike={w?.current.feels_like || 0} feelsLikeStatus={feelsLikeStatus} />
@@ -125,7 +161,7 @@ function Boxes({ w, theme }: { w: Weather; theme: Theme }) {
125161
visibilityStatus={visibilityStatus}
126162
/>
127163
<Wind theme={theme} w={w} />
128-
<AQI aqi={140} theme={theme} aqiStatus={aqiStatus} />
164+
<AirQualityIndex aqi={currentAQI?.list?.[0]?.main?.aqi} theme={theme} aqiStatus={aqiStatus} />
129165
<Cloudiness theme={theme} clouds={w?.current.clouds || 0} />
130166
<Precipitation theme={theme} rain={w?.daily[0]?.rain} snow={w?.daily[0]?.snow} status={rainStatus} />
131167
<SunRiseSet w={w} theme={theme} now={w?.current?.dt} sunrise={w?.current?.sunrise} sunset={w?.current?.sunset} />

src/screens/Weather/Main/components/AQI.tsx renamed to src/screens/Weather/Main/components/AirQualityIndex.tsx

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
1+
import { weatherStore } from '@/zustand/weatherStore'
12
import { NaturalFoodSolidIcon } from '@assets/icons/icons'
23
import Gradient from '@components/Gradient'
3-
import { boxSize } from '@screens/Weather/utils'
4+
import { hw } from '@screens/Home/style'
5+
import { getAQI, getWeather } from '@screens/Weather/api'
6+
import type { Weather } from '@screens/Weather/types'
7+
import { boxSize, Icons } from '@screens/Weather/utils'
8+
import { useMutation } from '@tanstack/react-query'
49
import { F, Medium, Regular } from '@utils/fonts'
510
import type { Theme } from '@utils/types'
6-
import React from 'react'
11+
import React, { useCallback } from 'react'
712
import { View } from 'react-native'
13+
import { useDerivedValue, useSharedValue } from 'react-native-reanimated'
814
import WeatherLabel from './WeatherLabel'
915

1016
const gradientColors = [
@@ -25,8 +31,16 @@ const gradientColors = [
2531
'#890045',
2632
'#7c0023',
2733
].reverse()
28-
export default function AQI({ aqi, theme, aqiStatus }: { aqi: number; theme: Theme; aqiStatus?: string }) {
29-
let aqiPercent = (aqi * 100) / 500
34+
export default function AirQualityIndex({
35+
aqi,
36+
theme,
37+
aqiStatus,
38+
}: {
39+
aqi: number | undefined
40+
theme: Theme
41+
aqiStatus?: string
42+
}) {
43+
let aqiPercent = (aqi || 0 * 100) / 500
3044
aqiPercent = aqiPercent > 95 ? 95 : aqiPercent
3145
return (
3246
<View className='aspect-square rounded-3xl bg-black/10' style={boxSize}>

src/screens/Weather/api.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,15 @@ function locationUrl(lat: number, lon: number, apiKey: string) {
1414
}
1515

1616
function weatherUrl(lat: number, lon: number, apiKey: string) {
17-
return `https://api.openweathermap.org/data/2.5/onecall?lat=${lat}&lon=${lon}&exclude=minutely&appid=${apiKey || OPENWEATHER_API_KEY}`
17+
return `https://api.openweathermap.org/data/2.5/onecall?lat=${lat}&lon=${lon}&exclude=minutely&appid=${apiKey || OPENWEATHER_API_KEY}&include=aqi`
18+
}
19+
20+
function AQIUrl(lat: number, lon: number, apiKey: string) {
21+
return `https://api.openweathermap.org/data/2.5/air_pollution?lat=${lat}&lon=${lon}&appid=${apiKey || OPENWEATHER_API_KEY}`
22+
}
23+
24+
function AQIForecastUrl(lat: number, lon: number, apiKey: string) {
25+
return `https://api.openweathermap.org/data/2.5/air_pollution/forecast?lat=${lat}&lon=${lon}&appid=${apiKey || OPENWEATHER_API_KEY}`
1826
}
1927

2028
export const WeatherAPI = {
@@ -88,3 +96,7 @@ export async function getLocation(lat: number, lon: number) {
8896
export async function getWeather(lat: number, lon: number) {
8997
return await (await fetch(weatherUrl(lat, lon, OPENWEATHER_API_KEY))).json()
9098
}
99+
100+
export async function getAQI(lat: number, lon: number) {
101+
return await (await fetch(AQIUrl(lat, lon, OPENWEATHER_API_KEY))).json()
102+
}

src/screens/Weather/types.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,3 +123,19 @@ export interface Temp {
123123
eve: number
124124
morn: number
125125
}
126+
127+
export interface AQI {
128+
coord: Coord
129+
list: List[]
130+
}
131+
132+
export interface Coord {
133+
lon: number
134+
lat: number
135+
}
136+
137+
export interface List {
138+
main: { aqi: number }
139+
components: { [key: string]: number }
140+
dt: number
141+
}

src/screens/Weather/utils.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,11 @@ export function getVisibilityStatusString(distance: number): string {
6767
}
6868

6969
export function getAQIStatus(aqi: number) {
70-
if (aqi < 50) return 'Good'
71-
if (aqi < 100) return 'Moderate'
70+
if (aqi < 50) return 'Good air quality'
71+
if (aqi < 100) return 'Moderate air quality'
7272
if (aqi < 150) return 'Unhealthy for Sensitive Groups'
73-
if (aqi < 200) return 'Unhealthy'
74-
if (aqi < 300) return 'Very Unhealthy'
73+
if (aqi < 200) return 'Unhealthy air quality'
74+
if (aqi < 300) return 'Very Unhealthy air quality'
7575
return 'Hazardous'
7676
}
7777
export function getRainStatus(rain: number | undefined) {

src/utils/storage.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ export const WeatherStorage = {
7777
WeatherAtmospherePressureUnit: 'WeatherAtmospherePressureUnit',
7878
WeatherCurrentWeather: 'WeatherCurrentWeather',
7979
WeatherLastUpdated: 'WeatherLastUpdated',
80+
WeatherLastUpdatedAQI: 'WeatherLastUpdatedAQI',
81+
WeatherCurrentAQI: 'WeatherCurrentAQI',
8082
WeatherCacheTime: 'WeatherCacheTime',
8183
WeatherTimeFormat: 'WeatherTimeFormat',
8284
},

src/zustand/weatherStore.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { Weather } from '@screens/Weather/types'
1+
import type { AQI, Weather } from '@screens/Weather/types'
22
import { WEATHER_CACHE_TIME } from '@utils/constants'
33
import S from '@utils/storage'
44
import { ToastAndroid } from 'react-native'
@@ -38,10 +38,14 @@ type WeatherSettingsStore = {
3838
setCurrentWeather: (weather: Weather) => void
3939
lastUpdated: number
4040
setLastUpdated: (time: number) => void
41+
lastUpdatedAQI: number
42+
setLastUpdatedAQI: (time: number) => void
4143
weatherCacheTime: number
4244
setWeatherCacheTime: (time: number) => void
4345
weatherTimeFormat: TimeFormat
4446
setWeatherTimeFormat: (format: TimeFormat) => void
47+
currentAQI: AQI
48+
setCurrentAQI: (aqi: AQI) => void
4549
}
4650

4751
export const weatherStore = create<WeatherSettingsStore>((set) => ({
@@ -66,14 +70,28 @@ export const weatherStore = create<WeatherSettingsStore>((set) => ({
6670
setCurrentWeather: (weather: Weather) => setCurrentWeather(weather, set),
6771
lastUpdated: getWeatherLastUpdated(),
6872
setLastUpdated: (time: number) => setWeatherLastUpdated(time, set),
73+
lastUpdatedAQI: getLastUpdatedAQI(),
74+
setLastUpdatedAQI: (time: number) => setLastUpdatedAQI(time, set),
6975
weatherCacheTime: getWeatherCacheTime(),
7076
setWeatherCacheTime: (time: number) => setWeatherCacheTime(time, set),
7177
weatherTimeFormat: getWeatherTimeFormat(),
7278
setWeatherTimeFormat: (format: TimeFormat) => setWeatherTimeFormat(format, set),
79+
currentAQI: getCurrentAQI(),
80+
setCurrentAQI: (aqi: AQI) => setCurrentAQI(aqi, set),
7381
}))
7482

7583
type Set = (fn: (state: WeatherSettingsStore) => WeatherSettingsStore) => void
7684

85+
function setLastUpdatedAQI(time: number, set: Set) {
86+
S.set('WeatherLastUpdatedAQI', time.toString())
87+
set((state) => ({ ...state, lastUpdatedAQI: time }))
88+
}
89+
90+
function getLastUpdatedAQI() {
91+
const lastUpdated = Number(S.get('WeatherLastUpdatedAQI') || '0')
92+
return isNaN(lastUpdated) ? 0 : lastUpdated
93+
}
94+
7795
function getWeatherTimeFormat() {
7896
return (S.get('WeatherTimeFormat') as TimeFormat) || '12h'
7997
}
@@ -111,6 +129,15 @@ function setCurrentWeather(weather: Weather, set: Set) {
111129
set((state) => ({ ...state, currentWeather: weather }))
112130
}
113131

132+
function getCurrentAQI() {
133+
return S.getParsed<AQI>('WeatherCurrentAQI')
134+
}
135+
136+
function setCurrentAQI(aqi: AQI, set: Set) {
137+
S.set('WeatherCurrentAQI', JSON.stringify(aqi))
138+
set((state) => ({ ...state, currentAQI: aqi }))
139+
}
140+
114141
function getAtmPressureUnit() {
115142
return (S.get('WeatherAtmospherePressureUnit') as AtmosPressureUnit) || 'hPa'
116143
}

0 commit comments

Comments
 (0)