mirror of
https://github.com/PranshulGG/WeatherMaster.git
synced 2026-10-21 10:40:23 -05:00
refactor: update source architecture to support multiple capabilities from the same API
This commit is contained in:
1 parent
b30c4e7418
commit
9c901ef2cd
38 files changed
+708
-664
No files matched your search
@@ -2,9 +2,6 @@ package com.pranshulgg.weather_master_app.core.di.network
|
||||
|
||||
import com.pranshulgg.weather_master_app.core.network.github.GithubApi
|
||||
import com.pranshulgg.weather_master_app.core.network.sources.address.nominatim.NominatimApi
|
||||
import com.pranshulgg.weather_master_app.core.network.sources.airquality.accu.AccuAqiApi
|
||||
import com.pranshulgg.weather_master_app.core.network.sources.airquality.openmeteo.OpenMeteoAqiApi
|
||||
import com.pranshulgg.weather_master_app.core.network.sources.alerts.accu.AlertsAccuApi
|
||||
import com.pranshulgg.weather_master_app.core.network.sources.alerts.fpas.FpasApi
|
||||
import com.pranshulgg.weather_master_app.core.network.sources.alerts.weatherapi.AlertsWeatherApi
|
||||
import com.pranshulgg.weather_master_app.core.network.sources.alerts.wmosevereweather.WmoSevereWeatherApi
|
||||
@@ -29,6 +26,7 @@ import com.pranshulgg.weather_master_app.core.network.sources.weather.metnorway.
|
||||
import com.pranshulgg.weather_master_app.core.network.sources.weather.metoffice.MetOfficeApi
|
||||
import com.pranshulgg.weather_master_app.core.network.sources.weather.nws.NwsApi
|
||||
import com.pranshulgg.weather_master_app.core.network.sources.weather.openmeteo.OpenMeteoApi
|
||||
import com.pranshulgg.weather_master_app.core.network.sources.weather.openmeteo.airquality.OpenMeteoAqiApi
|
||||
import com.pranshulgg.weather_master_app.core.network.sources.weather.pirateweather.PirateWeatherApi
|
||||
import com.pranshulgg.weather_master_app.core.network.sources.weather.smhi.SmhiApi
|
||||
import dagger.Module
|
||||
@@ -113,14 +111,6 @@ object ApiModule {
|
||||
@Singleton
|
||||
fun provideMeteoamApi(): MeteoamApi = MeteoamApi.create()
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideAccuAqiApi(): AccuAqiApi = AccuAqiApi.create()
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideAlertsAccuApi(): AlertsAccuApi = AlertsAccuApi.create()
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideAlertsWeatherApi(): AlertsWeatherApi = AlertsWeatherApi.create()
|
||||
|
||||
+22
-67
@@ -1,17 +1,6 @@
|
||||
package com.pranshulgg.weather_master_app.core.di.weather
|
||||
|
||||
import com.pranshulgg.weather_master_app.core.network.sources.airquality.accu.AccuAqiApi
|
||||
import com.pranshulgg.weather_master_app.core.network.sources.airquality.accu.AccuAqiRepository
|
||||
import com.pranshulgg.weather_master_app.core.network.sources.airquality.openmeteo.OpenMeteoAqiApi
|
||||
import com.pranshulgg.weather_master_app.core.network.sources.airquality.openmeteo.OpenMeteoAqiRepository
|
||||
import com.pranshulgg.weather_master_app.core.network.sources.alerts.accu.AlertsAccuApi
|
||||
import com.pranshulgg.weather_master_app.core.network.sources.alerts.accu.AlertsAccuRepository
|
||||
import com.pranshulgg.weather_master_app.core.network.sources.alerts.fpas.FpasApi
|
||||
import com.pranshulgg.weather_master_app.core.network.sources.alerts.fpas.FpasRepository
|
||||
import com.pranshulgg.weather_master_app.core.network.sources.alerts.weatherapi.AlertsWeatherApi
|
||||
import com.pranshulgg.weather_master_app.core.network.sources.alerts.weatherapi.AlertsWeatherApiRepository
|
||||
import com.pranshulgg.weather_master_app.core.network.sources.alerts.wmosevereweather.WmoSevereWeatherApi
|
||||
import com.pranshulgg.weather_master_app.core.network.sources.alerts.wmosevereweather.WmoSevereWeatherRepository
|
||||
import com.pranshulgg.weather_master_app.core.network.sources.weather.openmeteo.airquality.OpenMeteoAqiApi
|
||||
import com.pranshulgg.weather_master_app.core.network.sources.weather.accu.AccuApi
|
||||
import com.pranshulgg.weather_master_app.core.network.sources.weather.accu.AccuRepository
|
||||
import com.pranshulgg.weather_master_app.core.network.sources.weather.aemet.AemetApi
|
||||
@@ -71,16 +60,11 @@ object WeatherRepositoryModule {
|
||||
fun provideOpenMeteoRepository(
|
||||
dao: LocationsDao,
|
||||
api: OpenMeteoApi,
|
||||
weatherDao: WeatherDao
|
||||
): OpenMeteoRepository = OpenMeteoRepository(dao, weatherDao, api)
|
||||
weatherDao: WeatherDao,
|
||||
airQualityDao: AirQualityDao,
|
||||
airQualityApi: OpenMeteoAqiApi
|
||||
): OpenMeteoRepository = OpenMeteoRepository(dao, weatherDao, api, airQualityApi, airQualityDao)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideOpenMeteoAqiRepository(
|
||||
api: OpenMeteoAqiApi,
|
||||
dao: AirQualityDao
|
||||
): OpenMeteoAqiRepository =
|
||||
OpenMeteoAqiRepository(api, dao)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@@ -161,8 +145,19 @@ object WeatherRepositoryModule {
|
||||
dao: LocationsDao,
|
||||
api: AccuApi,
|
||||
weatherDao: WeatherDao,
|
||||
locationKeysDao: LocationKeysDao
|
||||
): AccuRepository = AccuRepository(dao, weatherDao, api, locationKeysDao)
|
||||
locationKeysDao: LocationKeysDao,
|
||||
airQualityDao: AirQualityDao,
|
||||
alertsDao: AlertsDao,
|
||||
locationsDao: LocationsDao
|
||||
): AccuRepository = AccuRepository(
|
||||
dao,
|
||||
weatherDao,
|
||||
api,
|
||||
locationKeysDao,
|
||||
airQualityDao,
|
||||
alertsDao,
|
||||
locationsDao
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@@ -172,33 +167,6 @@ object WeatherRepositoryModule {
|
||||
weatherDao: WeatherDao
|
||||
): MeteoamRepository = MeteoamRepository(dao, weatherDao, api)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideAccuAqiRepository(
|
||||
api: AccuAqiApi,
|
||||
dao: AirQualityDao,
|
||||
locationKeysDao: LocationKeysDao,
|
||||
accuApi: AccuApi
|
||||
): AccuAqiRepository = AccuAqiRepository(api, dao, locationKeysDao, accuApi)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideAccuAlertsRepository(
|
||||
api: AlertsAccuApi,
|
||||
dao: AlertsDao,
|
||||
locationKeysDao: LocationKeysDao,
|
||||
accuApi: AccuApi,
|
||||
locationsDao: LocationsDao
|
||||
): AlertsAccuRepository = AlertsAccuRepository(api, dao, locationKeysDao, accuApi, locationsDao)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideWeatherApiAlertsRepository(
|
||||
api: AlertsWeatherApi,
|
||||
dao: AlertsDao,
|
||||
locationsDao: LocationsDao
|
||||
): AlertsWeatherApiRepository = AlertsWeatherApiRepository(api, dao, locationsDao)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideIpmaRepository(
|
||||
@@ -208,21 +176,6 @@ object WeatherRepositoryModule {
|
||||
locationKeysDao: LocationKeysDao
|
||||
): IpmaRepository = IpmaRepository(dao, weatherDao, api, locationKeysDao)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideWmoSevereWeatherAlertsRepository(
|
||||
api: WmoSevereWeatherApi,
|
||||
dao: AlertsDao,
|
||||
locationsDao: LocationsDao
|
||||
): WmoSevereWeatherRepository = WmoSevereWeatherRepository(api, dao, locationsDao)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideFpasAlertsRepository(
|
||||
api: FpasApi,
|
||||
dao: AlertsDao,
|
||||
locationsDao: LocationsDao
|
||||
): FpasRepository = FpasRepository(api, dao, locationsDao)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@@ -266,8 +219,10 @@ object WeatherRepositoryModule {
|
||||
dao: LocationsDao,
|
||||
api: PirateWeatherApi,
|
||||
weatherDao: WeatherDao,
|
||||
apiKeysDao: ApiKeysDao
|
||||
): PirateWeatherRepository = PirateWeatherRepository(dao, weatherDao, api, apiKeysDao)
|
||||
apiKeysDao: ApiKeysDao,
|
||||
alertsDao: AlertsDao
|
||||
): PirateWeatherRepository =
|
||||
PirateWeatherRepository(dao, weatherDao, api, apiKeysDao, alertsDao)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
|
||||
+4
-6
@@ -1,8 +1,6 @@
|
||||
package com.pranshulgg.weather_master_app.core.model.domain.location
|
||||
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.AirQualitySource
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.AlertSource
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.WeatherSource
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.Source
|
||||
import com.pranshulgg.weather_master_app.core.model.weather.openmeteo.OpenMeteoModel
|
||||
|
||||
data class Location(
|
||||
@@ -14,13 +12,13 @@ data class Location(
|
||||
val timezone: String,
|
||||
val countryCode: String?,
|
||||
val state: String,
|
||||
val source: WeatherSource = WeatherSource.OPEN_METEO,
|
||||
val source: Source = Source.OPEN_METEO,
|
||||
val isFavorite: Boolean = false,
|
||||
val isPinned: Boolean = false,
|
||||
val isDefault: Boolean,
|
||||
val isDeviceLocation: Boolean = false,
|
||||
val alertSource: AlertSource = AlertSource.NONE,
|
||||
val airQualitySource: AirQualitySource = AirQualitySource.OPEN_METEO,
|
||||
val alertSource: Source = Source.NONE,
|
||||
val airQualitySource: Source = Source.OPEN_METEO,
|
||||
val customName: String? = null,
|
||||
val openMeteoModel: OpenMeteoModel = OpenMeteoModel.BEST_MATCH,
|
||||
val alertsLastFetchedAt: Long? = null
|
||||
|
||||
@@ -0,0 +1,252 @@
|
||||
package com.pranshulgg.weather_master_app.core.model.sources
|
||||
|
||||
import com.pranshulgg.weather_master_app.R
|
||||
|
||||
|
||||
// TODO: implement alerts / air quality for sources that support it
|
||||
enum class Source(
|
||||
val displayName: String,
|
||||
val hourlyAggregationLimitHours: Int = 24, // stupid and should be removed
|
||||
val displayLink: String,
|
||||
val fullName: String,
|
||||
val countryNameRes: Int? = null,
|
||||
val requiresUserApiKey: Boolean = false, // Source must not be selectable until the user has provided their API key
|
||||
val regionalButWorldwideSupport: Boolean = false,
|
||||
val capabilities: Set<Capability>,
|
||||
// val providesAlerts: Boolean = false,
|
||||
// val providesAirQuality: Boolean = false,
|
||||
) {
|
||||
OPEN_METEO(
|
||||
displayName = "Open Meteo",
|
||||
fullName = "Open Meteo",
|
||||
displayLink = "https://open-meteo.com/",
|
||||
capabilities = setOf(Capability.WEATHER, Capability.AIR_QUALITY)
|
||||
),
|
||||
NWS(
|
||||
displayName = "NWS",
|
||||
fullName = "National Weather Service",
|
||||
displayLink = "https://www.weather.gov/documentation/services-web-api",
|
||||
countryNameRes = R.string.country_usa,
|
||||
capabilities = setOf(Capability.WEATHER)
|
||||
),
|
||||
SMHI(
|
||||
displayName = "SMHI",
|
||||
fullName = "Swedish Meteorological and Hydrological Institute",
|
||||
displayLink = "https://opendata.smhi.se",
|
||||
countryNameRes = R.string.country_sweden,
|
||||
capabilities = setOf(Capability.WEATHER)
|
||||
),
|
||||
DWD(
|
||||
displayName = "DWD",
|
||||
fullName = "Bright Sky DWD",
|
||||
displayLink = "https://brightsky.dev",
|
||||
countryNameRes = R.string.country_germany,
|
||||
capabilities = setOf(Capability.WEATHER)
|
||||
),
|
||||
METEO_FRANCE(
|
||||
displayName = "Météo-France",
|
||||
fullName = "Météo-France",
|
||||
displayLink = "https://meteofrance.com/",
|
||||
regionalButWorldwideSupport = true,
|
||||
capabilities = setOf(Capability.WEATHER)
|
||||
),
|
||||
ECCC(
|
||||
displayName = "ECCC",
|
||||
fullName = "Environment and Climate Change Canada",
|
||||
displayLink = "https://app.weather.gc.ca/",
|
||||
countryNameRes = R.string.country_canada,
|
||||
capabilities = setOf(Capability.WEATHER)
|
||||
),
|
||||
FMI(
|
||||
displayName = "FMI",
|
||||
fullName = "Finnish Meteorological Institute",
|
||||
displayLink = "https://en.ilmatieteenlaitos.fi/",
|
||||
countryNameRes = R.string.country_finland,
|
||||
capabilities = setOf(Capability.WEATHER)
|
||||
),
|
||||
CHINA(
|
||||
displayName = "China",
|
||||
fullName = "China National Environmental Monitoring Centre",
|
||||
displayLink = "https://www.cnemc.cn/",
|
||||
capabilities = setOf(Capability.WEATHER)
|
||||
),
|
||||
BMKG(
|
||||
displayName = "BMKG",
|
||||
fullName = "Badan Meteorologi, Klimatologi, dan Geofisika",
|
||||
displayLink = "https://www.bmkg.go.id/",
|
||||
countryNameRes = R.string.country_indonesia,
|
||||
capabilities = setOf(Capability.WEATHER)
|
||||
),
|
||||
ACCU_WEATHER(
|
||||
displayName = "AccuWeather",
|
||||
fullName = "AccuWeather",
|
||||
displayLink = "https://www.accuweather.com/",
|
||||
capabilities = setOf(Capability.WEATHER, Capability.ALERTS, Capability.AIR_QUALITY)
|
||||
),
|
||||
METEO_AM(
|
||||
displayName = "Meteo AM",
|
||||
fullName = "Meteorologia Aeronautica Militare",
|
||||
displayLink = "https://www.meteoam.it/",
|
||||
countryNameRes = R.string.country_italy,
|
||||
capabilities = setOf(Capability.WEATHER)
|
||||
),
|
||||
IPMA(
|
||||
displayName = "IPMA",
|
||||
fullName = "Instituto Português do Mar e da Atmosfera",
|
||||
displayLink = "https://api.ipma.pt/",
|
||||
countryNameRes = R.string.country_portugal,
|
||||
capabilities = setOf(Capability.WEATHER)
|
||||
),
|
||||
GISMETEO(
|
||||
displayName = "Gismeteo",
|
||||
fullName = "Gismeteo",
|
||||
displayLink = "https://www.gismeteo.ru/",
|
||||
countryNameRes = R.string.country_russia,
|
||||
regionalButWorldwideSupport = true,
|
||||
capabilities = setOf(Capability.WEATHER)
|
||||
),
|
||||
MET_OFFICE(
|
||||
displayName = "Met Office",
|
||||
fullName = "Meteorological Office",
|
||||
displayLink = "https://www.metoffice.gov.uk/",
|
||||
countryNameRes = R.string.country_united_kingdom,
|
||||
requiresUserApiKey = true,
|
||||
regionalButWorldwideSupport = true,
|
||||
capabilities = setOf(Capability.WEATHER)
|
||||
),
|
||||
PIRATE_WEATHER(
|
||||
displayName = "Pirate Weather",
|
||||
fullName = "Pirate Weather",
|
||||
displayLink = "https://pirateweather.net/",
|
||||
requiresUserApiKey = true,
|
||||
regionalButWorldwideSupport = false,
|
||||
capabilities = setOf(Capability.WEATHER, Capability.ALERTS)
|
||||
),
|
||||
IMD(
|
||||
displayName = "IMD",
|
||||
fullName = "India Meteorological Department",
|
||||
displayLink = "https://mausam.imd.gov.in/",
|
||||
countryNameRes = R.string.country_india,
|
||||
capabilities = setOf(Capability.WEATHER)
|
||||
),
|
||||
MET_NORWAY(
|
||||
displayName = "Met Norway",
|
||||
fullName = "Met Norway",
|
||||
displayLink = "https://api.met.no/",
|
||||
regionalButWorldwideSupport = true,
|
||||
capabilities = setOf(Capability.WEATHER)
|
||||
),
|
||||
AEMET(
|
||||
displayName = "AEMET",
|
||||
fullName = "Agencia Estatal de Meteorología",
|
||||
displayLink = "https://opendata.aemet.es/centrodedescargas/altaUsuario",
|
||||
countryNameRes = R.string.country_spain,
|
||||
requiresUserApiKey = true,
|
||||
capabilities = setOf(Capability.WEATHER)
|
||||
),
|
||||
|
||||
WEATHER_API(
|
||||
displayName = "WeatherApi",
|
||||
fullName = "WeatherApi.com",
|
||||
displayLink = "https://www.weatherapi.com/",
|
||||
capabilities = setOf(Capability.ALERTS)
|
||||
),
|
||||
WMO_SEVERE_WEATHER(
|
||||
displayName = "WMO Severe Weather",
|
||||
fullName = "WMO Severe Weather Information Centre",
|
||||
displayLink = "https://www.wmo.int/",
|
||||
capabilities = setOf(Capability.ALERTS)
|
||||
),
|
||||
FPAS(
|
||||
displayName = "FOSS Public Alert Server",
|
||||
fullName = "FOSS Public Alert Server",
|
||||
displayLink = "https://invent.kde.org/webapps/foss-public-alert-server",
|
||||
capabilities = setOf(Capability.ALERTS)
|
||||
),
|
||||
NONE(
|
||||
displayName = "None",
|
||||
fullName = "",
|
||||
displayLink = "",
|
||||
capabilities = setOf(Capability.ALERTS, Capability.AIR_QUALITY)
|
||||
),
|
||||
CWA(
|
||||
displayName = "CWA",
|
||||
fullName = "Central Weather Administration",
|
||||
displayLink = "https://opendata.cwa.gov.tw/",
|
||||
countryNameRes = R.string.country_taiwan,
|
||||
requiresUserApiKey = true,
|
||||
capabilities = setOf(Capability.WEATHER)
|
||||
);
|
||||
|
||||
// Sources that provide snow/rain as precipitation
|
||||
fun providesSnowFall(): Boolean {
|
||||
return when (this) {
|
||||
MET_NORWAY -> false
|
||||
DWD -> false
|
||||
CHINA -> false
|
||||
BMKG -> false
|
||||
IMD -> false
|
||||
CWA -> false
|
||||
else -> true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// WE MAP EVERY WEATHER SOURCE HERE, AS THEY GET ADDED
|
||||
|
||||
private val sourcesByCountry = buildMap {
|
||||
put("US", listOf(Source.NWS))
|
||||
put("SE", listOf(Source.SMHI))
|
||||
put("DE", listOf(Source.DWD))
|
||||
put("CA", listOf(Source.ECCC))
|
||||
put("FI", listOf(Source.FMI))
|
||||
put("CN", listOf(Source.CHINA))
|
||||
put("ID", listOf(Source.BMKG))
|
||||
listOf("IT", "VA").forEach { put(it, listOf(Source.METEO_AM)) }
|
||||
put("PT", listOf(Source.IPMA))
|
||||
put("RU", listOf(Source.GISMETEO))
|
||||
listOf("GB", "UK").forEach { put(it, listOf(Source.MET_OFFICE)) }
|
||||
put("NO", listOf(Source.MET_NORWAY))
|
||||
put("FR", listOf(Source.METEO_FRANCE))
|
||||
put("ES", listOf(Source.AEMET))
|
||||
put("IN", listOf(Source.IMD))
|
||||
put("TW", listOf(Source.CWA))
|
||||
|
||||
}
|
||||
|
||||
fun getSourcesForCountry(countryCode: String?): List<Source> {
|
||||
return sourcesByCountry[countryCode] ?: emptyList()
|
||||
}
|
||||
|
||||
|
||||
// GLOBAL SOURCES
|
||||
private val sourcesGlobal = listOf(
|
||||
Source.OPEN_METEO,
|
||||
Source.MET_NORWAY,
|
||||
Source.METEO_FRANCE,
|
||||
Source.ACCU_WEATHER,
|
||||
Source.GISMETEO,
|
||||
Source.MET_OFFICE,
|
||||
Source.PIRATE_WEATHER
|
||||
)
|
||||
|
||||
fun getSourcesGlobal(): List<Source> {
|
||||
return sourcesGlobal
|
||||
}
|
||||
|
||||
fun Source.isSourceSupportedFor(countryCode: String?): Boolean {
|
||||
val source = sourcesByCountry[countryCode] ?: return false
|
||||
|
||||
return this in source
|
||||
}
|
||||
|
||||
fun Source.isGlobal(): Boolean {
|
||||
return this in sourcesGlobal
|
||||
}
|
||||
|
||||
enum class Capability {
|
||||
WEATHER,
|
||||
ALERTS,
|
||||
AIR_QUALITY
|
||||
}
|
||||
-198
@@ -1,198 +0,0 @@
|
||||
package com.pranshulgg.weather_master_app.core.model.sources
|
||||
|
||||
import android.content.Context
|
||||
import com.pranshulgg.weather_master_app.R
|
||||
|
||||
enum class WeatherSource(
|
||||
val displayName: String,
|
||||
val hourlyAggregationLimitHours: Int = 24, // stupid and should be removed
|
||||
val displayLink: String,
|
||||
val fullName: String,
|
||||
val countryNameRes: Int? = null,
|
||||
val requiresUserApiKey: Boolean = false, // Source must not be selectable until the user has provided their API key
|
||||
val regionalButWorldwideSupport: Boolean = false
|
||||
) {
|
||||
OPEN_METEO(
|
||||
displayName = "Open Meteo",
|
||||
fullName = "Open Meteo",
|
||||
displayLink = "https://open-meteo.com/"
|
||||
),
|
||||
NWS(
|
||||
displayName = "NWS",
|
||||
fullName = "National Weather Service",
|
||||
displayLink = "https://www.weather.gov/documentation/services-web-api",
|
||||
countryNameRes = R.string.country_usa
|
||||
),
|
||||
SMHI(
|
||||
displayName = "SMHI",
|
||||
fullName = "Swedish Meteorological and Hydrological Institute",
|
||||
displayLink = "https://opendata.smhi.se",
|
||||
countryNameRes = R.string.country_sweden
|
||||
),
|
||||
DWD(
|
||||
displayName = "DWD",
|
||||
fullName = "Bright Sky DWD",
|
||||
displayLink = "https://brightsky.dev",
|
||||
countryNameRes = R.string.country_germany
|
||||
),
|
||||
METEO_FRANCE(
|
||||
displayName = "Météo-France",
|
||||
fullName = "Météo-France",
|
||||
displayLink = "https://meteofrance.com/",
|
||||
regionalButWorldwideSupport = true
|
||||
),
|
||||
ECCC(
|
||||
displayName = "ECCC",
|
||||
fullName = "Environment and Climate Change Canada",
|
||||
displayLink = "https://app.weather.gc.ca/",
|
||||
countryNameRes = R.string.country_canada
|
||||
),
|
||||
FMI(
|
||||
displayName = "FMI",
|
||||
fullName = "Finnish Meteorological Institute",
|
||||
displayLink = "https://en.ilmatieteenlaitos.fi/",
|
||||
countryNameRes = R.string.country_finland
|
||||
),
|
||||
CHINA(
|
||||
displayName = "China",
|
||||
fullName = "China National Environmental Monitoring Centre",
|
||||
displayLink = "https://www.cnemc.cn/"
|
||||
),
|
||||
BMKG(
|
||||
displayName = "BMKG",
|
||||
fullName = "Badan Meteorologi, Klimatologi, dan Geofisika",
|
||||
displayLink = "https://www.bmkg.go.id/",
|
||||
countryNameRes = R.string.country_indonesia
|
||||
),
|
||||
ACCU_WEATHER(
|
||||
displayName = "AccuWeather",
|
||||
fullName = "AccuWeather",
|
||||
displayLink = "https://www.accuweather.com/"
|
||||
),
|
||||
METEO_AM(
|
||||
displayName = "Meteo AM",
|
||||
fullName = "Meteorologia Aeronautica Militare",
|
||||
displayLink = "https://www.meteoam.it/",
|
||||
countryNameRes = R.string.country_italy
|
||||
),
|
||||
IPMA(
|
||||
displayName = "IPMA",
|
||||
fullName = "Instituto Português do Mar e da Atmosfera",
|
||||
displayLink = "https://api.ipma.pt/",
|
||||
countryNameRes = R.string.country_portugal
|
||||
),
|
||||
GISMETEO(
|
||||
displayName = "Gismeteo",
|
||||
fullName = "Gismeteo",
|
||||
displayLink = "https://www.gismeteo.ru/",
|
||||
countryNameRes = R.string.country_russia,
|
||||
regionalButWorldwideSupport = true
|
||||
),
|
||||
MET_OFFICE(
|
||||
displayName = "Met Office",
|
||||
fullName = "Meteorological Office",
|
||||
displayLink = "https://www.metoffice.gov.uk/",
|
||||
countryNameRes = R.string.country_united_kingdom,
|
||||
requiresUserApiKey = true,
|
||||
regionalButWorldwideSupport = true
|
||||
),
|
||||
PIRATE_WEATHER(
|
||||
displayName = "Pirate Weather",
|
||||
fullName = "Pirate Weather",
|
||||
displayLink = "https://pirateweather.net/",
|
||||
requiresUserApiKey = true,
|
||||
regionalButWorldwideSupport = false
|
||||
),
|
||||
IMD(
|
||||
displayName = "IMD",
|
||||
fullName = "India Meteorological Department",
|
||||
displayLink = "https://mausam.imd.gov.in/",
|
||||
countryNameRes = R.string.country_india
|
||||
),
|
||||
MET_NORWAY(
|
||||
displayName = "Met Norway",
|
||||
fullName = "Met Norway",
|
||||
displayLink = "https://api.met.no/",
|
||||
regionalButWorldwideSupport = true
|
||||
),
|
||||
AEMET(
|
||||
displayName = "AEMET",
|
||||
fullName = "Agencia Estatal de Meteorología",
|
||||
displayLink = "https://opendata.aemet.es/centrodedescargas/altaUsuario",
|
||||
countryNameRes = R.string.country_spain,
|
||||
requiresUserApiKey = true
|
||||
),
|
||||
CWA(
|
||||
displayName = "CWA",
|
||||
fullName = "Central Weather Administration",
|
||||
displayLink = "https://opendata.cwa.gov.tw/",
|
||||
countryNameRes = R.string.country_taiwan,
|
||||
requiresUserApiKey = true
|
||||
);
|
||||
|
||||
// Sources that provide snow/rain as precipitation
|
||||
fun providesSnowFall(): Boolean {
|
||||
return when (this) {
|
||||
MET_NORWAY -> false
|
||||
DWD -> false
|
||||
CHINA -> false
|
||||
BMKG -> false
|
||||
IMD -> false
|
||||
CWA -> false
|
||||
else -> true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// WE MAP EVERY WEATHER SOURCE HERE, AS THEY GET ADDED
|
||||
|
||||
private val weatherSourcesByCountry = buildMap {
|
||||
put("US", listOf(WeatherSource.NWS))
|
||||
put("SE", listOf(WeatherSource.SMHI))
|
||||
put("DE", listOf(WeatherSource.DWD))
|
||||
put("CA", listOf(WeatherSource.ECCC))
|
||||
put("FI", listOf(WeatherSource.FMI))
|
||||
put("CN", listOf(WeatherSource.CHINA))
|
||||
put("ID", listOf(WeatherSource.BMKG))
|
||||
listOf("IT", "VA").forEach { put(it, listOf(WeatherSource.METEO_AM)) }
|
||||
put("PT", listOf(WeatherSource.IPMA))
|
||||
put("RU", listOf(WeatherSource.GISMETEO))
|
||||
listOf("GB", "UK").forEach { put(it, listOf(WeatherSource.MET_OFFICE)) }
|
||||
put("NO", listOf(WeatherSource.MET_NORWAY))
|
||||
put("FR", listOf(WeatherSource.METEO_FRANCE))
|
||||
put("ES", listOf(WeatherSource.AEMET))
|
||||
put("IN", listOf(WeatherSource.IMD))
|
||||
put("TW", listOf(WeatherSource.CWA))
|
||||
|
||||
}
|
||||
|
||||
fun getWeatherSourcesForCountry(countryCode: String?): List<WeatherSource> {
|
||||
return weatherSourcesByCountry[countryCode] ?: emptyList()
|
||||
}
|
||||
|
||||
|
||||
// GLOBAL SOURCES
|
||||
private val weatherSourcesGlobal = listOf(
|
||||
WeatherSource.OPEN_METEO,
|
||||
WeatherSource.MET_NORWAY,
|
||||
WeatherSource.METEO_FRANCE,
|
||||
WeatherSource.ACCU_WEATHER,
|
||||
WeatherSource.GISMETEO,
|
||||
WeatherSource.MET_OFFICE,
|
||||
WeatherSource.PIRATE_WEATHER
|
||||
)
|
||||
|
||||
fun getWeatherSourcesGlobal(): List<WeatherSource> {
|
||||
return weatherSourcesGlobal
|
||||
}
|
||||
|
||||
fun WeatherSource.isSourceSupportedFor(countryCode: String?): Boolean {
|
||||
val source = weatherSourcesByCountry[countryCode] ?: return false
|
||||
|
||||
return this in source
|
||||
}
|
||||
|
||||
fun WeatherSource.isGlobal(): Boolean {
|
||||
return this in weatherSourcesGlobal
|
||||
}
|
||||
+5
-1
@@ -1,12 +1,16 @@
|
||||
package com.pranshulgg.weather_master_app.core.model.weather
|
||||
|
||||
import com.pranshulgg.weather_master_app.core.model.domain.alerts.Alert
|
||||
import com.pranshulgg.weather_master_app.core.model.domain.weather.Weather
|
||||
|
||||
sealed class WeatherResult {
|
||||
data class Success(val weather: Weather) : WeatherResult()
|
||||
data class Success(val weather: Weather, val alerts: List<Alert> = emptyList()) :
|
||||
WeatherResult()
|
||||
|
||||
data object RefreshNotAvailable : WeatherResult()
|
||||
|
||||
data class Error(val exception: Exception, val cacheWeather: Weather? = null) : WeatherResult()
|
||||
|
||||
}
|
||||
|
||||
enum class WeatherResultType {
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ package com.pranshulgg.weather_master_app.core.network.sources.search.geonames
|
||||
|
||||
import com.pranshulgg.weather_master_app.core.model.domain.location.Location
|
||||
import com.pranshulgg.weather_master_app.data.local.mapper.locations.toDomain
|
||||
import com.pranshulgg.weather_master_app.data.repository.SearchRepository
|
||||
import com.pranshulgg.weather_master_app.data.repository.data.SearchRepository
|
||||
import javax.inject.Inject
|
||||
|
||||
|
||||
|
||||
+2
-2
@@ -3,7 +3,7 @@ package com.pranshulgg.weather_master_app.core.utils.weather.forecast
|
||||
import android.util.Log
|
||||
import com.pranshulgg.weather_master_app.core.model.domain.weather.WeatherDaily
|
||||
import com.pranshulgg.weather_master_app.core.model.domain.weather.WeatherHourly
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.WeatherSource
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.Source
|
||||
import com.pranshulgg.weather_master_app.core.utils.formatters.safeZoneId
|
||||
import java.time.Instant
|
||||
import java.time.ZoneId
|
||||
@@ -34,7 +34,7 @@ fun findMatchingDaily(
|
||||
fun findMatchingHourly(
|
||||
data: List<WeatherHourly>,
|
||||
currentMilli: Long,
|
||||
source: WeatherSource,
|
||||
source: Source,
|
||||
zoneId: String,
|
||||
alwaysReturn24Hrs: Boolean = false,
|
||||
keepPastHour: Boolean = false
|
||||
|
||||
+3
-2
@@ -47,7 +47,7 @@ import com.pranshulgg.weather_master_app.data.local.entity.weather.units.AppWeat
|
||||
AlertEntity::class,
|
||||
ApiKeyEntity::class
|
||||
],
|
||||
version = 56,
|
||||
version = 57,
|
||||
autoMigrations = [
|
||||
AutoMigration(from = 39, to = 40),
|
||||
AutoMigration(from = 42, to = 43),
|
||||
@@ -59,7 +59,8 @@ import com.pranshulgg.weather_master_app.data.local.entity.weather.units.AppWeat
|
||||
AutoMigration(from = 50, to = 51),
|
||||
AutoMigration(from = 52, to = 53),
|
||||
AutoMigration(from = 53, to = 54),
|
||||
AutoMigration(from = 54, to = 55)
|
||||
AutoMigration(from = 54, to = 55),
|
||||
AutoMigration(from = 56, to = 57)
|
||||
]
|
||||
)
|
||||
abstract class WeatherMasterDatabase : RoomDatabase() {
|
||||
|
||||
+4
-7
@@ -5,11 +5,8 @@ import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import androidx.room.Transaction
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.AirQualitySource
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.AlertSource
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.WeatherSource
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.Source
|
||||
import com.pranshulgg.weather_master_app.core.model.weather.openmeteo.OpenMeteoModel
|
||||
import com.pranshulgg.weather_master_app.data.local.entity.alerts.AlertEntity
|
||||
import com.pranshulgg.weather_master_app.data.local.entity.location.WeatherLocationEntity
|
||||
import com.pranshulgg.weather_master_app.data.local.entity.weather.WeatherWithRelations
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
@@ -63,7 +60,7 @@ interface LocationsDao {
|
||||
fun getAllLocationsCurrentWeather(): Flow<List<WeatherWithRelations>>
|
||||
|
||||
@Query("UPDATE weather_locations SET source = :source WHERE id = :id")
|
||||
suspend fun updateSourceForLocation(id: String, source: WeatherSource)
|
||||
suspend fun updateSourceForLocation(id: String, source: Source)
|
||||
|
||||
@Query("DELETE FROM weather_daily WHERE locationId = :id")
|
||||
suspend fun deleteDailyDataForLocation(id: String)
|
||||
@@ -81,10 +78,10 @@ interface LocationsDao {
|
||||
suspend fun getLocationForId(id: String): WeatherLocationEntity
|
||||
|
||||
@Query("UPDATE weather_locations SET airQualitySource = :source WHERE id = :id")
|
||||
suspend fun updateAirQualitySourceForLocation(id: String, source: AirQualitySource)
|
||||
suspend fun updateAirQualitySourceForLocation(id: String, source: Source)
|
||||
|
||||
@Query("UPDATE weather_locations SET alertSource = :source WHERE id = :id")
|
||||
suspend fun updateAlertSourceForLocation(id: String, source: AlertSource)
|
||||
suspend fun updateAlertSourceForLocation(id: String, source: Source)
|
||||
|
||||
|
||||
@Query("UPDATE weather_locations SET customName = :name WHERE id = :id")
|
||||
|
||||
+2
-3
@@ -5,8 +5,7 @@ import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import androidx.room.Transaction
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.WeatherSource
|
||||
import com.pranshulgg.weather_master_app.data.local.entity.weather.ApiKeyEntity
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.Source
|
||||
import com.pranshulgg.weather_master_app.data.local.entity.weather.CurrentWeatherEntity
|
||||
import com.pranshulgg.weather_master_app.data.local.entity.weather.DailyWeatherEntity
|
||||
import com.pranshulgg.weather_master_app.data.local.entity.weather.HourlyWeatherEntity
|
||||
@@ -54,7 +53,7 @@ interface WeatherDao {
|
||||
)
|
||||
suspend fun getHourlyDataForLocation(
|
||||
locationId: String,
|
||||
source: WeatherSource
|
||||
source: Source
|
||||
): List<HourlyWeatherEntity>
|
||||
|
||||
}
|
||||
+4
-6
@@ -3,9 +3,7 @@ package com.pranshulgg.weather_master_app.data.local.entity.location
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.AirQualitySource
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.AlertSource
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.WeatherSource
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.Source
|
||||
import com.pranshulgg.weather_master_app.core.model.weather.openmeteo.OpenMeteoModel
|
||||
import kotlin.uuid.ExperimentalUuidApi
|
||||
|
||||
@@ -20,15 +18,15 @@ data class WeatherLocationEntity(
|
||||
val lat: Double,
|
||||
val lon: Double,
|
||||
val timezone: String,
|
||||
val source: WeatherSource,
|
||||
val source: Source,
|
||||
val state: String? = null,
|
||||
val isFavorite: Boolean = false,
|
||||
val isPinned: Boolean = false,
|
||||
val countryCode: String? = null,
|
||||
val isDefault: Boolean = false,
|
||||
val isDeviceLocation: Boolean = false,
|
||||
val alertSource: AlertSource,
|
||||
val airQualitySource: AirQualitySource,
|
||||
val alertSource: Source,
|
||||
val airQualitySource: Source,
|
||||
val customName: String? = null,
|
||||
|
||||
@ColumnInfo(name = "openMeteoModel", defaultValue = "BEST_MATCH")
|
||||
|
||||
+2
-3
@@ -1,10 +1,9 @@
|
||||
package com.pranshulgg.weather_master_app.data.local.entity.weather
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.ForeignKey
|
||||
import androidx.room.Index
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.WeatherSource
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.Source
|
||||
import com.pranshulgg.weather_master_app.core.model.weather.WeatherCondition
|
||||
import com.pranshulgg.weather_master_app.core.model.weather.wind.WindDirection
|
||||
import com.pranshulgg.weather_master_app.data.local.entity.location.WeatherLocationEntity
|
||||
@@ -39,5 +38,5 @@ data class HourlyWeatherEntity(
|
||||
val time: Long,
|
||||
val precipitationProbability: Int?,
|
||||
|
||||
val cachedSource: WeatherSource? = null
|
||||
val cachedSource: Source? = null
|
||||
)
|
||||
+1
-1
@@ -4,7 +4,7 @@ import com.pranshulgg.weather_master_app.core.model.sources.SearchSource
|
||||
import com.pranshulgg.weather_master_app.core.network.sources.search.accu.AccuSearchRepository
|
||||
import com.pranshulgg.weather_master_app.core.network.sources.search.geonames.GeoNamesSearchRepository
|
||||
import com.pranshulgg.weather_master_app.core.network.sources.search.openmeteo.OpenMeteoSearchRepository
|
||||
import com.pranshulgg.weather_master_app.data.repository.SearchRepository
|
||||
import com.pranshulgg.weather_master_app.data.repository.data.SearchRepository
|
||||
import javax.inject.Inject
|
||||
|
||||
class SearchRepositoryProvider @Inject constructor(
|
||||
|
||||
+56
-26
@@ -1,6 +1,9 @@
|
||||
package com.pranshulgg.weather_master_app.data.provider
|
||||
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.WeatherSource
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.Source
|
||||
import com.pranshulgg.weather_master_app.core.network.sources.alerts.fpas.FpasRepository
|
||||
import com.pranshulgg.weather_master_app.core.network.sources.alerts.weatherapi.AlertsWeatherApiRepository
|
||||
import com.pranshulgg.weather_master_app.core.network.sources.alerts.wmosevereweather.WmoSevereWeatherRepository
|
||||
import com.pranshulgg.weather_master_app.core.network.sources.weather.accu.AccuRepository
|
||||
import com.pranshulgg.weather_master_app.core.network.sources.weather.aemet.AemetRepository
|
||||
import com.pranshulgg.weather_master_app.core.network.sources.weather.bmkg.BmkgRepository
|
||||
@@ -20,10 +23,12 @@ import com.pranshulgg.weather_master_app.core.network.sources.weather.nws.NwsRep
|
||||
import com.pranshulgg.weather_master_app.core.network.sources.weather.openmeteo.OpenMeteoRepository
|
||||
import com.pranshulgg.weather_master_app.core.network.sources.weather.pirateweather.PirateWeatherRepository
|
||||
import com.pranshulgg.weather_master_app.core.network.sources.weather.smhi.SmhiRepository
|
||||
import com.pranshulgg.weather_master_app.data.repository.WeatherRepository
|
||||
import com.pranshulgg.weather_master_app.data.repository.data.AirQualityRepository
|
||||
import com.pranshulgg.weather_master_app.data.repository.data.AlertRepository
|
||||
import com.pranshulgg.weather_master_app.data.repository.data.WeatherRepository
|
||||
import javax.inject.Inject
|
||||
|
||||
class WeatherRepositoryProvider @Inject constructor(
|
||||
class SourceRepositoryProvider @Inject constructor(
|
||||
private val openMeteoRepository: OpenMeteoRepository,
|
||||
private val nwsRepository: NwsRepository,
|
||||
private val metNorwayRepository: MetNorwayRepository,
|
||||
@@ -42,30 +47,55 @@ class WeatherRepositoryProvider @Inject constructor(
|
||||
private val aemetRepository: AemetRepository,
|
||||
private val imdRepository: ImdRepository,
|
||||
private val pirateWeatherRepository: PirateWeatherRepository,
|
||||
private val cwaRepository: CwaRepository
|
||||
) {
|
||||
private val cwaRepository: CwaRepository,
|
||||
|
||||
fun getRepository(source: WeatherSource): WeatherRepository {
|
||||
return when (source) {
|
||||
WeatherSource.OPEN_METEO -> openMeteoRepository
|
||||
WeatherSource.NWS -> nwsRepository
|
||||
WeatherSource.MET_NORWAY -> metNorwayRepository
|
||||
WeatherSource.SMHI -> smhiRepository
|
||||
WeatherSource.DWD -> dwdRepository
|
||||
WeatherSource.METEO_FRANCE -> meteoFranceRepository
|
||||
WeatherSource.ECCC -> ecccRepository
|
||||
WeatherSource.FMI -> fmiRepository
|
||||
WeatherSource.CHINA -> chinaRepository
|
||||
WeatherSource.BMKG -> bmkgRepository
|
||||
WeatherSource.ACCU_WEATHER -> accuRepository
|
||||
WeatherSource.METEO_AM -> meteoamRepository
|
||||
WeatherSource.IPMA -> ipmaRepository
|
||||
WeatherSource.GISMETEO -> gismeteoRepository
|
||||
WeatherSource.MET_OFFICE -> metOfficeRepository
|
||||
WeatherSource.AEMET -> aemetRepository
|
||||
WeatherSource.IMD -> imdRepository
|
||||
WeatherSource.PIRATE_WEATHER -> pirateWeatherRepository
|
||||
WeatherSource.CWA -> cwaRepository
|
||||
// ALERTS
|
||||
private val alertsWeatherApiRepository: AlertsWeatherApiRepository,
|
||||
private val wmoSevereWeatherRepository: WmoSevereWeatherRepository,
|
||||
private val fpasRepository: FpasRepository,
|
||||
|
||||
) {
|
||||
|
||||
val repositories = listOf(
|
||||
openMeteoRepository,
|
||||
nwsRepository,
|
||||
metNorwayRepository,
|
||||
smhiRepository,
|
||||
dwdRepository,
|
||||
meteoFranceRepository,
|
||||
ecccRepository,
|
||||
fmiRepository,
|
||||
chinaRepository,
|
||||
bmkgRepository,
|
||||
accuRepository,
|
||||
meteoamRepository,
|
||||
ipmaRepository,
|
||||
gismeteoRepository,
|
||||
metOfficeRepository,
|
||||
aemetRepository,
|
||||
imdRepository,
|
||||
pirateWeatherRepository,
|
||||
cwaRepository,
|
||||
alertsWeatherApiRepository,
|
||||
wmoSevereWeatherRepository,
|
||||
fpasRepository,
|
||||
)
|
||||
|
||||
fun getWeatherRepository(source: Source): WeatherRepository {
|
||||
return repositories.filterIsInstance<WeatherRepository>().firstOrNull {
|
||||
it.weatherSource == source
|
||||
} ?: openMeteoRepository
|
||||
}
|
||||
|
||||
fun getAlertRepository(source: Source): AlertRepository? {
|
||||
return repositories.filterIsInstance<AlertRepository>().firstOrNull {
|
||||
it.alertSource == source
|
||||
}
|
||||
}
|
||||
|
||||
fun getAirQualityRepository(source: Source): AirQualityRepository? {
|
||||
return repositories.filterIsInstance<AirQualityRepository>().firstOrNull {
|
||||
it.airQualitySource == source
|
||||
}
|
||||
}
|
||||
|
||||
+4
-8
@@ -4,18 +4,14 @@ import android.content.Context
|
||||
import androidx.room.Transaction
|
||||
import com.pranshulgg.weather_master_app.core.model.domain.AppException
|
||||
import com.pranshulgg.weather_master_app.core.model.domain.airquality.AirQuality
|
||||
import com.pranshulgg.weather_master_app.core.model.domain.alerts.Alert
|
||||
import com.pranshulgg.weather_master_app.core.model.domain.location.Location
|
||||
import com.pranshulgg.weather_master_app.core.model.domain.weather.Weather
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.AirQualitySource
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.AlertSource
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.WeatherSource
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.Source
|
||||
import com.pranshulgg.weather_master_app.core.model.weather.openmeteo.OpenMeteoModel
|
||||
import com.pranshulgg.weather_master_app.core.network.sources.address.nominatim.json.NominatimRepository
|
||||
import com.pranshulgg.weather_master_app.data.local.dao.airquality.AirQualityDao
|
||||
import com.pranshulgg.weather_master_app.data.local.dao.location.LocationsDao
|
||||
import com.pranshulgg.weather_master_app.data.local.mapper.airquality.toDomain
|
||||
import com.pranshulgg.weather_master_app.data.local.mapper.alerts.toDomain
|
||||
import com.pranshulgg.weather_master_app.data.local.mapper.locations.toDomain
|
||||
import com.pranshulgg.weather_master_app.data.local.mapper.locations.toEntity
|
||||
import com.pranshulgg.weather_master_app.data.local.mapper.weather.toDomain
|
||||
@@ -72,7 +68,7 @@ class LocationsRepository @Inject constructor(
|
||||
}
|
||||
|
||||
|
||||
suspend fun updateSourceForLocation(id: String, source: WeatherSource) {
|
||||
suspend fun updateSourceForLocation(id: String, source: Source) {
|
||||
dao.updateSourceForLocation(id, source)
|
||||
}
|
||||
|
||||
@@ -80,11 +76,11 @@ class LocationsRepository @Inject constructor(
|
||||
dao.updateOpenMeteoModelForLocation(id, model)
|
||||
}
|
||||
|
||||
suspend fun updateAirQualitySourceForLocation(id: String, source: AirQualitySource) {
|
||||
suspend fun updateAirQualitySourceForLocation(id: String, source: Source) {
|
||||
dao.updateAirQualitySourceForLocation(id, source)
|
||||
}
|
||||
|
||||
suspend fun updateAlertSourceForLocation(id: String, source: AlertSource) {
|
||||
suspend fun updateAlertSourceForLocation(id: String, source: Source) {
|
||||
dao.updateAlertSourceForLocation(id, source)
|
||||
}
|
||||
|
||||
|
||||
+50
-21
@@ -1,8 +1,7 @@
|
||||
package com.pranshulgg.weather_master_app.data.repository
|
||||
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.AirQualitySource
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.AlertSource
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.WeatherSource
|
||||
import com.pranshulgg.weather_master_app.core.model.domain.location.Location
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.Source
|
||||
import com.pranshulgg.weather_master_app.data.local.dao.airquality.AirQualityDao
|
||||
import com.pranshulgg.weather_master_app.data.local.dao.alerts.AlertsDao
|
||||
import com.pranshulgg.weather_master_app.data.local.dao.location.LocationKeysDao
|
||||
@@ -25,55 +24,85 @@ class WeatherDataReconcilerRepository @Inject constructor(
|
||||
* NWS might have saved grid points
|
||||
* which are important to be removed from the DB so they don't end up stale
|
||||
*/
|
||||
suspend fun cleanUpStaleData(
|
||||
previousSource: WeatherSource,
|
||||
suspend fun reconcileSourceChange(
|
||||
previous: Location,
|
||||
updated: Location
|
||||
) {
|
||||
if (previous.source != updated.source) {
|
||||
cleanUpStaleWeatherData(
|
||||
previousSource = previous.source,
|
||||
locationId = previous.id,
|
||||
airQualitySource = updated.airQualitySource,
|
||||
currentAlertSource = updated.alertSource
|
||||
)
|
||||
}
|
||||
|
||||
if (previous.airQualitySource != updated.airQualitySource) {
|
||||
cleanUpStaleAirQualityData(
|
||||
locationId = previous.id,
|
||||
currentWeatherSource = updated.source,
|
||||
currentAlertSource = updated.alertSource
|
||||
)
|
||||
}
|
||||
|
||||
if (previous.alertSource != updated.alertSource) {
|
||||
cleanUpStaleAlertsData(
|
||||
locationId = previous.id,
|
||||
currentWeatherSource = updated.source,
|
||||
airQualitySource = updated.airQualitySource
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun cleanUpStaleWeatherData(
|
||||
previousSource: Source,
|
||||
locationId: String,
|
||||
airQualitySource: AirQualitySource,
|
||||
currentAlertSource: AlertSource
|
||||
airQualitySource: Source,
|
||||
currentAlertSource: Source
|
||||
) {
|
||||
when (previousSource) {
|
||||
WeatherSource.NWS -> nwsDao.deleteGridPointsForLocation(locationId)
|
||||
WeatherSource.ACCU_WEATHER -> cleanAccuWeather(
|
||||
Source.NWS -> nwsDao.deleteGridPointsForLocation(locationId)
|
||||
|
||||
Source.ACCU_WEATHER -> cleanAccuWeather(
|
||||
locationId,
|
||||
airQualitySource,
|
||||
currentAlertSource
|
||||
)
|
||||
|
||||
WeatherSource.IPMA -> locationKeysDao.getCityKeyForLocation(locationId)
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun cleanAccuWeather(
|
||||
locationId: String, airQualitySource: AirQualitySource,
|
||||
currentAlertSource: AlertSource
|
||||
locationId: String, airQualitySource: Source,
|
||||
currentAlertSource: Source
|
||||
) {
|
||||
if (airQualitySource != AirQualitySource.ACCU_WEATHER || currentAlertSource != AlertSource.ACCU_WEATHER) {
|
||||
if (airQualitySource != Source.ACCU_WEATHER || currentAlertSource != Source.ACCU_WEATHER) {
|
||||
locationKeysDao.deleteCityKeyForLocation(locationId)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun cleanUpStaleAirQualityData(
|
||||
private suspend fun cleanUpStaleAirQualityData(
|
||||
locationId: String,
|
||||
currentWeatherSource: WeatherSource,
|
||||
currentAlertSource: AlertSource
|
||||
currentWeatherSource: Source,
|
||||
currentAlertSource: Source
|
||||
) {
|
||||
airQualityDao.deleteCurrentAirQuality(locationId)
|
||||
airQualityDao.deleteHourlyAirQuality(locationId)
|
||||
|
||||
if (currentWeatherSource != WeatherSource.ACCU_WEATHER || currentAlertSource != AlertSource.ACCU_WEATHER) {
|
||||
if (currentWeatherSource != Source.ACCU_WEATHER || currentAlertSource != Source.ACCU_WEATHER) {
|
||||
locationKeysDao.deleteCityKeyForLocation(locationId)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun cleanUpStaleAlertsData(
|
||||
private suspend fun cleanUpStaleAlertsData(
|
||||
locationId: String,
|
||||
currentWeatherSource: WeatherSource,
|
||||
airQualitySource: AirQualitySource,
|
||||
currentWeatherSource: Source,
|
||||
airQualitySource: Source,
|
||||
) {
|
||||
alertsDao.deleteAlertsForLocation(locationId)
|
||||
|
||||
if (currentWeatherSource != WeatherSource.ACCU_WEATHER || airQualitySource != AirQualitySource.ACCU_WEATHER) {
|
||||
if (currentWeatherSource != Source.ACCU_WEATHER || airQualitySource != Source.ACCU_WEATHER) {
|
||||
locationKeysDao.deleteCityKeyForLocation(locationId)
|
||||
}
|
||||
}
|
||||
|
||||
-13
@@ -1,13 +0,0 @@
|
||||
package com.pranshulgg.weather_master_app.data.repository
|
||||
|
||||
import com.pranshulgg.weather_master_app.core.model.weather.WeatherResult
|
||||
import com.pranshulgg.weather_master_app.core.model.domain.location.Location
|
||||
|
||||
interface WeatherRepository {
|
||||
suspend fun getWeather(
|
||||
location: Location,
|
||||
isManualRefresh: Boolean = false,
|
||||
isForceRefresh: Boolean
|
||||
): WeatherResult
|
||||
|
||||
}
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
package com.pranshulgg.weather_master_app.data.repository
|
||||
package com.pranshulgg.weather_master_app.data.repository.data
|
||||
|
||||
import com.pranshulgg.weather_master_app.core.model.domain.location.Location
|
||||
|
||||
interface SearchRepository {
|
||||
suspend fun search(query: String): List<Location>
|
||||
|
||||
}
|
||||
}
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
package com.pranshulgg.weather_master_app.data.repository.data
|
||||
|
||||
import com.pranshulgg.weather_master_app.core.model.domain.location.Location
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.Source
|
||||
import com.pranshulgg.weather_master_app.core.model.weather.WeatherResult
|
||||
import com.pranshulgg.weather_master_app.core.model.weather.airquality.AirQualityResult
|
||||
import com.pranshulgg.weather_master_app.core.model.weather.alerts.AlertResult
|
||||
import com.pranshulgg.weather_master_app.data.provider.SourceRepositoryProvider
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
class SourceDataRepository @Inject constructor(
|
||||
private val sourceRepositoryProvider: SourceRepositoryProvider
|
||||
) {
|
||||
suspend fun getData(
|
||||
location: Location,
|
||||
isManualRefresh: Boolean = false,
|
||||
isForceRefresh: Boolean = false,
|
||||
isForceRefreshForAirQuality: Boolean = false,
|
||||
isForceRefreshForAlerts: Boolean = false,
|
||||
onWeather: suspend (WeatherResult) -> Unit,
|
||||
onAlerts: suspend (AlertResult) -> Unit,
|
||||
onAirQuality: suspend (AirQualityResult) -> Unit,
|
||||
) = coroutineScope {
|
||||
|
||||
val weatherSource = location.source
|
||||
val alertSource = location.alertSource
|
||||
val airQualitySource = location.airQualitySource
|
||||
|
||||
val weatherRepo = sourceRepositoryProvider.getWeatherRepository(weatherSource)
|
||||
|
||||
val weatherJob = async {
|
||||
|
||||
|
||||
weatherRepo.getWeather(
|
||||
location,
|
||||
isManualRefresh,
|
||||
isForceRefresh
|
||||
)
|
||||
}
|
||||
|
||||
if (alertSource == weatherSource && weatherRepo.providesAlerts) {
|
||||
|
||||
launch {
|
||||
weatherJob.await()
|
||||
|
||||
val repo = sourceRepositoryProvider.getAlertRepository(alertSource)
|
||||
|
||||
if (repo != null) {
|
||||
onAlerts(repo.getAlerts(location = location))
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
launch {
|
||||
val repo = sourceRepositoryProvider.getAlertRepository(alertSource)
|
||||
|
||||
if (repo != null) {
|
||||
onAlerts(
|
||||
repo.getAlerts(
|
||||
location = location,
|
||||
isManualRefresh = isManualRefresh,
|
||||
isForceRefresh = isForceRefreshForAlerts
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (airQualitySource == weatherSource && weatherRepo.providesAirQuality) {
|
||||
launch {
|
||||
weatherJob.await()
|
||||
|
||||
val repo = sourceRepositoryProvider.getAirQualityRepository(airQualitySource)
|
||||
|
||||
if (repo != null) {
|
||||
onAirQuality(repo.getAirQuality(location = location))
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
launch {
|
||||
val repo = sourceRepositoryProvider.getAirQualityRepository(airQualitySource)
|
||||
|
||||
if (repo != null) {
|
||||
onAirQuality(
|
||||
repo.getAirQuality(
|
||||
location = location,
|
||||
isManualRefresh = isManualRefresh,
|
||||
isForceRefresh = isForceRefreshForAirQuality
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
launch {
|
||||
onWeather(weatherJob.await())
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package com.pranshulgg.weather_master_app.data.repository.data
|
||||
|
||||
import com.pranshulgg.weather_master_app.core.model.domain.location.Location
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.Source
|
||||
import com.pranshulgg.weather_master_app.core.model.weather.WeatherResult
|
||||
|
||||
interface WeatherRepository {
|
||||
val weatherSource: Source
|
||||
|
||||
// Keep them true if air quality or alerts are provided along with the weather response
|
||||
// rather than a separate API endpoint
|
||||
val providesAlerts: Boolean
|
||||
get() = false
|
||||
|
||||
val providesAirQuality: Boolean
|
||||
get() = false
|
||||
|
||||
suspend fun getWeather(
|
||||
location: Location,
|
||||
isManualRefresh: Boolean = false,
|
||||
isForceRefresh: Boolean
|
||||
): WeatherResult
|
||||
|
||||
}
|
||||
@@ -2,8 +2,6 @@ package com.pranshulgg.weather_master_app.data.worker
|
||||
|
||||
import android.Manifest
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.util.Log
|
||||
import androidx.annotation.RequiresPermission
|
||||
import androidx.hilt.work.HiltWorker
|
||||
import androidx.work.CoroutineWorker
|
||||
@@ -12,7 +10,7 @@ import com.pranshulgg.weather_master_app.core.model.domain.weather.Weather
|
||||
import com.pranshulgg.weather_master_app.core.model.domain.weather.WeatherUnits
|
||||
import com.pranshulgg.weather_master_app.core.model.weather.WeatherResult
|
||||
import com.pranshulgg.weather_master_app.core.prefs.helper.PreferencesHelper
|
||||
import com.pranshulgg.weather_master_app.data.provider.WeatherRepositoryProvider
|
||||
import com.pranshulgg.weather_master_app.data.provider.SourceRepositoryProvider
|
||||
import com.pranshulgg.weather_master_app.data.repository.LocationsRepository
|
||||
import com.pranshulgg.weather_master_app.data.repository.WeatherUnitsRepository
|
||||
import com.pranshulgg.weather_master_app.data.worker.gadgetbridge.sendGadgetBridgeWeatherData
|
||||
@@ -22,13 +20,12 @@ import com.pranshulgg.weather_master_app.data.worker.widgets.WeatherWidgetUpdate
|
||||
import com.pranshulgg.weather_master_app.data.worker.widgets.widgetWeatherMapper
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
@HiltWorker
|
||||
class WeatherWorker @AssistedInject constructor(
|
||||
@Assisted context: Context,
|
||||
@Assisted params: WorkerParameters,
|
||||
private val repositoryProvider: WeatherRepositoryProvider,
|
||||
private val repositoryProvider: SourceRepositoryProvider,
|
||||
private val locationsRepository: LocationsRepository,
|
||||
private val appVisibility: AppVisibility,
|
||||
private val weatherUnitsRepository: WeatherUnitsRepository
|
||||
@@ -64,7 +61,7 @@ class WeatherWorker @AssistedInject constructor(
|
||||
|
||||
|
||||
// Get the repository
|
||||
val repo = repositoryProvider.getRepository(default.source)
|
||||
val repo = repositoryProvider.getWeatherRepository(default.source)
|
||||
|
||||
val result = repo.getWeather(
|
||||
location = default,
|
||||
|
||||
+3
-4
@@ -1,16 +1,15 @@
|
||||
package com.pranshulgg.weather_master_app.data.worker.widgets
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import com.pranshulgg.weather_master_app.core.model.weather.WeatherResult
|
||||
import com.pranshulgg.weather_master_app.core.prefs.helper.PreferencesHelper
|
||||
import com.pranshulgg.weather_master_app.data.provider.WeatherRepositoryProvider
|
||||
import com.pranshulgg.weather_master_app.data.provider.SourceRepositoryProvider
|
||||
import com.pranshulgg.weather_master_app.data.repository.LocationsRepository
|
||||
import com.pranshulgg.weather_master_app.data.repository.WeatherUnitsRepository
|
||||
import jakarta.inject.Inject
|
||||
|
||||
class WidgetReload @Inject constructor(
|
||||
private val repositoryProvider: WeatherRepositoryProvider,
|
||||
private val repositoryProvider: SourceRepositoryProvider,
|
||||
private val locationsRepository: LocationsRepository,
|
||||
private val weatherUnitsRepository: WeatherUnitsRepository
|
||||
) {
|
||||
@@ -24,7 +23,7 @@ class WidgetReload @Inject constructor(
|
||||
|
||||
if (defaultLocation == null || units == null) return
|
||||
|
||||
val repo = repositoryProvider.getRepository(defaultLocation.source)
|
||||
val repo = repositoryProvider.getWeatherRepository(defaultLocation.source)
|
||||
|
||||
val result = repo.getWeather(
|
||||
location = defaultLocation,
|
||||
|
||||
-1
@@ -5,7 +5,6 @@ import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.pranshulgg.weather_master_app.core.model.domain.weather.WeatherUnits
|
||||
import com.pranshulgg.weather_master_app.core.network.sources.airquality.openmeteo.OpenMeteoAqiRepository
|
||||
import com.pranshulgg.weather_master_app.data.repository.LocationsRepository
|
||||
import com.pranshulgg.weather_master_app.data.repository.WeatherUnitsRepository
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
|
||||
+21
-30
@@ -1,8 +1,6 @@
|
||||
package com.pranshulgg.weather_master_app.feature.editlocation
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.asPaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
@@ -22,7 +20,6 @@ import androidx.compose.material3.IconButtonDefaults
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.SheetValue
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.rememberBottomSheetState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
@@ -39,9 +36,7 @@ import androidx.navigation.NavController
|
||||
import com.pranshulgg.weather_master_app.R
|
||||
import com.pranshulgg.weather_master_app.core.model.domain.location.Location
|
||||
import com.pranshulgg.weather_master_app.core.model.domain.weather.ApiKey
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.AirQualitySource
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.AlertSource
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.WeatherSource
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.Source
|
||||
import com.pranshulgg.weather_master_app.core.model.weather.openmeteo.OpenMeteoModel
|
||||
import com.pranshulgg.weather_master_app.core.ui.components.Gap
|
||||
import com.pranshulgg.weather_master_app.core.ui.components.LargeTopBarScaffold
|
||||
@@ -55,15 +50,14 @@ import com.pranshulgg.weather_master_app.feature.editlocation.ui.EditLocationBot
|
||||
import com.pranshulgg.weather_master_app.feature.editlocation.ui.EditLocationScreenDialogs
|
||||
import com.pranshulgg.weather_master_app.feature.shared.WeatherViewModel
|
||||
import com.pranshulgg.weather_master_app.feature.shared.ui.SharedBottomSheet
|
||||
import com.pranshulgg.weather_master_app.feature.shared.ui.SharedDialogs
|
||||
|
||||
|
||||
data class EditLocationScreenUiState(
|
||||
val location: Location? = null,
|
||||
val isWeatherSourcesForLocationSheetOpen: Boolean = false,
|
||||
val selectedWeatherSource: WeatherSource? = null,
|
||||
val selectedAlertSource: AlertSource? = null,
|
||||
val selectedAirQualitySource: AirQualitySource? = null,
|
||||
val selectedWeatherSource: Source? = null,
|
||||
val selectedAlertSource: Source? = null,
|
||||
val selectedAirQualitySource: Source? = null,
|
||||
val isAlertSourcesSheetOpen: Boolean = false,
|
||||
val isAirQualitySourcesSheetOpen: Boolean = false,
|
||||
val isEditLocationNameSheetOpen: Boolean = false,
|
||||
@@ -115,28 +109,25 @@ fun EditLocationScreen(
|
||||
|
||||
var currentLocationName by remember { mutableStateOf(locationName) }
|
||||
|
||||
val effectiveWeatherSource =
|
||||
uiState.selectedWeatherSource ?: uiState.location.source
|
||||
|
||||
val effectiveOpenMeteoModel =
|
||||
uiState.selectedOpenMeteoModel
|
||||
?: uiState.location.openMeteoModel
|
||||
|
||||
val selectedWeatherSourceString = buildString {
|
||||
if (uiState.selectedWeatherSource != null) {
|
||||
append(uiState.selectedWeatherSource.displayName)
|
||||
} else {
|
||||
append(uiState.location.source.displayName)
|
||||
append(effectiveWeatherSource.displayName)
|
||||
|
||||
effectiveWeatherSource.countryNameRes?.let {
|
||||
append(" (${stringResource(it)})")
|
||||
}
|
||||
|
||||
if (uiState.selectedWeatherSource?.countryNameRes != null) {
|
||||
append(" (${stringResource(uiState.selectedWeatherSource.countryNameRes)})")
|
||||
} else if (uiState.location.source.countryNameRes != null && uiState.selectedWeatherSource == null) {
|
||||
append(" (${stringResource(uiState.location.source.countryNameRes)})")
|
||||
if (
|
||||
effectiveWeatherSource == Source.OPEN_METEO) {
|
||||
append(" (${effectiveOpenMeteoModel.displayName})")
|
||||
}
|
||||
|
||||
if (uiState.selectedWeatherSource == WeatherSource.OPEN_METEO) {
|
||||
append(" (${uiState.selectedOpenMeteoModel?.displayName})")
|
||||
} else if (uiState.location.source == WeatherSource.OPEN_METEO && uiState.selectedWeatherSource == null) {
|
||||
append(" (${uiState.location.openMeteoModel.displayName})")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
LargeTopBarScaffold(
|
||||
title = stringResource(R.string.location_edit),
|
||||
navigationIcon = { NavigateUpBtn(navController) },
|
||||
@@ -189,8 +180,8 @@ fun EditLocationScreen(
|
||||
},
|
||||
trailing = {
|
||||
val showButton = if (uiState.selectedWeatherSource != null)
|
||||
uiState.selectedWeatherSource == WeatherSource.OPEN_METEO else
|
||||
uiState.location.source == WeatherSource.OPEN_METEO
|
||||
uiState.selectedWeatherSource == Source.OPEN_METEO else
|
||||
uiState.location.source == Source.OPEN_METEO
|
||||
|
||||
if (showButton) {
|
||||
IconButton(
|
||||
@@ -285,7 +276,7 @@ fun EditLocationScreen(
|
||||
selectedSource = uiState.selectedWeatherSource ?: uiState.location.source,
|
||||
onSave = {
|
||||
viewModel.updateSelectedWeatherSource(it)
|
||||
if (it == WeatherSource.OPEN_METEO) {
|
||||
if (it == Source.OPEN_METEO) {
|
||||
viewModel.updateSelectedOpenMeteoModel(
|
||||
uiState.selectedOpenMeteoModel ?: uiState.location.openMeteoModel
|
||||
)
|
||||
|
||||
+4
-8
@@ -4,12 +4,8 @@ import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.pranshulgg.weather_master_app.R
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.AirQualitySource
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.AlertSource
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.WeatherSource
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.Source
|
||||
import com.pranshulgg.weather_master_app.core.model.weather.openmeteo.OpenMeteoModel
|
||||
import com.pranshulgg.weather_master_app.core.ui.snackbar.SnackbarManager
|
||||
import com.pranshulgg.weather_master_app.data.repository.ApiKeysRepository
|
||||
import com.pranshulgg.weather_master_app.data.repository.LocationsRepository
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
@@ -54,15 +50,15 @@ class EditLocationViewModel @Inject constructor(
|
||||
_uiState.value = _uiState.value.copy(isWeatherSourcesForLocationSheetOpen = false)
|
||||
}
|
||||
|
||||
fun updateSelectedWeatherSource(source: WeatherSource) {
|
||||
fun updateSelectedWeatherSource(source: Source) {
|
||||
_uiState.value = _uiState.value.copy(selectedWeatherSource = source)
|
||||
}
|
||||
|
||||
fun updateSelectedAlertSource(source: AlertSource) {
|
||||
fun updateSelectedAlertSource(source: Source) {
|
||||
_uiState.value = _uiState.value.copy(selectedAlertSource = source)
|
||||
}
|
||||
|
||||
fun updateSelectedAirQualitySource(source: AirQualitySource) {
|
||||
fun updateSelectedAirQualitySource(source: Source) {
|
||||
_uiState.value = _uiState.value.copy(selectedAirQualitySource = source)
|
||||
}
|
||||
|
||||
|
||||
+14
-12
@@ -24,13 +24,9 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.pranshulgg.weather_master_app.R
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.AirQualitySource
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.AlertSource
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.WeatherSource
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.getWeatherSourcesForCountry
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.getWeatherSourcesGlobal
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.Capability
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.Source
|
||||
import com.pranshulgg.weather_master_app.core.model.weather.openmeteo.OpenMeteoModel
|
||||
import com.pranshulgg.weather_master_app.core.model.weather.openmeteo.OpenMeteoModelType
|
||||
import com.pranshulgg.weather_master_app.core.ui.components.ActionBottomSheet
|
||||
import com.pranshulgg.weather_master_app.core.ui.components.Gap
|
||||
import com.pranshulgg.weather_master_app.core.ui.components.SettingSection
|
||||
@@ -78,8 +74,8 @@ object EditLocationBottomSheet {
|
||||
fun AlertSourcesSheet(
|
||||
show: Boolean,
|
||||
sheetState: SheetState,
|
||||
selectedSource: AlertSource = AlertSource.NONE,
|
||||
onSave: (AlertSource) -> Unit,
|
||||
selectedSource: Source = Source.NONE,
|
||||
onSave: (Source) -> Unit,
|
||||
onDismiss: () -> Unit
|
||||
) {
|
||||
if (show) {
|
||||
@@ -91,6 +87,9 @@ object EditLocationBottomSheet {
|
||||
mutableStateOf(selectedSource)
|
||||
}
|
||||
|
||||
val sources = Source.entries.filter {
|
||||
Capability.ALERTS in it.capabilities
|
||||
}
|
||||
ActionBottomSheet(
|
||||
sheetState = sheetState,
|
||||
onCancel = { onDismiss() },
|
||||
@@ -100,7 +99,7 @@ object EditLocationBottomSheet {
|
||||
) {
|
||||
SettingSection(
|
||||
title = stringResource(R.string.global_sources),
|
||||
tiles = AlertSource.entries.map { source ->
|
||||
tiles = sources.map { source ->
|
||||
val isSelected = currentSelectedSource == source
|
||||
|
||||
SettingTile.ActionTile(
|
||||
@@ -129,8 +128,8 @@ object EditLocationBottomSheet {
|
||||
fun AirQualitySourcesSheet(
|
||||
show: Boolean,
|
||||
sheetState: SheetState,
|
||||
selectedSource: AirQualitySource = AirQualitySource.OPEN_METEO,
|
||||
onSave: (AirQualitySource) -> Unit,
|
||||
selectedSource: Source = Source.OPEN_METEO,
|
||||
onSave: (Source) -> Unit,
|
||||
onDismiss: () -> Unit
|
||||
) {
|
||||
if (show) {
|
||||
@@ -142,6 +141,9 @@ object EditLocationBottomSheet {
|
||||
mutableStateOf(selectedSource)
|
||||
}
|
||||
|
||||
val sources = Source.entries.filter {
|
||||
Capability.AIR_QUALITY in it.capabilities
|
||||
}
|
||||
ActionBottomSheet(
|
||||
sheetState = sheetState,
|
||||
onCancel = { onDismiss() },
|
||||
@@ -151,7 +153,7 @@ object EditLocationBottomSheet {
|
||||
) {
|
||||
SettingSection(
|
||||
title = stringResource(R.string.global_sources),
|
||||
tiles = AirQualitySource.entries.map { source ->
|
||||
tiles = sources.map { source ->
|
||||
val isSelected = currentSelectedSource == source
|
||||
|
||||
SettingTile.ActionTile(
|
||||
|
||||
@@ -39,7 +39,7 @@ import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.navigation.NavController
|
||||
import com.pranshulgg.weather_master_app.R
|
||||
import com.pranshulgg.weather_master_app.core.model.domain.location.Address
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.WeatherSource
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.Source
|
||||
import com.pranshulgg.weather_master_app.core.model.domain.location.Location
|
||||
import com.pranshulgg.weather_master_app.core.ui.components.Gap
|
||||
import com.pranshulgg.weather_master_app.core.ui.components.Symbol
|
||||
@@ -279,7 +279,7 @@ fun DeviceLocation.toDomain(context: Context): Location {
|
||||
timezone = ZoneId.systemDefault().id,
|
||||
countryCode = "",
|
||||
state = "",
|
||||
source = WeatherSource.OPEN_METEO,
|
||||
source = Source.OPEN_METEO,
|
||||
isFavorite = false,
|
||||
isPinned = false,
|
||||
isDefault = false, // Repository can handle it
|
||||
|
||||
-2
@@ -1,11 +1,9 @@
|
||||
package com.pranshulgg.weather_master_app.feature.locations
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import coil.util.Logger
|
||||
import com.pranshulgg.weather_master_app.core.model.domain.AppException
|
||||
import com.pranshulgg.weather_master_app.core.model.domain.location.Location
|
||||
import com.pranshulgg.weather_master_app.core.model.domain.toMessageRes
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package com.pranshulgg.weather_master_app.feature.main
|
||||
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.material3.DrawerValue
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
@@ -9,11 +7,9 @@ import androidx.compose.material3.SheetValue
|
||||
import androidx.compose.material3.SnackbarDuration
|
||||
import androidx.compose.material3.rememberBottomSheetState
|
||||
import androidx.compose.material3.rememberDrawerState
|
||||
import androidx.compose.material3.rememberModalBottomSheetState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
@@ -30,7 +26,6 @@ import com.pranshulgg.weather_master_app.core.model.domain.location.Location
|
||||
import com.pranshulgg.weather_master_app.core.model.domain.weather.Weather
|
||||
import com.pranshulgg.weather_master_app.core.model.domain.weather.WeatherBlock
|
||||
import com.pranshulgg.weather_master_app.core.model.domain.weather.WeatherUnits
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.WeatherSource
|
||||
import com.pranshulgg.weather_master_app.core.prefs.helper.PreferencesHelper
|
||||
import com.pranshulgg.weather_master_app.core.ui.navigation.NavRoutes
|
||||
import com.pranshulgg.weather_master_app.core.ui.snackbar.SnackbarManager
|
||||
@@ -42,7 +37,6 @@ import com.pranshulgg.weather_master_app.feature.main.ui.MainScreenDialogs
|
||||
import com.pranshulgg.weather_master_app.feature.main.ui.NavigationDrawer
|
||||
import com.pranshulgg.weather_master_app.feature.shared.WeatherViewModel
|
||||
import com.pranshulgg.weather_master_app.feature.shared.ui.SharedBottomSheet
|
||||
import com.pranshulgg.weather_master_app.feature.shared.ui.SharedDialogs
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
data class MainScreenWeatherUiState(
|
||||
|
||||
+2
-2
@@ -14,11 +14,11 @@ import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextDecoration
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.pranshulgg.weather_master_app.core.model.domain.weather.Weather
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.WeatherSource
|
||||
import com.pranshulgg.weather_master_app.core.ui.components.Gap
|
||||
import androidx.compose.foundation.layout.asPaddingValues
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.pranshulgg.weather_master_app.R
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.Source
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
@@ -27,7 +27,7 @@ fun CreditsBottomSection(weather: Weather?, onClick: () -> Unit) {
|
||||
val source = weather?.location?.source
|
||||
|
||||
val sourceString = source?.displayName
|
||||
?: WeatherSource.OPEN_METEO.displayName
|
||||
?: Source.OPEN_METEO.displayName
|
||||
|
||||
val countryString = buildString {
|
||||
if (source?.countryNameRes != null) {
|
||||
|
||||
+6
-6
@@ -28,12 +28,12 @@ fun WeatherAutoRefreshForeground(
|
||||
// isInitialized guard avoids duplicating setActiveLocation()'s
|
||||
// cold-start fetch (Android replays ON_START on resubscribe,
|
||||
// which happens the moment `location` first becomes non-null).
|
||||
if (weatherViewModel.uiState.value.isInitialized) {
|
||||
weatherViewModel.getWeather(
|
||||
location = it,
|
||||
source = it.source
|
||||
)
|
||||
}
|
||||
// if (weatherViewModel.uiState.value.isInitialized) {
|
||||
// weatherViewModel.getWeather(
|
||||
// location = it,
|
||||
// source = it.source
|
||||
// )
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
-20
@@ -1,35 +1,19 @@
|
||||
package com.pranshulgg.weather_master_app.feature.main.ui
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ListItem
|
||||
import androidx.compose.material3.ListItemDefaults
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.SheetState
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import androidx.compose.ui.platform.UriHandler
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.pranshulgg.weather_master_app.R
|
||||
import com.pranshulgg.weather_master_app.core.model.domain.location.Location
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.AirQualitySource
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.AlertSource
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.WeatherSource
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.Source
|
||||
import com.pranshulgg.weather_master_app.core.ui.components.ActionBottomSheet
|
||||
import com.pranshulgg.weather_master_app.core.ui.components.DialogBasic
|
||||
import com.pranshulgg.weather_master_app.core.ui.components.Gap
|
||||
import com.pranshulgg.weather_master_app.core.ui.components.SettingSection
|
||||
import com.pranshulgg.weather_master_app.core.ui.components.SettingTile
|
||||
import com.pranshulgg.weather_master_app.core.ui.components.SettingsTileIcon
|
||||
import com.pranshulgg.weather_master_app.core.ui.components.Symbol
|
||||
import com.pranshulgg.weather_master_app.feature.main.MainScreenViewModel
|
||||
|
||||
object MainScreenBottomSheets {
|
||||
@@ -57,7 +41,7 @@ object MainScreenBottomSheets {
|
||||
append(" (${stringResource(location.source.countryNameRes)})")
|
||||
}
|
||||
|
||||
if (location.source == WeatherSource.OPEN_METEO) {
|
||||
if (location.source == Source.OPEN_METEO) {
|
||||
append(" (${location.openMeteoModel.displayName})")
|
||||
}
|
||||
|
||||
@@ -86,7 +70,7 @@ object MainScreenBottomSheets {
|
||||
)
|
||||
)
|
||||
)
|
||||
if (location.airQualitySource != AirQualitySource.NONE) {
|
||||
if (location.airQualitySource != Source.NONE) {
|
||||
Gap(8.dp)
|
||||
SettingSection(
|
||||
title = stringResource(R.string.weather_air_quality),
|
||||
@@ -102,7 +86,7 @@ object MainScreenBottomSheets {
|
||||
)
|
||||
)
|
||||
}
|
||||
if (location.alertSource != AlertSource.NONE) {
|
||||
if (location.alertSource != Source.NONE) {
|
||||
Gap(8.dp)
|
||||
SettingSection(
|
||||
title = "Alerts",
|
||||
|
||||
@@ -20,7 +20,6 @@ import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.SheetValue
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.rememberBottomSheetState
|
||||
import androidx.compose.material3.rememberModalBottomSheetState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -37,9 +36,8 @@ import com.pranshulgg.weather_master_app.R
|
||||
import com.pranshulgg.weather_master_app.core.model.domain.location.Location
|
||||
import com.pranshulgg.weather_master_app.core.model.domain.weather.ApiKey
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.SearchSource
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.WeatherSource
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.Source
|
||||
import com.pranshulgg.weather_master_app.core.prefs.LocalAppPrefs
|
||||
import com.pranshulgg.weather_master_app.core.utils.formatters.toTitleCase
|
||||
import com.pranshulgg.weather_master_app.core.ui.components.EmptyContainerPlaceholder
|
||||
import com.pranshulgg.weather_master_app.core.ui.components.Gap
|
||||
import com.pranshulgg.weather_master_app.core.ui.components.LargeTopBarScaffold
|
||||
@@ -49,6 +47,7 @@ import com.pranshulgg.weather_master_app.core.ui.components.SettingSection
|
||||
import com.pranshulgg.weather_master_app.core.ui.components.SettingTile
|
||||
import com.pranshulgg.weather_master_app.core.ui.components.Symbol
|
||||
import com.pranshulgg.weather_master_app.core.ui.navigation.NavRoutes
|
||||
import com.pranshulgg.weather_master_app.core.utils.formatters.toTitleCase
|
||||
import com.pranshulgg.weather_master_app.feature.search.ui.SearchFloatingToolbar
|
||||
import com.pranshulgg.weather_master_app.feature.search.ui.SearchScreenBottomSheets
|
||||
import com.pranshulgg.weather_master_app.feature.shared.ui.SharedBottomSheet
|
||||
@@ -174,7 +173,7 @@ fun SearchScreen(navController: NavController) {
|
||||
SharedBottomSheet.WeatherSourcesForLocationSheet(
|
||||
countryCode = selectedLocation?.countryCode,
|
||||
show = uiState.isWeatherSourcesForLocationSheetOpen,
|
||||
selectedSource = selectedLocation?.source ?: WeatherSource.OPEN_METEO,
|
||||
selectedSource = selectedLocation?.source ?: Source.OPEN_METEO,
|
||||
onSave = {
|
||||
viewModel.hideWeatherSourcesForLocationSheet()
|
||||
viewModel.saveLocation(
|
||||
|
||||
+2
-2
@@ -11,7 +11,7 @@ import com.pranshulgg.weather_master_app.core.model.sources.SearchSource
|
||||
import com.pranshulgg.weather_master_app.core.model.domain.location.Location
|
||||
import com.pranshulgg.weather_master_app.core.model.domain.toAppException
|
||||
import com.pranshulgg.weather_master_app.core.model.domain.toMessageRes
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.WeatherSource
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.Source
|
||||
import com.pranshulgg.weather_master_app.core.network.sources.search.geonames.GeoNamesTimezoneRepository
|
||||
import com.pranshulgg.weather_master_app.core.prefs.AppPrefsState
|
||||
import com.pranshulgg.weather_master_app.core.ui.snackbar.SnackbarManager
|
||||
@@ -69,7 +69,7 @@ class SearchScreenViewModel @Inject constructor(
|
||||
location: Location?,
|
||||
onBack: () -> Unit,
|
||||
onReset: () -> Unit,
|
||||
weatherSource: WeatherSource
|
||||
weatherSource: Source
|
||||
) {
|
||||
if (location == null) return
|
||||
|
||||
|
||||
+2
-30
@@ -18,8 +18,7 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavController
|
||||
import com.pranshulgg.weather_master_app.R
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.AlertSource
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.WeatherSource
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.Source
|
||||
import com.pranshulgg.weather_master_app.core.ui.components.Gap
|
||||
import com.pranshulgg.weather_master_app.core.ui.components.LargeTopBarScaffold
|
||||
import com.pranshulgg.weather_master_app.core.ui.components.NavigateUpBtn
|
||||
@@ -29,38 +28,11 @@ import com.pranshulgg.weather_master_app.core.ui.components.SettingsTileIcon
|
||||
import com.pranshulgg.weather_master_app.core.ui.navigation.NavRoutes
|
||||
|
||||
|
||||
private data class WeatherSourceScreen(
|
||||
val displayName: String,
|
||||
val fullName: String,
|
||||
val displayLink: String,
|
||||
val countryNameRes: Int? = null
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun WeatherSourcesScreen(navController: NavController) {
|
||||
val uriHandler = LocalUriHandler.current
|
||||
|
||||
val alertSources =
|
||||
AlertSource.entries.filter { it.displayName !in WeatherSource.entries.map { source -> source.displayName } }
|
||||
.filter { it != AlertSource.NONE }
|
||||
.map {
|
||||
WeatherSourceScreen(
|
||||
displayName = it.displayName,
|
||||
fullName = it.fullName,
|
||||
displayLink = it.displayLink,
|
||||
)
|
||||
}
|
||||
|
||||
val weatherSources = WeatherSource.entries.map {
|
||||
WeatherSourceScreen(
|
||||
displayName = it.displayName,
|
||||
fullName = it.fullName,
|
||||
displayLink = it.displayLink,
|
||||
countryNameRes = it.countryNameRes
|
||||
)
|
||||
}
|
||||
|
||||
val sources = weatherSources + alertSources
|
||||
val sources = Source.entries
|
||||
|
||||
LargeTopBarScaffold(
|
||||
title = stringResource(R.string.weather_sources),
|
||||
|
||||
+68
-105
@@ -1,7 +1,6 @@
|
||||
package com.pranshulgg.weather_master_app.feature.shared
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.lifecycle.ViewModel
|
||||
@@ -11,24 +10,19 @@ import com.pranshulgg.weather_master_app.core.model.domain.location.Location
|
||||
import com.pranshulgg.weather_master_app.core.model.domain.toAppException
|
||||
import com.pranshulgg.weather_master_app.core.model.domain.toMessageRes
|
||||
import com.pranshulgg.weather_master_app.core.model.domain.weather.WeatherBlock
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.AirQualitySource
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.AlertSource
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.WeatherSource
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.Source
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.isGlobal
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.isSourceSupportedFor
|
||||
import com.pranshulgg.weather_master_app.core.model.weather.WeatherResult
|
||||
import com.pranshulgg.weather_master_app.core.model.weather.airquality.AirQualityResult
|
||||
import com.pranshulgg.weather_master_app.core.model.weather.alerts.AlertResult
|
||||
import com.pranshulgg.weather_master_app.core.model.weather.openmeteo.OpenMeteoModel
|
||||
import com.pranshulgg.weather_master_app.core.network.sources.airquality.openmeteo.OpenMeteoAqiRepository
|
||||
import com.pranshulgg.weather_master_app.core.ui.snackbar.SnackbarManager
|
||||
import com.pranshulgg.weather_master_app.data.provider.AirQualityRepositoryProvider
|
||||
import com.pranshulgg.weather_master_app.data.provider.AlertsRepositoryProvider
|
||||
import com.pranshulgg.weather_master_app.data.provider.WeatherRepositoryProvider
|
||||
import com.pranshulgg.weather_master_app.data.repository.LocationsRepository
|
||||
import com.pranshulgg.weather_master_app.data.repository.WeatherBlocksRepository
|
||||
import com.pranshulgg.weather_master_app.data.repository.WeatherDataReconcilerRepository
|
||||
import com.pranshulgg.weather_master_app.data.repository.WeatherUnitsRepository
|
||||
import com.pranshulgg.weather_master_app.data.repository.data.SourceDataRepository
|
||||
import com.pranshulgg.weather_master_app.data.worker.WeatherUpdateScheduler
|
||||
import com.pranshulgg.weather_master_app.feature.main.MainScreenWeatherUiState
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
@@ -43,19 +37,15 @@ import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
import kotlin.coroutines.cancellation.CancellationException
|
||||
import kotlin.time.Duration.Companion.minutes
|
||||
|
||||
@HiltViewModel
|
||||
class WeatherViewModel @Inject constructor(
|
||||
private val repo: WeatherRepositoryProvider,
|
||||
private val locationsRepo: LocationsRepository,
|
||||
appWeatherUnitsRepo: WeatherUnitsRepository,
|
||||
private val weatherBlocksRepository: WeatherBlocksRepository,
|
||||
private val openMeteoAqiRepository: OpenMeteoAqiRepository,
|
||||
private val weatherDataReconcilerRepository: WeatherDataReconcilerRepository,
|
||||
private val airQualityRepositoryProvider: AirQualityRepositoryProvider,
|
||||
private val alertRepositoryProvider: AlertsRepositoryProvider,
|
||||
private val sourceDataRepository: SourceDataRepository,
|
||||
@ApplicationContext private val context: Context
|
||||
) : ViewModel() {
|
||||
|
||||
@@ -116,7 +106,7 @@ class WeatherViewModel @Inject constructor(
|
||||
|
||||
fun getWeather(
|
||||
location: Location,
|
||||
source: WeatherSource,
|
||||
source: Source,
|
||||
isManualRefresh: Boolean = false,
|
||||
isForceRefresh: Boolean = false,
|
||||
isForceRefreshForAirQuality: Boolean = false,
|
||||
@@ -125,7 +115,11 @@ class WeatherViewModel @Inject constructor(
|
||||
setLoading(true)
|
||||
weatherJob?.cancel()
|
||||
val startTime = System.currentTimeMillis()
|
||||
_uiState.value = _uiState.value.copy(isError = false, isUnsupportedSource = false)
|
||||
_uiState.value = _uiState.value.copy(
|
||||
isError = false,
|
||||
isUnsupportedSource = false,
|
||||
isAirQualityLoading = true
|
||||
)
|
||||
|
||||
|
||||
weatherJob = viewModelScope.launch {
|
||||
@@ -151,17 +145,51 @@ class WeatherViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
// Run separately
|
||||
if (!_uiState.value.isError) {
|
||||
launch {
|
||||
handleAirQuality(effectiveLocation, isManualRefresh, effectiveForceRefreshForAirQuality)
|
||||
}
|
||||
launch {
|
||||
handleAlerts(effectiveLocation, isManualRefresh, effectiveForceRefreshForAlerts)
|
||||
}
|
||||
}
|
||||
|
||||
handleWeatherData(source, effectiveLocation, isManualRefresh, effectiveForceRefresh)
|
||||
sourceDataRepository.getData(
|
||||
location = effectiveLocation,
|
||||
isManualRefresh = isManualRefresh,
|
||||
isForceRefresh = effectiveForceRefresh,
|
||||
isForceRefreshForAirQuality = effectiveForceRefreshForAirQuality,
|
||||
isForceRefreshForAlerts = effectiveForceRefreshForAlerts,
|
||||
onWeather = { result ->
|
||||
handleWeatherData(result, effectiveLocation)
|
||||
},
|
||||
onAlerts = { result ->
|
||||
handleAlerts(result)
|
||||
},
|
||||
onAirQuality = { result ->
|
||||
handleAirQuality(result)
|
||||
},
|
||||
)
|
||||
// handleWeatherData(source, effectiveLocation, isManualRefresh, effectiveForceRefresh)
|
||||
|
||||
// Run separately
|
||||
// if (!_uiState.value.isError) {
|
||||
// launch {
|
||||
// handleAirQuality(
|
||||
// effectiveLocation,
|
||||
// isManualRefresh,
|
||||
// effectiveForceRefreshForAirQuality
|
||||
// )
|
||||
// }
|
||||
//
|
||||
|
||||
// Only run separately if the alert source is a different API of its own
|
||||
// If not, then run after the weather has fetched
|
||||
// if (!source.providesAlerts && effectiveLocation.alertSource.name != source.name) {
|
||||
// launch {
|
||||
// handleAlerts(
|
||||
// effectiveLocation,
|
||||
// isManualRefresh,
|
||||
// effectiveForceRefreshForAlerts
|
||||
// )
|
||||
// }
|
||||
// } else {
|
||||
// handleAlertsFromWeatherSource(effectiveLocation)
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
val elapsed = System.currentTimeMillis() - startTime
|
||||
val minLoadingTime = 1000L // 1s
|
||||
@@ -203,9 +231,9 @@ class WeatherViewModel @Inject constructor(
|
||||
|
||||
fun handleSourceChangeForWeather(
|
||||
location: Location,
|
||||
source: WeatherSource,
|
||||
airQualitySource: AirQualitySource,
|
||||
alertSource: AlertSource,
|
||||
source: Source,
|
||||
airQualitySource: Source,
|
||||
alertSource: Source,
|
||||
openMeteoModel: OpenMeteoModel
|
||||
) {
|
||||
val updatedLocation = location.copy(
|
||||
@@ -227,32 +255,11 @@ class WeatherViewModel @Inject constructor(
|
||||
val allowForceRefreshForAirQuality = location.airQualitySource != airQualitySource
|
||||
val allowForceRefreshForAlerts = location.alertSource != alertSource
|
||||
|
||||
|
||||
if (allowForceRefreshForWeather) {
|
||||
weatherDataReconcilerRepository.cleanUpStaleData(
|
||||
location.source,
|
||||
location.id,
|
||||
airQualitySource,
|
||||
alertSource
|
||||
)
|
||||
}
|
||||
if (allowForceRefreshForAirQuality) {
|
||||
weatherDataReconcilerRepository.cleanUpStaleAirQualityData(
|
||||
location.id,
|
||||
source,
|
||||
alertSource
|
||||
)
|
||||
}
|
||||
if (allowForceRefreshForAlerts) {
|
||||
weatherDataReconcilerRepository.cleanUpStaleAlertsData(
|
||||
location.id,
|
||||
source,
|
||||
airQualitySource
|
||||
)
|
||||
}
|
||||
_uiState.value = _uiState.value.copy(
|
||||
activeLocation = updatedLocation
|
||||
weatherDataReconcilerRepository.reconcileSourceChange(
|
||||
previous = location,
|
||||
updated = updatedLocation
|
||||
)
|
||||
_uiState.value = _uiState.value.copy(activeLocation = updatedLocation)
|
||||
getWeather(
|
||||
updatedLocation,
|
||||
source,
|
||||
@@ -260,8 +267,6 @@ class WeatherViewModel @Inject constructor(
|
||||
isForceRefreshForAirQuality = allowForceRefreshForAirQuality,
|
||||
isForceRefreshForAlerts = allowForceRefreshForAlerts
|
||||
)
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -297,18 +302,10 @@ class WeatherViewModel @Inject constructor(
|
||||
return locationsRepo.updateDeviceLocationPosition()
|
||||
}
|
||||
|
||||
private suspend fun handleWeatherData(
|
||||
source: WeatherSource,
|
||||
location: Location,
|
||||
isManualRefresh: Boolean,
|
||||
isForceRefresh: Boolean
|
||||
) {
|
||||
|
||||
val repo = repo.getRepository(source)
|
||||
private suspend fun handleWeatherData(result: WeatherResult, location: Location) {
|
||||
|
||||
|
||||
when (val result = repo.getWeather(location, isManualRefresh, isForceRefresh)) {
|
||||
|
||||
when (result) {
|
||||
|
||||
is WeatherResult.Success -> {
|
||||
_uiState.value = _uiState.value.copy(weather = result.weather, isInitialized = true)
|
||||
@@ -319,8 +316,6 @@ class WeatherViewModel @Inject constructor(
|
||||
val appExpectation = result.exception.toAppException()
|
||||
SnackbarManager.show(appExpectation.toMessageRes())
|
||||
|
||||
|
||||
|
||||
_uiState.value = _uiState.value.copy(
|
||||
isError = true,
|
||||
weather = result.cacheWeather,
|
||||
@@ -345,27 +340,11 @@ class WeatherViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun handleAirQuality(
|
||||
location: Location,
|
||||
isManualRefresh: Boolean,
|
||||
isForceRefresh: Boolean
|
||||
private fun handleAirQuality(
|
||||
result: AirQualityResult
|
||||
) {
|
||||
|
||||
if (_uiState.value.isAirQualityLoading) return
|
||||
|
||||
val repoAir = airQualityRepositoryProvider.getRepository(location.airQualitySource)
|
||||
|
||||
|
||||
if (repoAir == null) {
|
||||
_uiState.value = _uiState.value.copy(airQuality = null, isAirQualityLoading = false)
|
||||
return
|
||||
}
|
||||
|
||||
_uiState.value = _uiState.value.copy(isAirQualityLoading = true)
|
||||
|
||||
|
||||
|
||||
when (val result = repoAir.getAirQuality(location, isManualRefresh, isForceRefresh)) {
|
||||
when (result) {
|
||||
is AirQualityResult.Success -> {
|
||||
_uiState.value =
|
||||
_uiState.value.copy(
|
||||
@@ -387,30 +366,14 @@ class WeatherViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
|
||||
private suspend fun handleAlerts(
|
||||
location: Location,
|
||||
isManualRefresh: Boolean,
|
||||
isForceRefresh: Boolean
|
||||
) {
|
||||
private fun handleAlerts(result: AlertResult) {
|
||||
|
||||
|
||||
val repoAlert = alertRepositoryProvider.getRepository(location.alertSource)
|
||||
|
||||
|
||||
if (repoAlert == null) {
|
||||
_uiState.value = _uiState.value.copy(alerts = emptyList())
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
when (val result = repoAlert.getAlerts(location, isManualRefresh, isForceRefresh)) {
|
||||
when (result) {
|
||||
is AlertResult.Success -> {
|
||||
_uiState.value =
|
||||
_uiState.value.copy(
|
||||
alerts = result.alerts
|
||||
)
|
||||
|
||||
|
||||
}
|
||||
|
||||
// Fail silently, we just won't show the Air quality in the UI
|
||||
@@ -426,7 +389,7 @@ class WeatherViewModel @Inject constructor(
|
||||
|
||||
fun startAutoRefresh(
|
||||
location: Location,
|
||||
source: WeatherSource
|
||||
source: Source
|
||||
) {
|
||||
|
||||
if (autoRefreshJob?.isActive == true) return
|
||||
|
||||
+30
-46
@@ -1,40 +1,25 @@
|
||||
package com.pranshulgg.weather_master_app.feature.shared.ui
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.IconButtonDefaults
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.SheetState
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.pranshulgg.weather_master_app.R
|
||||
import com.pranshulgg.weather_master_app.core.model.domain.weather.ApiKey
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.WeatherSource
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.getWeatherSourcesForCountry
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.getWeatherSourcesGlobal
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.Source
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.getSourcesForCountry
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.getSourcesGlobal
|
||||
import com.pranshulgg.weather_master_app.core.ui.components.ActionBottomSheet
|
||||
import com.pranshulgg.weather_master_app.core.ui.components.DialogBasic
|
||||
import com.pranshulgg.weather_master_app.core.ui.components.Gap
|
||||
import com.pranshulgg.weather_master_app.core.ui.components.RadioRow
|
||||
import com.pranshulgg.weather_master_app.core.ui.components.SettingSection
|
||||
import com.pranshulgg.weather_master_app.core.ui.components.SettingTile
|
||||
import com.pranshulgg.weather_master_app.core.ui.components.SettingsTileIcon
|
||||
import com.pranshulgg.weather_master_app.core.ui.components.Symbol
|
||||
import com.pranshulgg.weather_master_app.core.ui.theme.ShapeRadius
|
||||
import com.pranshulgg.weather_master_app.feature.shared.components.ChangelogContent
|
||||
|
||||
object SharedBottomSheet {
|
||||
@@ -44,18 +29,18 @@ object SharedBottomSheet {
|
||||
countryCode: String?,
|
||||
show: Boolean,
|
||||
sheetState: SheetState,
|
||||
selectedSource: WeatherSource = WeatherSource.OPEN_METEO,
|
||||
selectedSource: Source = Source.OPEN_METEO,
|
||||
isEditing: Boolean = false,
|
||||
onSave: (WeatherSource) -> Unit,
|
||||
onSave: (Source) -> Unit,
|
||||
onDismiss: () -> Unit,
|
||||
onClickApiConfig: () -> Unit,
|
||||
apiKeys: List<ApiKey>,
|
||||
) {
|
||||
if (show) {
|
||||
val recommendedSources = getWeatherSourcesForCountry(countryCode?.uppercase())
|
||||
val globalSources = getWeatherSourcesGlobal()
|
||||
val recommendedSources = getSourcesForCountry(countryCode?.uppercase())
|
||||
val globalSources = getSourcesGlobal()
|
||||
|
||||
val isApiKeyAvailable: (WeatherSource) -> Boolean = { source ->
|
||||
val isApiKeyAvailable: (Source) -> Boolean = { source ->
|
||||
if (source.requiresUserApiKey) {
|
||||
apiKeys.isNotEmpty()
|
||||
&& apiKeys
|
||||
@@ -78,6 +63,22 @@ object SharedBottomSheet {
|
||||
)
|
||||
}
|
||||
|
||||
val handeSelection: (Source) -> Unit = {
|
||||
if (!it.requiresUserApiKey) {
|
||||
currentSelectedSource = it
|
||||
} else if (!isApiKeyAvailable(it)) {
|
||||
onClickApiConfig()
|
||||
} else {
|
||||
currentSelectedSource = it
|
||||
}
|
||||
}
|
||||
|
||||
val description: (Source) -> String? = {
|
||||
if (it.requiresUserApiKey &&
|
||||
!isApiKeyAvailable(it)
|
||||
) "Requires API key" else null
|
||||
}
|
||||
|
||||
|
||||
|
||||
ActionBottomSheet(
|
||||
@@ -106,20 +107,11 @@ object SharedBottomSheet {
|
||||
)
|
||||
},
|
||||
title = source.displayName + countryString,
|
||||
description = if (source.requiresUserApiKey && !isApiKeyAvailable(
|
||||
source
|
||||
)
|
||||
) "Requires API key" else null,
|
||||
description = description(source),
|
||||
colorDesc = MaterialTheme.colorScheme.error,
|
||||
selected = isSelected,
|
||||
onClick = {
|
||||
if (!source.requiresUserApiKey) {
|
||||
currentSelectedSource = source
|
||||
} else if (!isApiKeyAvailable(source)) {
|
||||
onClickApiConfig()
|
||||
} else {
|
||||
currentSelectedSource = source
|
||||
}
|
||||
handeSelection(source)
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -143,8 +135,9 @@ object SharedBottomSheet {
|
||||
},
|
||||
title = source.displayName + countryString,
|
||||
selected = isSelected,
|
||||
description = description(source),
|
||||
onClick = {
|
||||
currentSelectedSource = source
|
||||
handeSelection(source)
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -169,19 +162,10 @@ object SharedBottomSheet {
|
||||
},
|
||||
title = source.displayName + countryString,
|
||||
selected = isSelected,
|
||||
description = if (source.requiresUserApiKey && !isApiKeyAvailable(
|
||||
source
|
||||
)
|
||||
) "Requires API key" else null,
|
||||
description = description(source),
|
||||
colorDesc = MaterialTheme.colorScheme.error,
|
||||
onClick = {
|
||||
if (!source.requiresUserApiKey) {
|
||||
currentSelectedSource = source
|
||||
} else if (!isApiKeyAvailable(source)) {
|
||||
onClickApiConfig()
|
||||
} else {
|
||||
currentSelectedSource = source
|
||||
}
|
||||
handeSelection(source)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user