Fix city selection vs dashboard; Map/Manage updates; workspace and API logging
Made-with: Cursor
This commit is contained in:
11
.cursor/airmq-android.code-workspace
Normal file
11
.cursor/airmq-android.code-workspace
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": "../../airmq-android"
|
||||
},
|
||||
{
|
||||
"path": ".."
|
||||
}
|
||||
],
|
||||
"settings": {}
|
||||
}
|
||||
@@ -79,7 +79,12 @@ class CityViewModel @Inject constructor(
|
||||
private fun selectCity(city: org.db3.airmq.sdk.city.domain.City) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
cityService.setSelectedCity(city.id)
|
||||
_actions.tryEmit(CityScreenContract.Action.NavigateBack)
|
||||
.onSuccess { _actions.tryEmit(CityScreenContract.Action.NavigateBack) }
|
||||
.onFailure {
|
||||
_actions.tryEmit(
|
||||
CityScreenContract.Action.ShowToast(appContext.getString(R.string.toast_error))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,8 +98,8 @@ class CityViewModel @Inject constructor(
|
||||
private fun enableDetectAutomaticallyWithLocation(location: Location?) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
if (location != null) {
|
||||
cityService.refreshCityFromLocation(location)
|
||||
cityService.setDetectAutomatically(true)
|
||||
cityService.refreshCityFromLocation(location)
|
||||
val selectedCity = cityService.getSelectedCity()
|
||||
_uiState.value = _uiState.value.copy(
|
||||
detectAutomatically = true,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.db3.airmq.features.manage
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.clickable
|
||||
@@ -67,6 +68,7 @@ fun ManageScreen(
|
||||
viewModel: ManageViewModel = hiltViewModel()
|
||||
) {
|
||||
val uiState by viewModel.uiState.collectAsState()
|
||||
Log.d("MANAGE_DEBUG", uiState.toString())
|
||||
val lifecycleOwner = LocalLifecycleOwner.current
|
||||
DisposableEffect(lifecycleOwner, viewModel) {
|
||||
val observer = LifecycleEventObserver { _, event ->
|
||||
|
||||
@@ -301,21 +301,28 @@ private fun AirMQMap(
|
||||
}
|
||||
}
|
||||
|
||||
// Overlay rebuild must be keyed off composable inputs: AndroidView's `update` runs without
|
||||
// recording snapshot reads when values are only read inside View.post {}, so map items can
|
||||
// load without a rebuild until something else (e.g. pan → debounced rebuild) retriggers it.
|
||||
LaunchedEffect(items, centerOnMarker, clusterEnabled) {
|
||||
val snapshotItems = items
|
||||
val snapshotCenter = centerOnMarker
|
||||
val snapshotCluster = clusterEnabled
|
||||
mapView.post {
|
||||
rebuildAirMqMapOverlays(
|
||||
map = mapView,
|
||||
items = snapshotItems,
|
||||
onMarkerClick = latestOnMarkerClick.value,
|
||||
clusterEnabled = snapshotCluster,
|
||||
centerOnMarker = snapshotCenter,
|
||||
initialCameraDone = initialCameraDone
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
AndroidView(
|
||||
modifier = modifier.fillMaxSize(),
|
||||
factory = { mapView },
|
||||
update = { map ->
|
||||
map.post {
|
||||
rebuildAirMqMapOverlays(
|
||||
map = map,
|
||||
items = latestItems.value,
|
||||
onMarkerClick = latestOnMarkerClick.value,
|
||||
clusterEnabled = latestClusterEnabled.value,
|
||||
centerOnMarker = latestCenterOnMarker.value,
|
||||
initialCameraDone = initialCameraDone
|
||||
)
|
||||
}
|
||||
}
|
||||
factory = { mapView }
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ interface CityService {
|
||||
/**
|
||||
* If [DashboardCityContext.cityId] is missing, tries to resolve it from the local city DB
|
||||
* using the stored English name and updates preferences.
|
||||
* Does not notify [observeDashboardCityContext] (avoids re-entrant emissions during dashboard load).
|
||||
*/
|
||||
suspend fun refreshDashboardCityIdentity()
|
||||
|
||||
@@ -80,9 +81,9 @@ interface CityService {
|
||||
suspend fun setDetectAutomatically(enabled: Boolean)
|
||||
|
||||
/**
|
||||
* Refreshes the selected city from location. Used when detect automatically is on.
|
||||
* Refreshes the selected city from location when [getDetectAutomatically] is true.
|
||||
* Resolves closest city from the cities DB and updates stored city.
|
||||
* No-op if location is null or no matching city found.
|
||||
* No-op if auto-detect is off, location is null, or no matching city found.
|
||||
*/
|
||||
suspend fun refreshCityFromLocation(location: Location?)
|
||||
|
||||
|
||||
@@ -101,6 +101,7 @@ class CityServiceImpl @Inject constructor(
|
||||
}
|
||||
|
||||
override suspend fun refreshCityFromLocation(location: Location?) {
|
||||
if (!getDetectAutomatically()) return
|
||||
if (location == null) return
|
||||
val cities = ensureCitiesInDb()
|
||||
val resolvedCity = findClosestCity(cities, location.latitude, location.longitude)
|
||||
@@ -121,7 +122,6 @@ class CityServiceImpl @Inject constructor(
|
||||
val city = cityLocalDataSource.getAllCities()
|
||||
.find { it.nameEn.equals(nameEn, ignoreCase = true) } ?: return
|
||||
prefs.edit().putString(KEY_DASHBOARD_CITY_ID, city.id).apply()
|
||||
pushContextUpdate()
|
||||
}
|
||||
|
||||
override suspend fun getResolvedDashboardCityContext(): DashboardCityContext {
|
||||
|
||||
@@ -114,7 +114,12 @@ class DashboardMetricsRepositoryImpl @Inject constructor(
|
||||
}
|
||||
|
||||
private fun mapLastRow(row: CityAverageLastQuery.CityAverageLast): SensorSampleRow? {
|
||||
val t = GraphqlDateTimeParser.parseToEpochMillis(row.time) ?: return null
|
||||
val parsedTime = GraphqlDateTimeParser.parseToEpochMillis(row.time)
|
||||
val hasAnyReading = row.Temp != null || row.Hum != null || row.Press != null ||
|
||||
row.PMS1 != null || row.PMS25 != null || row.PMS10 != null ||
|
||||
row.radRg != null || row.PPM != null || row.IKAV != null ||
|
||||
row.CO2 != null || row.VOC != null || row.AQI != null
|
||||
val t = parsedTime ?: if (hasAnyReading) System.currentTimeMillis() else return null
|
||||
return SensorSampleRow(
|
||||
epochMillis = t,
|
||||
temp = row.Temp?.toFloat(),
|
||||
|
||||
Reference in New Issue
Block a user