mirror of
https://github.com/breezy-weather/breezy-weather.git
synced 2025-10-18 23:43:40 +00:00
Improve reverse geocoding of countries
This commit is contained in:
parent
f1e51b6219
commit
6e284e505b
10 changed files with 255 additions and 144 deletions
|
@ -178,6 +178,8 @@ class RefreshHelper @Inject constructor(
|
|||
* - Apply updated coordinates
|
||||
* - Reverse geocoding (if current location)
|
||||
* On non-current location, just returns the location
|
||||
*
|
||||
* TODO: Remove redundancy with default reverse geocoding calls
|
||||
*/
|
||||
suspend fun getLocation(
|
||||
context: Context,
|
||||
|
@ -189,6 +191,8 @@ class RefreshHelper @Inject constructor(
|
|||
return LocationResult(location, emptyList())
|
||||
}
|
||||
|
||||
var needsCountryCodeRefresh = false
|
||||
|
||||
val currentErrors = mutableListOf<RefreshError>()
|
||||
|
||||
// STEP 1 - Update coordinates if current position
|
||||
|
@ -271,7 +275,8 @@ class RefreshHelper @Inject constructor(
|
|||
)
|
||||
location
|
||||
} else {
|
||||
locationRepository.update(it)
|
||||
needsCountryCodeRefresh = !location.reverseGeocodingSource.isNullOrEmpty() &&
|
||||
!location.reverseGeocodingSource.equals(BuildConfig.DEFAULT_GEOCODING_SOURCE)
|
||||
it
|
||||
}
|
||||
}
|
||||
|
@ -302,9 +307,7 @@ class RefreshHelper @Inject constructor(
|
|||
).copy(
|
||||
// We failed to refresh, so retry reverse geocoding next time
|
||||
needsGeocodeRefresh = true
|
||||
).also {
|
||||
locationRepository.update(it)
|
||||
}
|
||||
)
|
||||
} catch (_: Throwable) {
|
||||
/**
|
||||
* Returns the original location
|
||||
|
@ -330,13 +333,25 @@ class RefreshHelper @Inject constructor(
|
|||
locationWithUpdatedCoordinates // Same as "location"
|
||||
}
|
||||
|
||||
// STEP 3 - Add timezone if missing
|
||||
// STEP 3 - Validate ambiguous ISO 3166 codes
|
||||
val locationInfoFromDefaultSource = if (needsCountryCodeRefresh) {
|
||||
getLocationWithUnambiguousCountryCode(locationGeocoded, context)
|
||||
} else {
|
||||
locationGeocoded
|
||||
}
|
||||
|
||||
// STEP 4 - Add timezone if missing
|
||||
val locationWithTimeZone = if (locationGeocoded.isTimeZoneInvalid) {
|
||||
locationGeocoded.copy(
|
||||
locationInfoFromDefaultSource.copy(
|
||||
timeZone = getTimeZoneForLocation(context, locationGeocoded)
|
||||
)
|
||||
} else {
|
||||
locationGeocoded
|
||||
locationInfoFromDefaultSource
|
||||
}
|
||||
|
||||
// STEP 5 - If there was any change, update in database
|
||||
if (location != locationWithTimeZone) {
|
||||
locationRepository.update(locationWithTimeZone)
|
||||
}
|
||||
|
||||
return LocationResult(locationWithTimeZone, currentErrors)
|
||||
|
@ -377,6 +392,35 @@ class RefreshHelper @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
suspend fun getLocationWithUnambiguousCountryCode(
|
||||
location: Location,
|
||||
context: Context,
|
||||
): Location {
|
||||
return if (ambigousCountryCodes.any { cc -> location.countryCode.equals(cc, ignoreCase = true) }) {
|
||||
try {
|
||||
// Getting the address for this from the fallback reverse geocoding source
|
||||
requestReverseGeocoding(
|
||||
sourceManager.getReverseGeocodingSourceOrDefault(BuildConfig.DEFAULT_GEOCODING_SOURCE),
|
||||
location,
|
||||
context
|
||||
).let {
|
||||
if (!it.countryCode.equals(location.countryCode, ignoreCase = true)) {
|
||||
location.copy(
|
||||
countryCode = it.countryCode,
|
||||
country = it.country
|
||||
)
|
||||
} else {
|
||||
location
|
||||
}
|
||||
}
|
||||
} catch (_: Throwable) {
|
||||
location
|
||||
}
|
||||
} else {
|
||||
location
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun updateLocation(location: Location, oldFormattedId: String? = null) {
|
||||
locationRepository.update(location, oldFormattedId)
|
||||
}
|
||||
|
@ -1327,5 +1371,28 @@ class RefreshHelper @Inject constructor(
|
|||
|
||||
const val CACHING_DISTANCE_LIMIT = 5000 // 5 km
|
||||
const val REVERSE_GEOCODING_DISTANCE_LIMIT = 50000 // 50 km
|
||||
|
||||
/**
|
||||
* For technical reasons, we need to better identify each territory
|
||||
* Crimea is not included to let each location search/address lookup source resolves it the way they want
|
||||
* and we will resolve the timezone as Europe/Simferopol whether identified as UA or RU
|
||||
*/
|
||||
private val ambigousCountryCodes = arrayOf(
|
||||
"AR", // Claims: AQ
|
||||
"AU", // Territories: CX, CC, HM (uninhabited), NF. Claims: AQ
|
||||
"CL", // Claims: AQ
|
||||
"CN", // Territories: HK, MO. Claims: TW
|
||||
"DK", // Territories: FO, GL
|
||||
"FI", // Territories: AX
|
||||
"FR", // Territories: GF, PF, TF (uninhabited), GP, MQ, YT, NC, RE, BL, MF, PM, WF. Claims: AQ
|
||||
"GB", // Territories: AI, BM, IO, KY, FK, GI, GG, IM, JE, MS, PN, SH, GS (uninhabited), TC, VG. Claims: AQ
|
||||
"IL", // Claims: PS
|
||||
"MA", // Claims: EH
|
||||
"NL", // Territories: AW, BQ, CW, SX
|
||||
"NO", // Territories: BV, SJ. Claims: AQ
|
||||
"NZ", // Territories: TK. Associated states: CK, NU. Claims: AQ
|
||||
"RS", // Claims: XK
|
||||
"US" // Territories: AS, GU, MP, PR, UM (uninhabited), VI
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,16 +21,18 @@ import breezyweather.domain.location.model.LocationAddressInfo
|
|||
import breezyweather.domain.source.SourceFeature
|
||||
import com.google.maps.android.PolyUtil
|
||||
import com.google.maps.android.SphericalUtil
|
||||
import com.google.maps.android.data.geojson.GeoJsonFeature
|
||||
import com.google.maps.android.data.Feature
|
||||
import com.google.maps.android.data.geojson.GeoJsonMultiPolygon
|
||||
import com.google.maps.android.data.geojson.GeoJsonParser
|
||||
import com.google.maps.android.data.geojson.GeoJsonPoint
|
||||
import com.google.maps.android.data.geojson.GeoJsonPolygon
|
||||
import com.google.maps.android.model.LatLng
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.reactivex.rxjava3.core.Observable
|
||||
import org.breezyweather.R
|
||||
import org.breezyweather.common.extensions.codeForNaturalEarth
|
||||
import org.breezyweather.common.extensions.currentLocale
|
||||
import org.breezyweather.common.extensions.getCountryName
|
||||
import org.breezyweather.common.source.ReverseGeocodingSource
|
||||
import org.breezyweather.common.utils.helpers.LogHelper
|
||||
import org.breezyweather.sources.RefreshHelper
|
||||
|
@ -50,13 +52,21 @@ import javax.inject.Inject
|
|||
*
|
||||
* https://mapshaper.org/ was used to convert to GeoJSON
|
||||
* TODO: It would be best to make our own converter so that we can exclude features we don’t want and
|
||||
* make the geojson file more lightweight
|
||||
* make the geojson file more lightweight
|
||||
*/
|
||||
class NaturalEarthService @Inject constructor() : ReverseGeocodingSource {
|
||||
class NaturalEarthService @Inject constructor(
|
||||
@ApplicationContext context: Context,
|
||||
) : ReverseGeocodingSource {
|
||||
|
||||
override val id = "naturalearth"
|
||||
override val name = "Natural Earth"
|
||||
|
||||
private val geoJsonParser: GeoJsonParser by lazy {
|
||||
val text = context.resources.openRawResource(R.raw.ne_50m_admin_0_countries)
|
||||
.bufferedReader().use { it.readText() }
|
||||
GeoJsonParser(JSONObject(text))
|
||||
}
|
||||
|
||||
override val supportedFeatures = mapOf(
|
||||
SourceFeature.REVERSE_GEOCODING to name
|
||||
)
|
||||
|
@ -68,13 +78,7 @@ class NaturalEarthService @Inject constructor() : ReverseGeocodingSource {
|
|||
): Observable<List<LocationAddressInfo>> {
|
||||
val languageCode = context.currentLocale.codeForNaturalEarth
|
||||
|
||||
// Countries
|
||||
val matchingCountries = getMatchingFeaturesForLocation(
|
||||
context,
|
||||
R.raw.ne_50m_admin_0_countries,
|
||||
latitude,
|
||||
longitude
|
||||
)
|
||||
val matchingCountries = geoJsonParser.features.filter { isMatchingFeature(it, latitude, longitude) }
|
||||
if (matchingCountries.size != 1) {
|
||||
LogHelper.log(
|
||||
msg = "[NaturalEarthService] Reverse geocoding skipped: ${matchingCountries.size} matching results"
|
||||
|
@ -82,44 +86,39 @@ class NaturalEarthService @Inject constructor() : ReverseGeocodingSource {
|
|||
return Observable.just(emptyList())
|
||||
}
|
||||
|
||||
val countryCode = matchingCountries[0].getProperty("ISO_A2") ?: ""
|
||||
return Observable.just(
|
||||
listOf(
|
||||
LocationAddressInfo(
|
||||
country = matchingCountries[0].getProperty("NAME_$languageCode")
|
||||
country = countryCode.takeIf { it.isNotEmpty() }
|
||||
?.let { context.currentLocale.getCountryName(it) }
|
||||
?: matchingCountries[0].getProperty("NAME_$languageCode")
|
||||
?: matchingCountries[0].getProperty("NAME_LONG"),
|
||||
countryCode = matchingCountries[0].getProperty("ISO_A2")
|
||||
?: ""
|
||||
countryCode = countryCode
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun getMatchingFeaturesForLocation(
|
||||
context: Context,
|
||||
file: Int,
|
||||
private fun isMatchingFeature(
|
||||
feature: Feature,
|
||||
latitude: Double,
|
||||
longitude: Double,
|
||||
): List<GeoJsonFeature> {
|
||||
val text = context.resources.openRawResource(file)
|
||||
.bufferedReader().use { it.readText() }
|
||||
val geoJsonParser = GeoJsonParser(JSONObject(text))
|
||||
|
||||
return geoJsonParser.features.filter { feature ->
|
||||
when (feature.geometry) {
|
||||
is GeoJsonPolygon -> (feature.geometry as GeoJsonPolygon).coordinates.any { polygon ->
|
||||
): Boolean {
|
||||
return when (feature.geometry) {
|
||||
is GeoJsonPolygon -> (feature.geometry as GeoJsonPolygon).coordinates.any { polygon ->
|
||||
PolyUtil.containsLocation(latitude, longitude, polygon, true)
|
||||
}
|
||||
is GeoJsonMultiPolygon -> (feature.geometry as GeoJsonMultiPolygon).polygons.any {
|
||||
it.coordinates.any { polygon ->
|
||||
PolyUtil.containsLocation(latitude, longitude, polygon, true)
|
||||
}
|
||||
is GeoJsonMultiPolygon -> (feature.geometry as GeoJsonMultiPolygon).polygons.any {
|
||||
it.coordinates.any { polygon ->
|
||||
PolyUtil.containsLocation(latitude, longitude, polygon, true)
|
||||
}
|
||||
}
|
||||
is GeoJsonPoint -> SphericalUtil.computeDistanceBetween(
|
||||
LatLng(latitude, longitude),
|
||||
(feature.geometry as GeoJsonPoint).coordinates
|
||||
) < RefreshHelper.REVERSE_GEOCODING_DISTANCE_LIMIT
|
||||
else -> false
|
||||
}
|
||||
is GeoJsonPoint -> SphericalUtil.computeDistanceBetween(
|
||||
LatLng(latitude, longitude),
|
||||
(feature.geometry as GeoJsonPoint).coordinates
|
||||
) < RefreshHelper.REVERSE_GEOCODING_DISTANCE_LIMIT
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -155,7 +155,7 @@ class MainActivity : BreezyActivity(), HomeFragment.Callback, ManagementFragment
|
|||
if (viewModel.locationExists(location)) {
|
||||
SnackbarHelper.showSnackbar(getString(R.string.location_message_already_exists))
|
||||
} else {
|
||||
viewModel.addLocation(location, null)
|
||||
viewModel.addLocation(location, null, this)
|
||||
SnackbarHelper.showSnackbar(getString(R.string.location_message_added))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -481,8 +481,7 @@ class MainActivityViewModel @Inject constructor(
|
|||
fun addLocation(
|
||||
location: Location,
|
||||
index: Int? = null,
|
||||
context: Context? = null, // Needed if adding timezone
|
||||
addTimeZoneIfMissing: Boolean = false,
|
||||
context: Context? = null, // Needed for timezone
|
||||
): Boolean {
|
||||
// do not add an existing location.
|
||||
if (validLocationList.value.firstOrNull { it.formattedId == location.formattedId } != null) {
|
||||
|
@ -491,7 +490,7 @@ class MainActivityViewModel @Inject constructor(
|
|||
|
||||
_locationListLoading.value = true
|
||||
|
||||
val locationToAdd = if (addTimeZoneIfMissing && context != null && location.isTimeZoneInvalid) {
|
||||
val locationWithValidTimeZone = if (context != null && location.isTimeZoneInvalid) {
|
||||
location.copy(
|
||||
timeZone = runBlocking {
|
||||
refreshHelper.getTimeZoneForLocation(context, location)
|
||||
|
@ -502,7 +501,7 @@ class MainActivityViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
val valid = validLocationList.value.toMutableList()
|
||||
valid.add(index ?: valid.size, locationToAdd)
|
||||
valid.add(index ?: valid.size, locationWithValidTimeZone)
|
||||
|
||||
updateInnerData(valid)
|
||||
writeLocationList(locationList = valid)
|
||||
|
|
|
@ -472,7 +472,7 @@ open class ManagementFragment : MainModuleFragment(), TouchReactor {
|
|||
if (viewModel.locationExists(newLocation)) {
|
||||
SnackbarHelper.showSnackbar(getString(R.string.location_message_already_exists))
|
||||
} else {
|
||||
viewModel.addLocation(newLocation, null)
|
||||
viewModel.addLocation(newLocation, null, requireContext())
|
||||
SnackbarHelper.showSnackbar(getString(R.string.location_message_added))
|
||||
}
|
||||
}
|
||||
|
@ -495,7 +495,7 @@ open class ManagementFragment : MainModuleFragment(), TouchReactor {
|
|||
if (viewModel.locationExists(addedLocation)) {
|
||||
SnackbarHelper.showSnackbar(getString(R.string.location_message_already_exists))
|
||||
} else {
|
||||
viewModel.addLocation(addedLocation, null)
|
||||
viewModel.addLocation(addedLocation, null, requireContext())
|
||||
SnackbarHelper.showSnackbar(getString(R.string.location_message_added))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,19 +66,15 @@ import androidx.compose.ui.unit.dp
|
|||
import androidx.core.app.ActivityCompat
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import breezyweather.domain.location.model.Location
|
||||
import breezyweather.domain.source.SourceFeature
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import org.breezyweather.BuildConfig
|
||||
import org.breezyweather.R
|
||||
import org.breezyweather.common.basic.BreezyActivity
|
||||
import org.breezyweather.common.extensions.inputMethodManager
|
||||
import org.breezyweather.domain.location.model.applyDefaultPreset
|
||||
import org.breezyweather.domain.location.model.getPlace
|
||||
import org.breezyweather.domain.settings.SettingsManager
|
||||
import org.breezyweather.sources.SourceManager
|
||||
import org.breezyweather.sources.getConfiguredLocationSearchSources
|
||||
import org.breezyweather.sources.getLocationSearchSourceOrDefault
|
||||
import org.breezyweather.sources.getWeatherSource
|
||||
import org.breezyweather.ui.common.composables.AlertDialogNoPadding
|
||||
import org.breezyweather.ui.common.composables.SecondarySourcesPreference
|
||||
import org.breezyweather.ui.common.widgets.Material3Scaffold
|
||||
|
@ -110,8 +106,9 @@ class SearchActivity : BreezyActivity() {
|
|||
private fun ContentView() {
|
||||
val context = LocalContext.current
|
||||
val dialogLocationSearchSourceOpenState = rememberSaveable { mutableStateOf(false) }
|
||||
val dialogLocationSourcesOpenState = rememberSaveable { mutableStateOf(false) }
|
||||
var selectedLocation: Location? by rememberSaveable { mutableStateOf(null) }
|
||||
val dialogLocationSourcesOpenState = viewModel.dialogLocationSourcesOpenState.collectAsState()
|
||||
val selectedLocation = viewModel.selectedLocation.collectAsState()
|
||||
val isLoading = viewModel.isLoading.collectAsState()
|
||||
var text by rememberSaveable { mutableStateOf("") }
|
||||
var latestTextSearch by rememberSaveable { mutableStateOf("") }
|
||||
val locationSearchSourceState = viewModel.locationSearchSource.collectAsState()
|
||||
|
@ -184,7 +181,7 @@ class SearchActivity : BreezyActivity() {
|
|||
)
|
||||
HorizontalDivider(color = SearchBarDefaults.colors().dividerColor)
|
||||
val listResourceState = viewModel.listResource.collectAsState()
|
||||
if (listResourceState.value.second == LoadableLocationStatus.LOADING) {
|
||||
if (listResourceState.value.second == LoadableLocationStatus.LOADING || isLoading.value) {
|
||||
LinearProgressIndicator(modifier = Modifier.fillMaxWidth())
|
||||
}
|
||||
if (listResourceState.value.first.isNotEmpty()) {
|
||||
|
@ -199,101 +196,20 @@ class SearchActivity : BreezyActivity() {
|
|||
headlineContent = { Text(location.getPlace(context)) },
|
||||
supportingContent = { Text(location.administrationLevels()) },
|
||||
modifier = Modifier.clickable {
|
||||
val defaultSource = SettingsManager.getInstance(context).defaultForecastSource
|
||||
|
||||
selectedLocation = when (defaultSource) {
|
||||
"auto" -> location.applyDefaultPreset(sourceManager)
|
||||
else -> {
|
||||
val source = sourceManager.getWeatherSource(defaultSource)
|
||||
if (source == null) {
|
||||
location.applyDefaultPreset(sourceManager)
|
||||
} else {
|
||||
location.copy(
|
||||
forecastSource = source.id,
|
||||
currentSource = if (SourceFeature.CURRENT in
|
||||
source.supportedFeatures &&
|
||||
source.isFeatureSupportedForLocation(
|
||||
location,
|
||||
SourceFeature.CURRENT
|
||||
)
|
||||
) {
|
||||
source.id
|
||||
} else {
|
||||
null
|
||||
},
|
||||
airQualitySource = if (SourceFeature.AIR_QUALITY in
|
||||
source.supportedFeatures &&
|
||||
source.isFeatureSupportedForLocation(
|
||||
location,
|
||||
SourceFeature.AIR_QUALITY
|
||||
)
|
||||
) {
|
||||
source.id
|
||||
} else {
|
||||
null
|
||||
},
|
||||
pollenSource = if (SourceFeature.POLLEN in
|
||||
source.supportedFeatures &&
|
||||
source.isFeatureSupportedForLocation(
|
||||
location,
|
||||
SourceFeature.POLLEN
|
||||
)
|
||||
) {
|
||||
source.id
|
||||
} else {
|
||||
null
|
||||
},
|
||||
minutelySource = if (SourceFeature.MINUTELY in
|
||||
source.supportedFeatures &&
|
||||
source.isFeatureSupportedForLocation(
|
||||
location,
|
||||
SourceFeature.MINUTELY
|
||||
)
|
||||
) {
|
||||
source.id
|
||||
} else {
|
||||
null
|
||||
},
|
||||
alertSource = if (SourceFeature.ALERT in
|
||||
source.supportedFeatures &&
|
||||
source.isFeatureSupportedForLocation(
|
||||
location,
|
||||
SourceFeature.ALERT
|
||||
)
|
||||
) {
|
||||
source.id
|
||||
} else {
|
||||
null
|
||||
},
|
||||
normalsSource = if (SourceFeature.NORMALS in
|
||||
source.supportedFeatures &&
|
||||
source.isFeatureSupportedForLocation(
|
||||
location,
|
||||
SourceFeature.NORMALS
|
||||
)
|
||||
) {
|
||||
source.id
|
||||
} else {
|
||||
null
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
dialogLocationSourcesOpenState.value = true
|
||||
viewModel.setSelectedLocation(location)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
if (dialogLocationSourcesOpenState.value && selectedLocation != null) {
|
||||
if (dialogLocationSourcesOpenState.value && selectedLocation.value != null) {
|
||||
SecondarySourcesPreference(
|
||||
sourceManager = sourceManager,
|
||||
location = selectedLocation!!,
|
||||
location = selectedLocation.value!!,
|
||||
onClose = { location: Location? ->
|
||||
if (location != null) {
|
||||
finishSelf(location)
|
||||
}
|
||||
dialogLocationSourcesOpenState.value = false
|
||||
viewModel.closeDialogLocationSources()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
@ -19,20 +19,26 @@ package org.breezyweather.ui.search
|
|||
import android.content.Context
|
||||
import breezyweather.domain.location.model.Location
|
||||
import breezyweather.domain.location.model.LocationAddressInfo
|
||||
import breezyweather.domain.source.SourceFeature
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||
import io.reactivex.rxjava3.observers.DisposableObserver
|
||||
import org.breezyweather.BuildConfig
|
||||
import org.breezyweather.common.rxjava.ObserverContainer
|
||||
import org.breezyweather.common.rxjava.SchedulerTransformer
|
||||
import org.breezyweather.domain.location.model.applyDefaultPreset
|
||||
import org.breezyweather.domain.settings.ConfigStore
|
||||
import org.breezyweather.domain.settings.SettingsManager
|
||||
import org.breezyweather.sources.RefreshHelper
|
||||
import org.breezyweather.sources.SourceManager
|
||||
import org.breezyweather.sources.getWeatherSource
|
||||
import org.breezyweather.ui.main.utils.RefreshErrorType
|
||||
import javax.inject.Inject
|
||||
|
||||
class SearchActivityRepository @Inject internal constructor(
|
||||
@ApplicationContext context: Context,
|
||||
private val mRefreshHelper: RefreshHelper,
|
||||
private val mSourceManager: SourceManager,
|
||||
private val mCompositeDisposable: CompositeDisposable,
|
||||
) {
|
||||
private val mConfig: ConfigStore = ConfigStore(context, PREFERENCE_SEARCH_CONFIG)
|
||||
|
@ -76,6 +82,100 @@ class SearchActivityRepository @Inject internal constructor(
|
|||
)
|
||||
}
|
||||
|
||||
suspend fun getLocationWithUnambiguousCountryCode(
|
||||
location: Location,
|
||||
context: Context,
|
||||
): Location {
|
||||
return mRefreshHelper.getLocationWithUnambiguousCountryCode(location, context)
|
||||
}
|
||||
|
||||
fun getLocationWithAppliedPreference(
|
||||
location: Location,
|
||||
context: Context,
|
||||
): Location {
|
||||
val defaultSource = SettingsManager.getInstance(context).defaultForecastSource
|
||||
|
||||
return when (defaultSource) {
|
||||
"auto" -> location.applyDefaultPreset(mSourceManager)
|
||||
else -> {
|
||||
val source = mSourceManager.getWeatherSource(defaultSource)
|
||||
if (source == null) {
|
||||
location.applyDefaultPreset(mSourceManager)
|
||||
} else {
|
||||
location.copy(
|
||||
forecastSource = source.id,
|
||||
currentSource = if (SourceFeature.CURRENT in
|
||||
source.supportedFeatures &&
|
||||
source.isFeatureSupportedForLocation(
|
||||
location,
|
||||
SourceFeature.CURRENT
|
||||
)
|
||||
) {
|
||||
source.id
|
||||
} else {
|
||||
null
|
||||
},
|
||||
airQualitySource = if (SourceFeature.AIR_QUALITY in
|
||||
source.supportedFeatures &&
|
||||
source.isFeatureSupportedForLocation(
|
||||
location,
|
||||
SourceFeature.AIR_QUALITY
|
||||
)
|
||||
) {
|
||||
source.id
|
||||
} else {
|
||||
null
|
||||
},
|
||||
pollenSource = if (SourceFeature.POLLEN in
|
||||
source.supportedFeatures &&
|
||||
source.isFeatureSupportedForLocation(
|
||||
location,
|
||||
SourceFeature.POLLEN
|
||||
)
|
||||
) {
|
||||
source.id
|
||||
} else {
|
||||
null
|
||||
},
|
||||
minutelySource = if (SourceFeature.MINUTELY in
|
||||
source.supportedFeatures &&
|
||||
source.isFeatureSupportedForLocation(
|
||||
location,
|
||||
SourceFeature.MINUTELY
|
||||
)
|
||||
) {
|
||||
source.id
|
||||
} else {
|
||||
null
|
||||
},
|
||||
alertSource = if (SourceFeature.ALERT in
|
||||
source.supportedFeatures &&
|
||||
source.isFeatureSupportedForLocation(
|
||||
location,
|
||||
SourceFeature.ALERT
|
||||
)
|
||||
) {
|
||||
source.id
|
||||
} else {
|
||||
null
|
||||
},
|
||||
normalsSource = if (SourceFeature.NORMALS in
|
||||
source.supportedFeatures &&
|
||||
source.isFeatureSupportedForLocation(
|
||||
location,
|
||||
SourceFeature.NORMALS
|
||||
)
|
||||
) {
|
||||
source.id
|
||||
} else {
|
||||
null
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var lastSelectedLocationSearchSource: String
|
||||
set(value) {
|
||||
mConfig.edit().putString(KEY_LAST_DEFAULT_SOURCE, value).apply()
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
package org.breezyweather.ui.search
|
||||
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import breezyweather.domain.location.model.Location
|
||||
import breezyweather.domain.location.model.LocationAddressInfo
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
|
@ -25,6 +26,7 @@ import kotlinx.coroutines.flow.asStateFlow
|
|||
import org.breezyweather.BreezyWeather
|
||||
import org.breezyweather.common.basic.BreezyViewModel
|
||||
import org.breezyweather.common.extensions.currentLocale
|
||||
import org.breezyweather.common.extensions.launchIO
|
||||
import org.breezyweather.common.utils.helpers.SnackbarHelper
|
||||
import org.breezyweather.ui.main.utils.RefreshErrorType
|
||||
import javax.inject.Inject
|
||||
|
@ -42,6 +44,12 @@ class SearchViewModel @Inject constructor(
|
|||
repository.lastSelectedLocationSearchSource
|
||||
)
|
||||
val locationSearchSource = _locationSearchSource.asStateFlow()
|
||||
private val _selectedLocation: MutableStateFlow<Location?> = MutableStateFlow(null)
|
||||
val selectedLocation = _selectedLocation.asStateFlow()
|
||||
private val _dialogLocationSourcesOpenState: MutableStateFlow<Boolean> = MutableStateFlow(false)
|
||||
val dialogLocationSourcesOpenState = _dialogLocationSourcesOpenState.asStateFlow()
|
||||
private val _isLoading: MutableStateFlow<Boolean> = MutableStateFlow(false)
|
||||
val isLoading = _isLoading.asStateFlow()
|
||||
private val mRepository: SearchActivityRepository = repository
|
||||
|
||||
fun requestLocationList(str: String) {
|
||||
|
@ -95,6 +103,22 @@ class SearchViewModel @Inject constructor(
|
|||
_locationSearchSource.value = locSearchSource
|
||||
}
|
||||
|
||||
fun setSelectedLocation(location: Location) {
|
||||
viewModelScope.launchIO {
|
||||
_isLoading.value = true
|
||||
_selectedLocation.value = mRepository.getLocationWithAppliedPreference(
|
||||
mRepository.getLocationWithUnambiguousCountryCode(location, getApplication()),
|
||||
getApplication()
|
||||
)
|
||||
_isLoading.value = false
|
||||
_dialogLocationSourcesOpenState.value = true
|
||||
}
|
||||
}
|
||||
|
||||
fun closeDialogLocationSources() {
|
||||
_dialogLocationSourcesOpenState.value = false
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
mRepository.cancel()
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -20,7 +20,6 @@ const val COORDINATES_PRECISION = 5
|
|||
* on the original Natural Earth file already converted to JSON
|
||||
*
|
||||
* TODO: Make this task convert shapefile to json instead of relying on external tools
|
||||
* TODO: Add missing Penghu and Matsu islands to Taiwan geometry as they are supported by the Taiwanese CWA source
|
||||
*/
|
||||
fun TaskContainerScope.registerNaturalEarthConfigTask(project: Project): TaskProvider<Task> {
|
||||
return with(project) {
|
||||
|
@ -81,8 +80,15 @@ fun TaskContainerScope.registerNaturalEarthConfigTask(project: Project): TaskPro
|
|||
"NAME_LEN"
|
||||
)
|
||||
properties.keys().forEach { k ->
|
||||
if (!k.startsWith("NAME_") && k != "ISO_A2") {
|
||||
propertiesToRemove.add(k)
|
||||
if (k != "ISO_A2") {
|
||||
if (!k.startsWith("NAME_")) {
|
||||
// Remove everything we don't need
|
||||
propertiesToRemove.add(k)
|
||||
} else if (!properties.getString("ISO_A2").isNullOrEmpty()) {
|
||||
// Remove every name as Android already provides it
|
||||
// Unless it's an unknown country (with no ISO A2)
|
||||
propertiesToRemove.add(k)
|
||||
}
|
||||
}
|
||||
}
|
||||
propertiesToRemove.forEach { p ->
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue