diff --git a/app/src/main/java/net/vonforst/evmap/api/goingelectric/GoingElectricApi.kt b/app/src/main/java/net/vonforst/evmap/api/goingelectric/GoingElectricApi.kt index 232d4662..0cb47ca7 100644 --- a/app/src/main/java/net/vonforst/evmap/api/goingelectric/GoingElectricApi.kt +++ b/app/src/main/java/net/vonforst/evmap/api/goingelectric/GoingElectricApi.kt @@ -29,6 +29,15 @@ interface GoingElectricApi { @GET("chargepoints/") fun getChargepointDetail(@Query("ge_id") id: Long): Call + @GET("chargepoints/pluglist/") + suspend fun getPlugs(): Response + + @GET("chargepoints/networklist/") + suspend fun getNetworks(): Response + + @GET("chargepoints/chargecardlist/") + suspend fun getChargeCards(): Response + companion object { private val cacheSize = 10L * 1024 * 1024; // 10MB diff --git a/app/src/main/java/net/vonforst/evmap/api/goingelectric/GoingElectricModel.kt b/app/src/main/java/net/vonforst/evmap/api/goingelectric/GoingElectricModel.kt index 1531a1ad..26580550 100644 --- a/app/src/main/java/net/vonforst/evmap/api/goingelectric/GoingElectricModel.kt +++ b/app/src/main/java/net/vonforst/evmap/api/goingelectric/GoingElectricModel.kt @@ -24,6 +24,12 @@ data class ChargepointList( val chargelocations: List ) +@JsonClass(generateAdapter = true) +data class StringList( + val status: String, + val result: List +) + sealed class ChargepointListItem @JsonClass(generateAdapter = true) diff --git a/app/src/main/java/net/vonforst/evmap/storage/Database.kt b/app/src/main/java/net/vonforst/evmap/storage/Database.kt index aa57c71d..b179311c 100644 --- a/app/src/main/java/net/vonforst/evmap/storage/Database.kt +++ b/app/src/main/java/net/vonforst/evmap/storage/Database.kt @@ -17,19 +17,21 @@ import net.vonforst.evmap.viewmodel.SliderFilterValue ChargeLocation::class, BooleanFilterValue::class, MultipleChoiceFilterValue::class, - SliderFilterValue::class - ], version = 3 + SliderFilterValue::class, + Plug::class + ], version = 4 ) @TypeConverters(Converters::class) abstract class AppDatabase : RoomDatabase() { abstract fun chargeLocationsDao(): ChargeLocationsDao abstract fun filterValueDao(): FilterValueDao + abstract fun plugDao(): PlugDao companion object { private lateinit var context: Context private val database: AppDatabase by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { Room.databaseBuilder(context, AppDatabase::class.java, "evmap.db") - .addMigrations(MIGRATION_2, MIGRATION_3) + .addMigrations(MIGRATION_2, MIGRATION_3, MIGRATION_4) .build() } @@ -58,5 +60,12 @@ abstract class AppDatabase : RoomDatabase() { db.endTransaction() } } + + private val MIGRATION_4 = object : Migration(3, 4) { + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("CREATE TABLE IF NOT EXISTS `Plug` (`name` TEXT NOT NULL, PRIMARY KEY(`name`))") + } + + } } } \ No newline at end of file diff --git a/app/src/main/java/net/vonforst/evmap/storage/PlugDao.kt b/app/src/main/java/net/vonforst/evmap/storage/PlugDao.kt new file mode 100644 index 00000000..e63bbb1f --- /dev/null +++ b/app/src/main/java/net/vonforst/evmap/storage/PlugDao.kt @@ -0,0 +1,49 @@ +package net.vonforst.evmap.storage + +import androidx.lifecycle.LiveData +import androidx.room.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import net.vonforst.evmap.api.goingelectric.GoingElectricApi +import java.time.Duration +import java.time.Instant + +@Entity +data class Plug(@PrimaryKey val name: String) + +@Dao +interface PlugDao { + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insert(vararg plugs: Plug) + + @Delete + suspend fun delete(vararg plugs: Plug) + + @Query("SELECT * FROM plug") + fun getAllPlugs(): LiveData> +} + +class PlugRepository( + private val api: GoingElectricApi, private val scope: CoroutineScope, + private val dao: PlugDao, private val prefs: PreferenceDataSource +) { + fun getPlugs(): LiveData> { + scope.launch { + updatePlugs() + } + return dao.getAllPlugs() + } + + private suspend fun updatePlugs() { + if (Duration.between(prefs.lastPlugUpdate, Instant.now()) < Duration.ofDays(1)) return + + val response = api.getPlugs() + if (!response.isSuccessful) return + + for (name in response.body()!!.result) { + dao.insert(Plug(name)) + } + + prefs.lastPlugUpdate = Instant.now() + } +} \ No newline at end of file diff --git a/app/src/main/java/net/vonforst/evmap/storage/PreferenceDataSource.kt b/app/src/main/java/net/vonforst/evmap/storage/PreferenceDataSource.kt index 940b4301..2e2493dd 100644 --- a/app/src/main/java/net/vonforst/evmap/storage/PreferenceDataSource.kt +++ b/app/src/main/java/net/vonforst/evmap/storage/PreferenceDataSource.kt @@ -2,6 +2,7 @@ package net.vonforst.evmap.storage import android.content.Context import androidx.preference.PreferenceManager +import java.time.Instant class PreferenceDataSource(context: Context) { val sp = PreferenceManager.getDefaultSharedPreferences(context) @@ -11,4 +12,10 @@ class PreferenceDataSource(context: Context) { set(value) { sp.edit().putBoolean("navigate_use_maps", value).apply() } + + var lastPlugUpdate: Instant + get() = Instant.ofEpochMilli(sp.getLong("last_plug_update", 0L)) + set(value) { + sp.edit().putLong("last_plug_update", value.toEpochMilli()).apply() + } } \ No newline at end of file diff --git a/app/src/main/java/net/vonforst/evmap/viewmodel/FilterViewModel.kt b/app/src/main/java/net/vonforst/evmap/viewmodel/FilterViewModel.kt index 8c87619c..3363ab61 100644 --- a/app/src/main/java/net/vonforst/evmap/viewmodel/FilterViewModel.kt +++ b/app/src/main/java/net/vonforst/evmap/viewmodel/FilterViewModel.kt @@ -5,7 +5,7 @@ import androidx.databinding.BaseObservable import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.LiveData import androidx.lifecycle.MediatorLiveData -import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.viewModelScope import androidx.room.Entity import androidx.room.PrimaryKey import net.vonforst.evmap.R @@ -13,6 +13,9 @@ import net.vonforst.evmap.adapter.Equatable import net.vonforst.evmap.api.goingelectric.Chargepoint import net.vonforst.evmap.api.goingelectric.GoingElectricApi import net.vonforst.evmap.storage.AppDatabase +import net.vonforst.evmap.storage.Plug +import net.vonforst.evmap.storage.PlugRepository +import net.vonforst.evmap.storage.PreferenceDataSource import kotlin.math.abs import kotlin.reflect.KClass import kotlin.reflect.full.cast @@ -24,41 +27,47 @@ internal fun mapPowerInverse(power: Int) = powerSteps .minBy { it.first }?.second ?: 0 internal fun getFilters( - api: GoingElectricApi, - application: Application + application: Application, + plugs: LiveData> ): LiveData>> { - val liveData = MutableLiveData>>() - liveData.value = listOf( - BooleanFilter(application.getString(R.string.filter_free), "freecharging"), - BooleanFilter(application.getString(R.string.filter_free_parking), "freeparking"), - SliderFilter( - application.getString(R.string.filter_min_power), "min_power", - powerSteps.size - 1, - mapping = ::mapPower, - inverseMapping = ::mapPowerInverse, - unit = "kW" - ), - MultipleChoiceFilter( - application.getString(R.string.filter_connectors), "connectors", - mapOf( - Chargepoint.TYPE_1 to application.getString(R.string.plug_type_1), - Chargepoint.TYPE_2 to application.getString(R.string.plug_type_2), - Chargepoint.TYPE_3 to application.getString(R.string.plug_type_3), - Chargepoint.CCS to application.getString(R.string.plug_ccs), - Chargepoint.SCHUKO to application.getString(R.string.plug_schuko), - Chargepoint.CHADEMO to application.getString(R.string.plug_chademo), - Chargepoint.SUPERCHARGER to application.getString(R.string.plug_supercharger), - Chargepoint.CEE_BLAU to application.getString(R.string.plug_cee_blau), - Chargepoint.CEE_ROT to application.getString(R.string.plug_cee_rot) - ) - ), - SliderFilter( - application.getString(R.string.filter_min_connectors), - "min_connectors", - 10 + return MediatorLiveData>>().apply { + val plugNames = mapOf( + Chargepoint.TYPE_1 to application.getString(R.string.plug_type_1), + Chargepoint.TYPE_2 to application.getString(R.string.plug_type_2), + Chargepoint.TYPE_3 to application.getString(R.string.plug_type_3), + Chargepoint.CCS to application.getString(R.string.plug_ccs), + Chargepoint.SCHUKO to application.getString(R.string.plug_schuko), + Chargepoint.CHADEMO to application.getString(R.string.plug_chademo), + Chargepoint.SUPERCHARGER to application.getString(R.string.plug_supercharger), + Chargepoint.CEE_BLAU to application.getString(R.string.plug_cee_blau), + Chargepoint.CEE_ROT to application.getString(R.string.plug_cee_rot) ) - ) - return liveData + addSource(plugs) { plugs -> + val plugMap = plugs.map { plug -> + plug.name to (plugNames[plug.name] ?: plug.name) + }.toMap() + value = listOf( + BooleanFilter(application.getString(R.string.filter_free), "freecharging"), + BooleanFilter(application.getString(R.string.filter_free_parking), "freeparking"), + SliderFilter( + application.getString(R.string.filter_min_power), "min_power", + powerSteps.size - 1, + mapping = ::mapPower, + inverseMapping = ::mapPowerInverse, + unit = "kW" + ), + MultipleChoiceFilter( + application.getString(R.string.filter_connectors), "connectors", + plugMap + ), + SliderFilter( + application.getString(R.string.filter_min_connectors), + "min_connectors", + 10 + ) + ) + } + } } @@ -84,9 +93,14 @@ class FilterViewModel(application: Application, geApiKey: String) : AndroidViewModel(application) { private var api = GoingElectricApi.create(geApiKey, context = application) private var db = AppDatabase.getInstance(application) + private var prefs = PreferenceDataSource(application) + + private val plugs: LiveData> by lazy { + PlugRepository(api, viewModelScope, db.plugDao(), prefs).getPlugs() + } private val filters: LiveData>> by lazy { - getFilters(api, application) + getFilters(application, plugs) } private val filterValues: LiveData> by lazy { diff --git a/app/src/main/java/net/vonforst/evmap/viewmodel/MapViewModel.kt b/app/src/main/java/net/vonforst/evmap/viewmodel/MapViewModel.kt index 69278e93..6b622118 100644 --- a/app/src/main/java/net/vonforst/evmap/viewmodel/MapViewModel.kt +++ b/app/src/main/java/net/vonforst/evmap/viewmodel/MapViewModel.kt @@ -11,6 +11,9 @@ import net.vonforst.evmap.api.goingelectric.ChargepointList import net.vonforst.evmap.api.goingelectric.ChargepointListItem import net.vonforst.evmap.api.goingelectric.GoingElectricApi import net.vonforst.evmap.storage.AppDatabase +import net.vonforst.evmap.storage.Plug +import net.vonforst.evmap.storage.PlugRepository +import net.vonforst.evmap.storage.PreferenceDataSource import retrofit2.Call import retrofit2.Callback import retrofit2.Response @@ -20,6 +23,7 @@ data class MapPosition(val bounds: LatLngBounds, val zoom: Float) class MapViewModel(application: Application, geApiKey: String) : AndroidViewModel(application) { private var api = GoingElectricApi.create(geApiKey, context = application) private var db = AppDatabase.getInstance(application) + private var prefs = PreferenceDataSource(application) val bottomSheetState: MutableLiveData by lazy { MutableLiveData() @@ -31,7 +35,10 @@ class MapViewModel(application: Application, geApiKey: String) : AndroidViewMode private val filterValues: LiveData> by lazy { db.filterValueDao().getFilterValues() } - private val filters = getFilters(api, application) + private val plugs: LiveData> by lazy { + PlugRepository(api, viewModelScope, db.plugDao(), prefs).getPlugs() + } + private val filters = getFilters(application, plugs) private val filtersWithValue: LiveData>> by lazy { filtersWithValue(filters, filterValues)