From 04c2889895483f5925c90ed7a856d38391fcab45 Mon Sep 17 00:00:00 2001 From: Isidro Henoch Date: Wed, 8 Dec 2021 04:28:51 -0600 Subject: WIP: Implements the basic structure for the units list/map - Updates the Devices and Positions APIs to properly construct the Url's query when there are null values - Adds a units controller to the shared module - Adds a devices and map fragment that each print the fetched units on the console - Adds a units view model to connect previously mentioned fragments with the units controller --- .../mx/trackermap/TrackerMap/android/TrackerApp.kt | 4 +- .../TrackerMap/android/devices/DevicesFragment.kt | 19 +++++++ .../TrackerMap/android/map/MapFragment.kt | 19 +++++++ .../TrackerMap/android/units/UnitsViewModel.kt | 37 +++++++++++- .../TrackerMap/client/apis/DevicesApi.kt | 8 ++- .../TrackerMap/client/apis/PositionsApi.kt | 10 +++- .../trackermap/TrackerMap/client/models/Device.kt | 31 ++++++----- .../TrackerMap/client/models/Position.kt | 24 ++++---- .../TrackerMap/client/models/UnitInformation.kt | 6 ++ .../TrackerMap/controllers/UnitsController.kt | 65 ++++++++++++++++++++++ 10 files changed, 192 insertions(+), 31 deletions(-) create mode 100644 shared/src/commonMain/kotlin/mx/trackermap/TrackerMap/client/models/UnitInformation.kt create mode 100644 shared/src/commonMain/kotlin/mx/trackermap/TrackerMap/controllers/UnitsController.kt diff --git a/androidApp/src/main/java/mx/trackermap/TrackerMap/android/TrackerApp.kt b/androidApp/src/main/java/mx/trackermap/TrackerMap/android/TrackerApp.kt index b236577..e23c0de 100644 --- a/androidApp/src/main/java/mx/trackermap/TrackerMap/android/TrackerApp.kt +++ b/androidApp/src/main/java/mx/trackermap/TrackerMap/android/TrackerApp.kt @@ -6,6 +6,7 @@ import mx.trackermap.TrackerMap.android.units.UnitsViewModel import mx.trackermap.TrackerMap.client.apis.DevicesApi import mx.trackermap.TrackerMap.client.apis.PositionsApi import mx.trackermap.TrackerMap.client.apis.SessionApi +import mx.trackermap.TrackerMap.controllers.UnitsController import org.koin.android.ext.koin.androidContext import org.koin.android.ext.koin.androidLogger import org.koin.androidx.viewmodel.dsl.viewModel @@ -23,9 +24,10 @@ class TrackerApp : Application() { single { SessionApi(get()) } single { DevicesApi(get()) } single { PositionsApi(get()) } + single { UnitsController(get(), get()) } viewModel { LoginViewModel(get(), get()) } - viewModel { UnitsViewModel() } + single { UnitsViewModel(get()) } } startKoin { diff --git a/androidApp/src/main/java/mx/trackermap/TrackerMap/android/devices/DevicesFragment.kt b/androidApp/src/main/java/mx/trackermap/TrackerMap/android/devices/DevicesFragment.kt index 7e09b17..98c98cc 100644 --- a/androidApp/src/main/java/mx/trackermap/TrackerMap/android/devices/DevicesFragment.kt +++ b/androidApp/src/main/java/mx/trackermap/TrackerMap/android/devices/DevicesFragment.kt @@ -1,16 +1,22 @@ package mx.trackermap.TrackerMap.android.devices import android.os.Bundle +import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment +import kotlinx.coroutines.DelicateCoroutinesApi import mx.trackermap.TrackerMap.android.databinding.DevicesFragmentBinding +import mx.trackermap.TrackerMap.android.units.UnitsViewModel +import org.koin.androidx.viewmodel.ext.android.viewModel class DevicesFragment: Fragment() { private var _binding: DevicesFragmentBinding? = null private val binding get() = _binding!! + private val unitsViewModel: UnitsViewModel by viewModel() + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -24,4 +30,17 @@ class DevicesFragment: Fragment() { super.onDestroyView() _binding = null } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + setupObservers() + } + + @DelicateCoroutinesApi + private fun setupObservers() { + unitsViewModel.units.observe(this) { units -> + Log.d("DevicesFragment", "Success $units") + } + } } \ No newline at end of file diff --git a/androidApp/src/main/java/mx/trackermap/TrackerMap/android/map/MapFragment.kt b/androidApp/src/main/java/mx/trackermap/TrackerMap/android/map/MapFragment.kt index 0d4d91a..2ffaa2c 100644 --- a/androidApp/src/main/java/mx/trackermap/TrackerMap/android/map/MapFragment.kt +++ b/androidApp/src/main/java/mx/trackermap/TrackerMap/android/map/MapFragment.kt @@ -1,16 +1,22 @@ package mx.trackermap.TrackerMap.android.map import android.os.Bundle +import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment +import kotlinx.coroutines.DelicateCoroutinesApi import mx.trackermap.TrackerMap.android.databinding.MapFragmentBinding +import mx.trackermap.TrackerMap.android.units.UnitsViewModel +import org.koin.androidx.viewmodel.ext.android.viewModel class MapFragment: Fragment() { private var _binding: MapFragmentBinding? = null private val binding get() = _binding!! + private val unitsViewModel: UnitsViewModel by viewModel() + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -24,4 +30,17 @@ class MapFragment: Fragment() { super.onDestroyView() _binding = null } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + setupObservers() + } + + @DelicateCoroutinesApi + private fun setupObservers() { + unitsViewModel.units.observe(this) { units -> + Log.d("MapFragment", "Success $units") + } + } } \ No newline at end of file diff --git a/androidApp/src/main/java/mx/trackermap/TrackerMap/android/units/UnitsViewModel.kt b/androidApp/src/main/java/mx/trackermap/TrackerMap/android/units/UnitsViewModel.kt index 11df89b..7ac6e28 100644 --- a/androidApp/src/main/java/mx/trackermap/TrackerMap/android/units/UnitsViewModel.kt +++ b/androidApp/src/main/java/mx/trackermap/TrackerMap/android/units/UnitsViewModel.kt @@ -2,15 +2,44 @@ package mx.trackermap.TrackerMap.android.units import android.util.Log import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.DelicateCoroutinesApi +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.launch +import mx.trackermap.TrackerMap.client.models.UnitInformation +import mx.trackermap.TrackerMap.controllers.UnitsController +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject -class UnitsViewModel : ViewModel() { +@DelicateCoroutinesApi +class UnitsViewModel( + private val savedStateHandle: SavedStateHandle +) : ViewModel(), KoinComponent { enum class UnitsDisplayMode { MAP, LIST } - var unitsDisplayMode = MutableLiveData(UnitsDisplayMode.MAP) + private val unitsController: UnitsController by inject() + + var unitsDisplayMode = MutableLiveData(UnitsDisplayMode.MAP) + var units = MutableLiveData>() + + init { + Log.d("UnitsViewModel", "Initializing Units View Model") + viewModelScope.launch { + setupObservers() + } + } + + private suspend fun setupObservers() { + Log.d("UnitsViewModel", "Setup observers") + unitsController.displayedUnitsFlow.collect { units -> + this.units.value = units + } + } fun toggleDisplayMode() { Log.d("UnitsViewModel", "Toggling Display mode") @@ -22,4 +51,8 @@ class UnitsViewModel : ViewModel() { } unitsDisplayMode.postValue(newDisplayMode) } + + fun search(query: String) { + unitsController.search(query) + } } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/mx/trackermap/TrackerMap/client/apis/DevicesApi.kt b/shared/src/commonMain/kotlin/mx/trackermap/TrackerMap/client/apis/DevicesApi.kt index bc87f78..331cc4e 100644 --- a/shared/src/commonMain/kotlin/mx/trackermap/TrackerMap/client/apis/DevicesApi.kt +++ b/shared/src/commonMain/kotlin/mx/trackermap/TrackerMap/client/apis/DevicesApi.kt @@ -29,7 +29,13 @@ class DevicesApi(basePath: kotlin.String = "https://demo.traccar.org/api") : Api */ @Suppress("UNCHECKED_CAST") suspend fun devicesGet(all: kotlin.Boolean? = null, userId: kotlin.Int? = null, id: kotlin.Int? = null, uniqueId: kotlin.String? = null): kotlin.Array { - val localVariableQuery: MultiValueMap = mapOf("all" to listOf("$all"), "userId" to listOf("$userId"), "id" to listOf("$id"), "uniqueId" to listOf("$uniqueId")) + val query: MutableMap> = mutableMapOf() + all?.let { query["all"] = listOf("$it") } + userId?.let { query["userId"] = listOf("$it") } + id?.let { query["id"] = listOf("$it") } + uniqueId?.let { query["uniqueId"] = listOf("$it") } + val localVariableQuery: MultiValueMap = query + val localVariableConfig = RequestConfig( RequestMethod.GET, "/devices", query = localVariableQuery diff --git a/shared/src/commonMain/kotlin/mx/trackermap/TrackerMap/client/apis/PositionsApi.kt b/shared/src/commonMain/kotlin/mx/trackermap/TrackerMap/client/apis/PositionsApi.kt index 8c1b73e..278611f 100644 --- a/shared/src/commonMain/kotlin/mx/trackermap/TrackerMap/client/apis/PositionsApi.kt +++ b/shared/src/commonMain/kotlin/mx/trackermap/TrackerMap/client/apis/PositionsApi.kt @@ -27,8 +27,14 @@ class PositionsApi(basePath: kotlin.String = "https://demo.traccar.org/api") : A * @return kotlin.Array */ @Suppress("UNCHECKED_CAST") - suspend fun positionsGet(deviceId: kotlin.Int? = null, from: java.time.LocalDateTime? = null, to: java.time.LocalDateTime? = null, id: kotlin.Int? = null): kotlin.Array { - val localVariableQuery: MultiValueMap = mapOf("deviceId" to listOf("$deviceId"), "from" to listOf("$from"), "to" to listOf("$to"), "id" to listOf("$id")) + suspend fun positionsGet(deviceId: kotlin.Int? = null, from: LocalDateTimeAdapter? = null, to: LocalDateTimeAdapter? = null, id: kotlin.Int? = null): kotlin.Array { + val query: MutableMap> = mutableMapOf() + deviceId?.let { query["deviceId"] = listOf("$it") } + from?.let { query["from"] = listOf("$it") } + to?.let { query["to"] = listOf("$it") } + id?.let { query["id"] = listOf("$it") } + val localVariableQuery: MultiValueMap = query + val localVariableConfig = RequestConfig( RequestMethod.GET, "/positions", query = localVariableQuery diff --git a/shared/src/commonMain/kotlin/mx/trackermap/TrackerMap/client/models/Device.kt b/shared/src/commonMain/kotlin/mx/trackermap/TrackerMap/client/models/Device.kt index 3729345..f8a1642 100644 --- a/shared/src/commonMain/kotlin/mx/trackermap/TrackerMap/client/models/Device.kt +++ b/shared/src/commonMain/kotlin/mx/trackermap/TrackerMap/client/models/Device.kt @@ -11,6 +11,9 @@ */ package mx.trackermap.TrackerMap.client.models +import kotlinx.datetime.LocalDateTime +import kotlinx.serialization.Serializable + /** * @@ -29,22 +32,22 @@ package mx.trackermap.TrackerMap.client.models * @param geofenceIds * @param attributes */ +@Serializable data class Device ( - val id: kotlin.Int? = null, - val name: kotlin.String? = null, - val uniqueId: kotlin.String? = null, - val status: kotlin.String? = null, - val disabled: kotlin.Boolean? = null, + val id: Int, + val name: String, + val uniqueId: String? = null, + val status: String? = null, + val disabled: Boolean? = null, /* in IS0 8601 format. eg. `1963-11-22T18:30:00Z` */ - val lastUpdate: java.time.LocalDateTime? = null, - val positionId: kotlin.Int? = null, - val groupId: kotlin.Int? = null, - val phone: kotlin.String? = null, - val model: kotlin.String? = null, - val contact: kotlin.String? = null, - val category: kotlin.String? = null, - val geofenceIds: kotlin.Array? = null, - val attributes: kotlin.Any? = null +// val lastUpdate: LocalDateTime? = null, + val positionId: Int? = null, + val groupId: Int? = null, + val phone: String? = null, + val model: String? = null, + val contact: String? = null, + val category: String? = null, + val geofenceIds: Array? = null ) { } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/mx/trackermap/TrackerMap/client/models/Position.kt b/shared/src/commonMain/kotlin/mx/trackermap/TrackerMap/client/models/Position.kt index 0ce4ed2..d765825 100644 --- a/shared/src/commonMain/kotlin/mx/trackermap/TrackerMap/client/models/Position.kt +++ b/shared/src/commonMain/kotlin/mx/trackermap/TrackerMap/client/models/Position.kt @@ -11,6 +11,9 @@ */ package mx.trackermap.TrackerMap.client.models +import kotlinx.datetime.LocalDateTime +import kotlinx.serialization.Serializable + /** * @@ -32,28 +35,27 @@ package mx.trackermap.TrackerMap.client.models * @param network * @param attributes */ +@Serializable data class Position ( val id: kotlin.Int? = null, val deviceId: kotlin.Int? = null, val protocol: kotlin.String? = null, /* in IS0 8601 format. eg. `1963-11-22T18:30:00Z` */ - val deviceTime: java.time.LocalDateTime? = null, +// val deviceTime: LocalDateTime? = null, /* in IS0 8601 format. eg. `1963-11-22T18:30:00Z` */ - val fixTime: java.time.LocalDateTime? = null, +// val fixTime: LocalDateTime? = null, /* in IS0 8601 format. eg. `1963-11-22T18:30:00Z` */ - val serverTime: java.time.LocalDateTime? = null, +// val serverTime: LocalDateTime? = null, val outdated: kotlin.Boolean? = null, val valid: kotlin.Boolean? = null, - val latitude: java.math.BigDecimal? = null, - val longitude: java.math.BigDecimal? = null, - val altitude: java.math.BigDecimal? = null, + val latitude: Double? = null, + val longitude: Double? = null, + val altitude: Double? = null, /* in knots */ - val speed: java.math.BigDecimal? = null, - val course: java.math.BigDecimal? = null, + val speed: Double? = null, + val course: Double? = null, val address: kotlin.String? = null, - val accuracy: java.math.BigDecimal? = null, - val network: kotlin.Any? = null, - val attributes: kotlin.Any? = null + val accuracy: Double? = null ) { } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/mx/trackermap/TrackerMap/client/models/UnitInformation.kt b/shared/src/commonMain/kotlin/mx/trackermap/TrackerMap/client/models/UnitInformation.kt new file mode 100644 index 0000000..edebff0 --- /dev/null +++ b/shared/src/commonMain/kotlin/mx/trackermap/TrackerMap/client/models/UnitInformation.kt @@ -0,0 +1,6 @@ +package mx.trackermap.TrackerMap.client.models + +data class UnitInformation( + val device: Device, + val position: Position? +) \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/mx/trackermap/TrackerMap/controllers/UnitsController.kt b/shared/src/commonMain/kotlin/mx/trackermap/TrackerMap/controllers/UnitsController.kt new file mode 100644 index 0000000..9c11b79 --- /dev/null +++ b/shared/src/commonMain/kotlin/mx/trackermap/TrackerMap/controllers/UnitsController.kt @@ -0,0 +1,65 @@ +package mx.trackermap.TrackerMap.controllers + +import kotlinx.coroutines.DelicateCoroutinesApi +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.launch +import mx.trackermap.TrackerMap.client.apis.DevicesApi +import mx.trackermap.TrackerMap.client.apis.PositionsApi +import mx.trackermap.TrackerMap.client.models.Position +import mx.trackermap.TrackerMap.client.models.UnitInformation + +@DelicateCoroutinesApi +class UnitsController( + private val devicesApi: DevicesApi, + private val positionsApi: PositionsApi +) { + + val displayedUnitsFlow = MutableStateFlow>(emptyList()) + private val unitsFlow = MutableStateFlow>(emptyList()) + private val queryFlow = MutableStateFlow("") + + init { + GlobalScope.launch { + fetchUnits() + } + GlobalScope.launch { + setupFlows() + } + } + + suspend fun fetchUnits() { + val devices = devicesApi.devicesGet() + val positions = positionsApi.positionsGet() + val positionsMap: MutableMap = mutableMapOf() + + positions.forEach { position -> + position.deviceId?.let { + positionsMap[it] = position + } + } + + val units = devices.map { + UnitInformation(it, positionsMap[it.id]) + } + unitsFlow.value = units + } + + fun search(query: String) { + queryFlow.value = query + } + + suspend fun setupFlows() { + unitsFlow.combine(queryFlow) { units, query -> + units.filter { unit -> + unit.device.name.contains(query) || + unit.device.contact?.contains(query) == true || + unit.position?.address?.contains(query) == true + } + }.collect { units -> + this.displayedUnitsFlow.value = units + } + } +} \ No newline at end of file -- cgit v1.2.3