Skip to content

Commit

Permalink
add ChargeLocationRepository
Browse files Browse the repository at this point in the history
encapsulates logic to load charging stations for future implementation of offline caching (#164, #97)
  • Loading branch information
johan12345 committed Sep 10, 2022
1 parent 3853536 commit 3c30481
Show file tree
Hide file tree
Showing 11 changed files with 235 additions and 223 deletions.
16 changes: 6 additions & 10 deletions app/src/google/java/net/vonforst/evmap/auto/ChargerDetailScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,13 @@ import net.vonforst.evmap.api.stringProvider
import net.vonforst.evmap.model.ChargeLocation
import net.vonforst.evmap.model.Favorite
import net.vonforst.evmap.storage.AppDatabase
import net.vonforst.evmap.storage.ChargeLocationsRepository
import net.vonforst.evmap.storage.PreferenceDataSource
import net.vonforst.evmap.ui.ChargerIconGenerator
import net.vonforst.evmap.ui.availabilityText
import net.vonforst.evmap.ui.getMarkerTint
import net.vonforst.evmap.viewmodel.Status
import net.vonforst.evmap.viewmodel.getReferenceData
import net.vonforst.evmap.viewmodel.awaitFinished
import java.time.ZoneId
import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle
Expand All @@ -50,10 +51,8 @@ class ChargerDetailScreen(ctx: CarContext, val chargerSparse: ChargeLocation) :

val prefs = PreferenceDataSource(ctx)
private val db = AppDatabase.getInstance(carContext)
private val api by lazy {
createApi(prefs.dataSource, ctx)
}
private val referenceData = api.getReferenceData(lifecycleScope, carContext)
private val repo =
ChargeLocationsRepository(createApi(prefs.dataSource, ctx), lifecycleScope, db, prefs)

private val imageSize = 128 // images should be 128dp according to docs
private val imageSizeLarge = 480 // images should be 480 x 480 dp according to docs
Expand All @@ -71,9 +70,7 @@ class ChargerDetailScreen(ctx: CarContext, val chargerSparse: ChargeLocation) :
private var favoriteUpdateJob: Job? = null

init {
referenceData.observe(this) {
loadCharger()
}
loadCharger()
}

override fun onGetTemplate(): Template {
Expand Down Expand Up @@ -356,11 +353,10 @@ class ChargerDetailScreen(ctx: CarContext, val chargerSparse: ChargeLocation) :
}

private fun loadCharger() {
val referenceData = referenceData.value ?: return
lifecycleScope.launch {
favorite = db.favoritesDao().findFavorite(chargerSparse.id, chargerSparse.dataSource)

val response = api.getChargepointDetail(referenceData, chargerSparse.id)
val response = repo.getChargepointDetail(chargerSparse.id).awaitFinished()
if (response.status == Status.SUCCESS) {
val charger = response.data!!

Expand Down
23 changes: 9 additions & 14 deletions app/src/google/java/net/vonforst/evmap/auto/MapScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,14 @@ import net.vonforst.evmap.api.stringProvider
import net.vonforst.evmap.model.ChargeLocation
import net.vonforst.evmap.model.FILTERS_FAVORITES
import net.vonforst.evmap.storage.AppDatabase
import net.vonforst.evmap.storage.ChargeLocationsRepository
import net.vonforst.evmap.storage.PreferenceDataSource
import net.vonforst.evmap.ui.availabilityText
import net.vonforst.evmap.ui.getMarkerTint
import net.vonforst.evmap.utils.distanceBetween
import net.vonforst.evmap.viewmodel.awaitFinished
import net.vonforst.evmap.viewmodel.filtersWithValue
import net.vonforst.evmap.viewmodel.getFilterValues
import net.vonforst.evmap.viewmodel.getReferenceData
import java.io.IOException
import java.time.Duration
import java.time.Instant
Expand Down Expand Up @@ -62,9 +63,8 @@ class MapScreen(ctx: CarContext, val session: EVMapSession) :
private var chargers: List<ChargeLocation>? = null
private var prefs = PreferenceDataSource(ctx)
private val db = AppDatabase.getInstance(carContext)
private val api by lazy {
createApi(prefs.dataSource, ctx)
}
private val repo =
ChargeLocationsRepository(createApi(prefs.dataSource, ctx), lifecycleScope, db, prefs)
private val searchRadius = 5 // kilometers
private val distanceUpdateThreshold = Duration.ofSeconds(15)
private val availabilityUpdateThreshold = Duration.ofMinutes(1)
Expand All @@ -74,13 +74,11 @@ class MapScreen(ctx: CarContext, val session: EVMapSession) :
ctx.constraintManager.getContentLimit(ConstraintManager.CONTENT_LIMIT_TYPE_PLACE_LIST)
} else 6

private val referenceData = api.getReferenceData(lifecycleScope, carContext)
private val filterStatus = MutableLiveData<Long>().apply {
value = prefs.filterStatus
}
private val filterValues = db.filterValueDao().getFilterValues(filterStatus, prefs.dataSource)
private val filters =
Transformations.map(referenceData) { api.getFilters(it, carContext.stringProvider()) }
private val filters = repo.getFilters(carContext.stringProvider())
private val filtersWithValue = filtersWithValue(filters, filterValues)

private val hardwareMan: CarHardwareManager by lazy {
Expand Down Expand Up @@ -307,7 +305,6 @@ class MapScreen(ctx: CarContext, val session: EVMapSession) :

private fun loadChargers() {
val location = location ?: return
val referenceData = referenceData.value ?: return
val filters = filtersWithValue.value ?: return

val searchLocation =
Expand All @@ -326,24 +323,22 @@ class MapScreen(ctx: CarContext, val session: EVMapSession) :
)
}
} else {
val response = api.getChargepointsRadius(
referenceData,
val response = repo.getChargepointsRadius(
searchLocation,
searchRadius,
zoom = 16f,
filters
)
).awaitFinished()
chargers = response.data?.filterIsInstance(ChargeLocation::class.java)
chargers?.let {
if (it.size < maxRows) {
// try again with larger radius
val response = api.getChargepointsRadius(
referenceData,
val response = repo.getChargepointsRadius(
searchLocation,
searchRadius * 10,
zoom = 16f,
filters
)
).awaitFinished()
chargers =
response.data?.filterIsInstance(ChargeLocation::class.java)
}
Expand Down
3 changes: 2 additions & 1 deletion app/src/main/java/net/vonforst/evmap/api/ChargepointApi.kt
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ interface ChargepointApi<out T : ReferenceData> {

fun getFilters(referenceData: ReferenceData, sp: StringProvider): List<Filter<FilterValue>>

fun getName(): String
val name: String
val id: String
}

interface StringProvider {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,8 @@ class GoingElectricApiWrapper(
private val clusterThreshold = 11f
val api = GoingElectricApi.create(apikey, baseurl, context)

override fun getName() = "GoingElectric.de"
override val name = "GoingElectric.de"
override val id = "going_electric"

override suspend fun getChargepoints(
referenceData: ReferenceData,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,8 @@ class OpenChargeMapApiWrapper(
private val clusterThreshold = 11
val api = OpenChargeMapApi.create(apikey, baseurl, context)

override fun getName() = "OpenChargeMap.org"
override val name = "OpenChargeMap.org"
override val id = "open_charge_map"

private fun formatMultipleChoice(value: MultipleChoiceFilterValue?) =
if (value == null || value.all) null else value.values.joinToString(",")
Expand Down
3 changes: 1 addition & 2 deletions app/src/main/java/net/vonforst/evmap/fragment/MapFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ import net.vonforst.evmap.adapter.ConnectorAdapter
import net.vonforst.evmap.adapter.DetailsAdapter
import net.vonforst.evmap.adapter.GalleryAdapter
import net.vonforst.evmap.adapter.PlaceAutocompleteAdapter
import net.vonforst.evmap.api.goingelectric.GoingElectricApiWrapper
import net.vonforst.evmap.autocomplete.ApiUnavailableException
import net.vonforst.evmap.autocomplete.PlaceWithBounds
import net.vonforst.evmap.bold
Expand Down Expand Up @@ -394,7 +393,7 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
val charger = vm.charger.value?.data
if (charger?.editUrl != null) {
(activity as? MapsActivity)?.openUrl(charger.editUrl)
if (vm.apiType == GoingElectricApiWrapper::class.java) {
if (vm.apiId == "going_electric") {
// instructions specific to GoingElectric
Toast.makeText(
requireContext(),
Expand Down
117 changes: 96 additions & 21 deletions app/src/main/java/net/vonforst/evmap/storage/ChargeLocationsDao.kt
Original file line number Diff line number Diff line change
@@ -1,34 +1,109 @@
package net.vonforst.evmap.storage

import androidx.lifecycle.LiveData
import androidx.room.*
import androidx.lifecycle.*
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import com.car2go.maps.model.LatLng
import com.car2go.maps.model.LatLngBounds
import kotlinx.coroutines.CoroutineScope
import net.vonforst.evmap.api.ChargepointApi
import net.vonforst.evmap.api.StringProvider
import net.vonforst.evmap.api.goingelectric.GoingElectricApiWrapper
import net.vonforst.evmap.api.openchargemap.OpenChargeMapApiWrapper
import net.vonforst.evmap.model.ChargeLocation
import net.vonforst.evmap.model.ChargepointListItem
import net.vonforst.evmap.model.FilterValues
import net.vonforst.evmap.model.ReferenceData
import net.vonforst.evmap.viewmodel.Resource
import net.vonforst.evmap.viewmodel.await

@Dao
interface ChargeLocationsDao {
abstract class ChargeLocationsDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(vararg locations: ChargeLocation)

@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertBlocking(vararg locations: ChargeLocation)
abstract suspend fun insert(vararg locations: ChargeLocation)

@Delete
suspend fun delete(vararg locations: ChargeLocation)
abstract suspend fun delete(vararg locations: ChargeLocation)
}

/**
* The ChargeLocationsRepository wraps the ChargepointApi and the DB to provide caching
* functionality.
*/
class ChargeLocationsRepository(
api: ChargepointApi<ReferenceData>, private val scope: CoroutineScope,
private val db: AppDatabase, private val prefs: PreferenceDataSource
) {
val api = MutableLiveData<ChargepointApi<ReferenceData>>().apply { value = api }

val referenceData = this.api.switchMap {
when (it) {
is GoingElectricApiWrapper -> {
GEReferenceDataRepository(
it,
scope,
db.geReferenceDataDao(),
prefs
).getReferenceData()
}
is OpenChargeMapApiWrapper -> {
OCMReferenceDataRepository(
it,
scope,
db.ocmReferenceDataDao(),
prefs
).getReferenceData()
}
else -> {
throw RuntimeException("no reference data implemented")
}
}
}
private val chargeLocationsDao = db.chargeLocationsDao()

fun getChargepoints(
bounds: LatLngBounds,
zoom: Float,
filters: FilterValues?
): LiveData<Resource<List<ChargepointListItem>>> {
val api = api.value!!
return liveData {
val refData = referenceData.await()
val result = api.getChargepoints(refData, bounds, zoom, filters)

emit(result)
}
}

@Query("SELECT * FROM chargelocation")
fun getAllChargeLocations(): LiveData<List<ChargeLocation>>
fun getChargepointsRadius(
location: LatLng,
radius: Int,
zoom: Float,
filters: FilterValues?
): LiveData<Resource<List<ChargepointListItem>>> {
val api = api.value!!
return liveData {
val refData = referenceData.await()
val result =
api.getChargepointsRadius(refData, location, radius, zoom, filters)

@Query("SELECT * FROM chargelocation")
suspend fun getAllChargeLocationsAsync(): List<ChargeLocation>
emit(result)
}
}

@Query("SELECT * FROM chargelocation")
fun getAllChargeLocationsBlocking(): List<ChargeLocation>
fun getChargepointDetail(
id: Long
): LiveData<Resource<ChargeLocation>> {
return liveData {
val refData = referenceData.await()
val result = api.value!!.getChargepointDetail(refData, id)
emit(result)
}
}

@Query("SELECT * FROM chargelocation WHERE lat >= :lat1 AND lat <= :lat2 AND lng >= :lng1 AND lng <= :lng2")
suspend fun getChargeLocationsInBoundsAsync(
lat1: Double,
lat2: Double,
lng1: Double,
lng2: Double
): List<ChargeLocation>
fun getFilters(sp: StringProvider) = referenceData.map { data ->
api.value!!.getFilters(data, sp)
}
}
41 changes: 5 additions & 36 deletions app/src/main/java/net/vonforst/evmap/viewmodel/Common.kt
Original file line number Diff line number Diff line change
@@ -1,46 +1,15 @@
package net.vonforst.evmap.viewmodel

import android.content.Context
import androidx.lifecycle.LiveData
import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.switchMap
import kotlinx.coroutines.CoroutineScope
import net.vonforst.evmap.api.ChargepointApi
import net.vonforst.evmap.api.goingelectric.GoingElectricApiWrapper
import net.vonforst.evmap.api.openchargemap.OpenChargeMapApiWrapper
import net.vonforst.evmap.model.*
import net.vonforst.evmap.storage.*
import net.vonforst.evmap.model.Filter
import net.vonforst.evmap.model.FilterValue
import net.vonforst.evmap.model.FilterValues
import net.vonforst.evmap.model.FilterWithValue
import net.vonforst.evmap.storage.FilterValueDao
import kotlin.reflect.full.cast

fun ChargepointApi<ReferenceData>.getReferenceData(
scope: CoroutineScope,
ctx: Context
): LiveData<out ReferenceData> {
val db = AppDatabase.getInstance(ctx)
val prefs = PreferenceDataSource(ctx)
return when (this) {
is GoingElectricApiWrapper -> {
GEReferenceDataRepository(
this,
scope,
db.geReferenceDataDao(),
prefs
).getReferenceData()
}
is OpenChargeMapApiWrapper -> {
OCMReferenceDataRepository(
this,
scope,
db.ocmReferenceDataDao(),
prefs
).getReferenceData()
}
else -> {
throw RuntimeException("no reference data implemented")
}
}
}

fun filtersWithValue(
filters: LiveData<List<Filter<FilterValue>>>,
filterValues: LiveData<List<FilterValue>>
Expand Down
16 changes: 6 additions & 10 deletions app/src/main/java/net/vonforst/evmap/viewmodel/FilterViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,17 @@ import net.vonforst.evmap.api.createApi
import net.vonforst.evmap.api.stringProvider
import net.vonforst.evmap.model.*
import net.vonforst.evmap.storage.AppDatabase
import net.vonforst.evmap.storage.ChargeLocationsRepository
import net.vonforst.evmap.storage.FilterProfile
import net.vonforst.evmap.storage.PreferenceDataSource


class FilterViewModel(application: Application) : AndroidViewModel(application) {
private var db = AppDatabase.getInstance(application)
private var prefs = PreferenceDataSource(application)
private var api: ChargepointApi<ReferenceData> = createApi(prefs.dataSource, application)

private val referenceData = api.getReferenceData(viewModelScope, application)
private val filters = MediatorLiveData<List<Filter<FilterValue>>>().apply {
addSource(referenceData) { data ->
value = api.getFilters(data, application.stringProvider())
}
}
private val db = AppDatabase.getInstance(application)
private val prefs = PreferenceDataSource(application)
private val api: ChargepointApi<ReferenceData> = createApi(prefs.dataSource, application)
private val repo = ChargeLocationsRepository(api, viewModelScope, db, prefs)
private val filters = repo.getFilters(application.stringProvider())

private val filterValues: LiveData<List<FilterValue>> by lazy {
db.filterValueDao().getFilterValues(FILTERS_CUSTOM, prefs.dataSource)
Expand Down
Loading

0 comments on commit 3c30481

Please sign in to comment.