feat(map): load device time series for map panel chart
Add LocationTimeSeries GraphQL query and DeviceTimeSeriesRepository. Extend DevicePanelState with chart data/loading/offset; MapViewModel fetches via location _id and maps with DashboardChartMapper. Wire MapScreen preview with sample chart data. Made-with: Cursor
This commit is contained in:
@@ -38,6 +38,8 @@ import androidx.lifecycle.LifecycleEventObserver
|
||||
import androidx.lifecycle.compose.LocalLifecycleOwner
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import org.db3.airmq.R
|
||||
import org.db3.airmq.features.common.metric.SensorType as MetricSensorType
|
||||
import org.db3.airmq.features.dashboard.DashboardChartMapper
|
||||
import org.db3.airmq.features.map.MapScreenContract.Action
|
||||
import org.db3.airmq.features.map.MapScreenContract.DevicePanelState
|
||||
import org.db3.airmq.features.map.MapScreenContract.DeviceSensorType
|
||||
@@ -584,7 +586,12 @@ private fun PreviewMapScreenDevicePanel() {
|
||||
status = "Online",
|
||||
selectedRange = TimeRange.DAY,
|
||||
displayedDateRange = "Today",
|
||||
selectedSensor = DeviceSensorType.DUST
|
||||
selectedSensor = DeviceSensorType.DUST,
|
||||
chartDataset = DashboardChartMapper.chartDataset(
|
||||
DashboardChartMapper.previewStaticRows(),
|
||||
MetricSensorType.DUST
|
||||
),
|
||||
isChartLoading = false
|
||||
)
|
||||
),
|
||||
onEvent = {},
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package org.db3.airmq.features.map
|
||||
|
||||
import org.db3.airmq.features.common.chart.ChartDataset
|
||||
|
||||
object MapScreenContract {
|
||||
|
||||
enum class SensorType {
|
||||
@@ -37,7 +39,12 @@ object MapScreenContract {
|
||||
DeviceSensorType.TEMPERATURE,
|
||||
DeviceSensorType.DUST,
|
||||
DeviceSensorType.RADIOACTIVITY
|
||||
)
|
||||
),
|
||||
val chartDataset: ChartDataset = ChartDataset.Single(emptyList()),
|
||||
val isChartLoading: Boolean = false,
|
||||
val chartErrorMessage: String? = null,
|
||||
/** Non-positive: shift the window into the past; `0` ends at now. */
|
||||
val chartWindowOffset: Int = 0,
|
||||
)
|
||||
|
||||
data class SearchPanelState(
|
||||
|
||||
@@ -5,6 +5,9 @@ import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
@@ -13,17 +16,25 @@ import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.db3.airmq.R
|
||||
import org.db3.airmq.features.common.chart.ChartDataset
|
||||
import org.db3.airmq.features.common.metric.SensorType
|
||||
import org.db3.airmq.features.dashboard.DashboardChartMapper
|
||||
import org.db3.airmq.features.map.MapScreenContract.Action
|
||||
import org.db3.airmq.features.map.MapScreenContract.DevicePanelState
|
||||
import org.db3.airmq.features.map.MapScreenContract.DeviceSensorType
|
||||
import org.db3.airmq.features.map.MapScreenContract.Event
|
||||
import org.db3.airmq.features.map.MapScreenContract.SearchResult
|
||||
import org.db3.airmq.features.map.MapScreenContract.SearchPanelState
|
||||
import org.db3.airmq.features.map.MapScreenContract.SensorType
|
||||
import org.db3.airmq.features.map.MapScreenContract.SensorType as MapSensorType
|
||||
import org.db3.airmq.features.map.MapScreenContract.State
|
||||
import org.db3.airmq.features.map.MapScreenContract.TimeRange
|
||||
import org.db3.airmq.sdk.auth.ApiTokenStore
|
||||
import org.db3.airmq.sdk.dashboard.DeviceTimeSeriesRepository
|
||||
import org.db3.airmq.sdk.dashboard.SensorSampleRow
|
||||
import org.db3.airmq.sdk.map.MapService
|
||||
import org.db3.airmq.sdk.map.domain.MapItem
|
||||
import org.db3.airmq.sdk.settings.SettingsService
|
||||
@@ -32,7 +43,9 @@ import org.db3.airmq.sdk.settings.SettingsService
|
||||
class MapViewModel @Inject constructor(
|
||||
@ApplicationContext private val appContext: Context,
|
||||
private val mapService: MapService,
|
||||
private val settingsService: SettingsService
|
||||
private val apiTokenStore: ApiTokenStore,
|
||||
private val settingsService: SettingsService,
|
||||
private val deviceTimeSeriesRepository: DeviceTimeSeriesRepository,
|
||||
) : ViewModel() {
|
||||
|
||||
private val _uiState = MutableStateFlow(State(isLoading = true))
|
||||
@@ -43,9 +56,14 @@ class MapViewModel @Inject constructor(
|
||||
val actions: SharedFlow<Action> = _actions.asSharedFlow()
|
||||
|
||||
private var showOfflineDevices = false
|
||||
private var deviceChartRowsCache: List<SensorSampleRow> = emptyList()
|
||||
|
||||
init {
|
||||
refreshMapItems()
|
||||
viewModelScope.launch {
|
||||
apiTokenStore.observeToken().collectLatest {
|
||||
refreshMapItems()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onEvent(event: Event) {
|
||||
@@ -55,6 +73,7 @@ class MapViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
is Event.SearchButtonClicked -> {
|
||||
deviceChartRowsCache = emptyList()
|
||||
_uiState.value = _uiState.value.copy(
|
||||
searchPanelState = SearchPanelState(),
|
||||
devicePanelState = null
|
||||
@@ -85,6 +104,7 @@ class MapViewModel @Inject constructor(
|
||||
devicePanelState = selectedItem.toDevicePanelState(),
|
||||
selectedMarkerId = event.resultId
|
||||
)
|
||||
loadDeviceChart(forceFetch = true)
|
||||
}
|
||||
|
||||
is Event.MyLocationClicked -> {
|
||||
@@ -111,9 +131,11 @@ class MapViewModel @Inject constructor(
|
||||
devicePanelState = selectedItem.toDevicePanelState(),
|
||||
selectedMarkerId = event.itemId
|
||||
)
|
||||
loadDeviceChart(forceFetch = true)
|
||||
}
|
||||
|
||||
is Event.DevicePanelClosed -> {
|
||||
deviceChartRowsCache = emptyList()
|
||||
val previousMarkerId = _uiState.value.devicePanelState?.id
|
||||
_uiState.value = _uiState.value.copy(
|
||||
devicePanelState = null,
|
||||
@@ -131,46 +153,149 @@ class MapViewModel @Inject constructor(
|
||||
_uiState.value = _uiState.value.copy(
|
||||
devicePanelState = panelData.copy(
|
||||
selectedRange = event.range,
|
||||
displayedDateRange = rangeLabel(event.range)
|
||||
displayedDateRange = rangeLabel(event.range),
|
||||
chartWindowOffset = 0
|
||||
)
|
||||
)
|
||||
loadDeviceChart(forceFetch = true)
|
||||
}
|
||||
|
||||
is Event.DateBackClicked -> {
|
||||
val panelData = _uiState.value.devicePanelState ?: return
|
||||
_uiState.value = _uiState.value.copy(
|
||||
devicePanelState = panelData.copy(
|
||||
displayedDateRange = appContext.getString(
|
||||
R.string.map_previous_period,
|
||||
rangeLabel(panelData.selectedRange)
|
||||
)
|
||||
chartWindowOffset = panelData.chartWindowOffset - 1
|
||||
)
|
||||
)
|
||||
loadDeviceChart(forceFetch = true)
|
||||
}
|
||||
|
||||
Event.DateForwardClicked -> {
|
||||
val panelData = _uiState.value.devicePanelState ?: return
|
||||
if (panelData.chartWindowOffset >= 0) return
|
||||
_uiState.value = _uiState.value.copy(
|
||||
devicePanelState = panelData.copy(
|
||||
displayedDateRange = appContext.getString(
|
||||
R.string.map_next_period,
|
||||
rangeLabel(panelData.selectedRange)
|
||||
)
|
||||
chartWindowOffset = (panelData.chartWindowOffset + 1).coerceAtMost(0)
|
||||
)
|
||||
)
|
||||
loadDeviceChart(forceFetch = true)
|
||||
}
|
||||
is Event.DeviceSensorSelected -> {
|
||||
val panelData = _uiState.value.devicePanelState ?: return
|
||||
_uiState.value = _uiState.value.copy(
|
||||
devicePanelState = panelData.copy(selectedSensor = event.sensor)
|
||||
)
|
||||
loadDeviceChart(forceFetch = false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadDeviceChart(forceFetch: Boolean = true) {
|
||||
viewModelScope.launch {
|
||||
val panel = _uiState.value.devicePanelState ?: return@launch
|
||||
val locationId = panel.id
|
||||
|
||||
if (!forceFetch && deviceChartRowsCache.isNotEmpty()) {
|
||||
val current = _uiState.value.devicePanelState ?: return@launch
|
||||
if (current.id != locationId) return@launch
|
||||
_uiState.value = _uiState.value.copy(
|
||||
devicePanelState = current.copy(
|
||||
chartDataset = DashboardChartMapper.chartDataset(
|
||||
deviceChartRowsCache,
|
||||
current.selectedSensor.toMetricSensorType()
|
||||
),
|
||||
isChartLoading = false,
|
||||
chartErrorMessage = null
|
||||
)
|
||||
)
|
||||
return@launch
|
||||
}
|
||||
|
||||
if (locationId.startsWith(DUMMY_PREFIX) || apiTokenStore.getToken().isNullOrBlank()) {
|
||||
deviceChartRowsCache = emptyList()
|
||||
val current = _uiState.value.devicePanelState ?: return@launch
|
||||
_uiState.value = _uiState.value.copy(
|
||||
devicePanelState = current.copy(
|
||||
chartDataset = ChartDataset.Single(emptyList()),
|
||||
isChartLoading = false,
|
||||
chartErrorMessage = null
|
||||
)
|
||||
)
|
||||
return@launch
|
||||
}
|
||||
|
||||
val loadingPanel = panel.copy(isChartLoading = true, chartErrorMessage = null)
|
||||
_uiState.value = _uiState.value.copy(devicePanelState = loadingPanel)
|
||||
|
||||
val now = System.currentTimeMillis()
|
||||
val span = spanMillis(panel.selectedRange)
|
||||
val tTo = now + panel.chartWindowOffset * span
|
||||
val tFrom = tTo - span
|
||||
val (intervalH, intervalD, intervalM) = intervalsForRange(panel.selectedRange)
|
||||
|
||||
val result = withContext(Dispatchers.IO) {
|
||||
deviceTimeSeriesRepository.fetchTimeSeries(
|
||||
locationId = locationId,
|
||||
tFromEpochMillis = tFrom,
|
||||
tToEpochMillis = tTo,
|
||||
intervalHours = intervalH,
|
||||
intervalDays = intervalD,
|
||||
intervalMinutes = intervalM,
|
||||
)
|
||||
}
|
||||
|
||||
val currentPanel = _uiState.value.devicePanelState
|
||||
if (currentPanel?.id != locationId) return@launch
|
||||
|
||||
result.fold(
|
||||
onSuccess = { rows ->
|
||||
deviceChartRowsCache = rows
|
||||
val label = formatWindowRange(tFrom, tTo)
|
||||
_uiState.value = _uiState.value.copy(
|
||||
devicePanelState = currentPanel.copy(
|
||||
chartDataset = DashboardChartMapper.chartDataset(
|
||||
rows,
|
||||
currentPanel.selectedSensor.toMetricSensorType()
|
||||
),
|
||||
isChartLoading = false,
|
||||
chartErrorMessage = null,
|
||||
displayedDateRange = label
|
||||
)
|
||||
)
|
||||
},
|
||||
onFailure = { throwable ->
|
||||
deviceChartRowsCache = emptyList()
|
||||
_actions.tryEmit(
|
||||
Action.ShowToast(
|
||||
throwable.message ?: appContext.getString(R.string.map_failed_to_load_items)
|
||||
)
|
||||
)
|
||||
_uiState.value = _uiState.value.copy(
|
||||
devicePanelState = currentPanel.copy(
|
||||
chartDataset = ChartDataset.Single(emptyList()),
|
||||
isChartLoading = false,
|
||||
chartErrorMessage = throwable.message
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun refreshMapItems() {
|
||||
_uiState.value = _uiState.value.copy(isLoading = true)
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
if (apiTokenStore.getToken().isNullOrBlank()) {
|
||||
domainItems = emptyList()
|
||||
val searchPanelState = _uiState.value.searchPanelState
|
||||
val selectedSensorType = _uiState.value.selectedTopSensor
|
||||
_uiState.value = _uiState.value.copy(
|
||||
isLoading = false,
|
||||
items = dummyMarkers(selectedSensorType),
|
||||
searchPanelState = searchPanelState?.copy(results = emptyList())
|
||||
)
|
||||
return@launch
|
||||
}
|
||||
val result = runCatching {
|
||||
showOfflineDevices = settingsService.getOfflineDevicesVisible()
|
||||
mapService.fetchMapItems(showOfflineDevices = showOfflineDevices)
|
||||
@@ -234,6 +359,25 @@ class MapViewModel @Inject constructor(
|
||||
TimeRange.MONTH -> appContext.getString(R.string.map_time_this_month)
|
||||
}
|
||||
|
||||
private fun formatWindowRange(tFrom: Long, tTo: Long): String {
|
||||
val df = SimpleDateFormat("MMM d HH:mm", Locale.getDefault())
|
||||
return "${df.format(Date(tFrom))} – ${df.format(Date(tTo))}"
|
||||
}
|
||||
|
||||
private fun spanMillis(range: TimeRange): Long = when (range) {
|
||||
TimeRange.HOUR -> HOUR_MS
|
||||
TimeRange.DAY -> DAY_MS
|
||||
TimeRange.WEEK -> WEEK_MS
|
||||
TimeRange.MONTH -> MONTH_MS
|
||||
}
|
||||
|
||||
private fun intervalsForRange(range: TimeRange): Triple<Int, Int, Int> = when (range) {
|
||||
TimeRange.HOUR -> Triple(0, 0, 15)
|
||||
TimeRange.DAY -> Triple(1, 0, 0)
|
||||
TimeRange.WEEK -> Triple(0, 1, 0)
|
||||
TimeRange.MONTH -> Triple(0, 1, 0)
|
||||
}
|
||||
|
||||
private fun remapMarkers() {
|
||||
val markers = domainItems.toMarkers(_uiState.value.selectedTopSensor)
|
||||
_uiState.value = _uiState.value.copy(
|
||||
@@ -241,7 +385,7 @@ class MapViewModel @Inject constructor(
|
||||
)
|
||||
}
|
||||
|
||||
private fun dummyMarkers(sensorType: SensorType): List<MapMarker> = listOf(
|
||||
private fun dummyMarkers(sensorType: MapSensorType): List<MapMarker> = listOf(
|
||||
MapMarker(
|
||||
id = "dummy-1",
|
||||
title = "AirMQ Demo #1",
|
||||
@@ -288,15 +432,15 @@ class MapViewModel @Inject constructor(
|
||||
)
|
||||
)
|
||||
|
||||
private fun List<MapItem>.toMarkers(sensorType: SensorType): List<MapMarker> {
|
||||
private fun List<MapItem>.toMarkers(sensorType: MapSensorType): List<MapMarker> {
|
||||
return mapNotNull { item ->
|
||||
val value = when (sensorType) {
|
||||
SensorType.DUST -> item.dustValue
|
||||
SensorType.RADIOACTIVITY -> item.radioactivityValue
|
||||
MapSensorType.DUST -> item.dustValue
|
||||
MapSensorType.RADIOACTIVITY -> item.radioactivityValue
|
||||
}
|
||||
|
||||
// Return null if device is offline or value is missing
|
||||
if (!showOfflineDevices && (!item.isOnline || value == null )) {
|
||||
if (!showOfflineDevices && (!item.isOnline || value == null)) {
|
||||
return@mapNotNull null
|
||||
}
|
||||
|
||||
@@ -316,8 +460,8 @@ class MapViewModel @Inject constructor(
|
||||
|
||||
private fun MapMarker.toDevicePanelState(): DevicePanelState {
|
||||
val defaultSensor = when (sensorType) {
|
||||
SensorType.DUST -> DeviceSensorType.DUST
|
||||
SensorType.RADIOACTIVITY -> DeviceSensorType.RADIOACTIVITY
|
||||
MapSensorType.DUST -> DeviceSensorType.DUST
|
||||
MapSensorType.RADIOACTIVITY -> DeviceSensorType.RADIOACTIVITY
|
||||
}
|
||||
return DevicePanelState(
|
||||
id = id,
|
||||
@@ -329,7 +473,25 @@ class MapViewModel @Inject constructor(
|
||||
},
|
||||
selectedRange = TimeRange.DAY,
|
||||
displayedDateRange = rangeLabel(TimeRange.DAY),
|
||||
selectedSensor = defaultSensor
|
||||
selectedSensor = defaultSensor,
|
||||
chartDataset = ChartDataset.Single(emptyList()),
|
||||
isChartLoading = true,
|
||||
chartErrorMessage = null,
|
||||
chartWindowOffset = 0,
|
||||
)
|
||||
}
|
||||
|
||||
private fun DeviceSensorType.toMetricSensorType(): SensorType = when (this) {
|
||||
DeviceSensorType.TEMPERATURE -> SensorType.TEMPERATURE
|
||||
DeviceSensorType.DUST -> SensorType.DUST
|
||||
DeviceSensorType.RADIOACTIVITY -> SensorType.RADIOACTIVITY
|
||||
}
|
||||
|
||||
private companion object {
|
||||
private const val DUMMY_PREFIX = "dummy-"
|
||||
private const val HOUR_MS = 60L * 60L * 1000L
|
||||
private const val DAY_MS = 24L * HOUR_MS
|
||||
private const val WEEK_MS = 7L * DAY_MS
|
||||
private const val MONTH_MS = 30L * DAY_MS
|
||||
}
|
||||
}
|
||||
|
||||
18
sdk/src/main/graphql/LocationTimeSeries.graphql
Normal file
18
sdk/src/main/graphql/LocationTimeSeries.graphql
Normal file
@@ -0,0 +1,18 @@
|
||||
query LocationTimeSeries($filter: LocationFilter!, $span: TimeSpan!) {
|
||||
location(filter: $filter) {
|
||||
_id
|
||||
timeSeries(filter: $span) {
|
||||
deviceId
|
||||
time
|
||||
Temp
|
||||
Hum
|
||||
Press
|
||||
PMS1
|
||||
PMS25
|
||||
PMS10
|
||||
radRg
|
||||
PPM
|
||||
IKAV
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package org.db3.airmq.sdk.dashboard
|
||||
|
||||
/**
|
||||
* Fetches per-location sensor time series for map / device charts.
|
||||
*/
|
||||
interface DeviceTimeSeriesRepository {
|
||||
|
||||
/**
|
||||
* @param intervalHours/Days/Minutes bucketing for aggregation (server-defined); pass 0 for unused axes.
|
||||
*/
|
||||
suspend fun fetchTimeSeries(
|
||||
locationId: String,
|
||||
tFromEpochMillis: Long,
|
||||
tToEpochMillis: Long,
|
||||
intervalHours: Int,
|
||||
intervalDays: Int,
|
||||
intervalMinutes: Int,
|
||||
): Result<List<SensorSampleRow>>
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package org.db3.airmq.sdk.dashboard
|
||||
|
||||
import android.util.Log
|
||||
import com.apollographql.apollo.ApolloClient
|
||||
import com.apollographql.apollo.api.Optional
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
import java.util.TimeZone
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import org.db3.airmq.sdk.LocationTimeSeriesQuery
|
||||
import org.db3.airmq.sdk.type.LocationFilter
|
||||
import org.db3.airmq.sdk.type.TimeSpan
|
||||
|
||||
@Singleton
|
||||
class DeviceTimeSeriesRepositoryImpl @Inject constructor(
|
||||
private val apolloClient: ApolloClient,
|
||||
) : DeviceTimeSeriesRepository {
|
||||
|
||||
override suspend fun fetchTimeSeries(
|
||||
locationId: String,
|
||||
tFromEpochMillis: Long,
|
||||
tToEpochMillis: Long,
|
||||
intervalHours: Int,
|
||||
intervalDays: Int,
|
||||
intervalMinutes: Int,
|
||||
): Result<List<SensorSampleRow>> = runCatching {
|
||||
val filter = LocationFilter(
|
||||
_id = Optional.Present(locationId),
|
||||
)
|
||||
val span = TimeSpan(
|
||||
t_from = Optional.Present(formatUtcIso(tFromEpochMillis)),
|
||||
t_to = Optional.Present(formatUtcIso(tToEpochMillis)),
|
||||
interval_h = Optional.Present(intervalHours),
|
||||
interval_d = Optional.Present(intervalDays),
|
||||
interval_m = Optional.Present(intervalMinutes),
|
||||
)
|
||||
|
||||
Log.d(
|
||||
TAG,
|
||||
"LocationTimeSeries locationId=$locationId t_from=${span.t_from} t_to=${span.t_to} " +
|
||||
"ih=$intervalHours id=$intervalDays im=$intervalMinutes"
|
||||
)
|
||||
|
||||
val response = apolloClient
|
||||
.query(LocationTimeSeriesQuery(filter = filter, span = span))
|
||||
.execute()
|
||||
|
||||
response.exception?.let { throw it }
|
||||
response.errors?.firstOrNull()?.let { throw IllegalStateException(it.message) }
|
||||
|
||||
val series = response.data?.location?.timeSeries.orEmpty()
|
||||
val rows = series.mapNotNull { row -> row?.toSensorSampleRow() }
|
||||
|
||||
Log.d(TAG, "LocationTimeSeries parsed ${rows.size} rows (raw points=${series.size})")
|
||||
|
||||
rows
|
||||
}
|
||||
|
||||
private fun formatUtcIso(epochMillis: Long): String {
|
||||
val sdf = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US)
|
||||
sdf.timeZone = TimeZone.getTimeZone("UTC")
|
||||
return sdf.format(Date(epochMillis))
|
||||
}
|
||||
|
||||
private companion object {
|
||||
private const val TAG = "DeviceTimeSeriesApi"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package org.db3.airmq.sdk.dashboard
|
||||
|
||||
import org.db3.airmq.sdk.LocationTimeSeriesQuery
|
||||
|
||||
internal fun LocationTimeSeriesQuery.TimeSeries.toSensorSampleRow(): SensorSampleRow? {
|
||||
val t = GraphqlDateTimeParser.parseToEpochMillis(time) ?: return null
|
||||
return SensorSampleRow(
|
||||
epochMillis = t,
|
||||
temp = Temp?.toFloat(),
|
||||
hum = Hum?.toFloat(),
|
||||
press = Press?.toFloat(),
|
||||
pms1 = PMS1?.toFloat(),
|
||||
pms25 = PMS25?.toFloat(),
|
||||
pms10 = PMS10?.toFloat(),
|
||||
radRg = radRg?.toFloat(),
|
||||
ppm = PPM?.toFloat(),
|
||||
ikav = IKAV?.toFloat(),
|
||||
)
|
||||
}
|
||||
@@ -18,6 +18,8 @@ import org.db3.airmq.sdk.auth.SharedPreferencesApiTokenStore
|
||||
import org.db3.airmq.sdk.auth.SharedPreferencesLocalEmailAuthStore
|
||||
import org.db3.airmq.sdk.dashboard.DashboardMetricsRepository
|
||||
import org.db3.airmq.sdk.dashboard.DashboardMetricsRepositoryImpl
|
||||
import org.db3.airmq.sdk.dashboard.DeviceTimeSeriesRepository
|
||||
import org.db3.airmq.sdk.dashboard.DeviceTimeSeriesRepositoryImpl
|
||||
import org.db3.airmq.sdk.map.MapServiceImpl
|
||||
import org.db3.airmq.sdk.map.MapService
|
||||
import org.db3.airmq.sdk.settings.SettingsService
|
||||
@@ -74,6 +76,12 @@ abstract class SDKBindModule {
|
||||
impl: DashboardMetricsRepositoryImpl
|
||||
): DashboardMetricsRepository
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
abstract fun bindDeviceTimeSeriesRepository(
|
||||
impl: DeviceTimeSeriesRepositoryImpl
|
||||
): DeviceTimeSeriesRepository
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
abstract fun bindSettingsService(impl: SettingsServiceImpl): SettingsService
|
||||
|
||||
Reference in New Issue
Block a user