From b0371c1b208aedb26c83a84929ba61ba76c17bf6 Mon Sep 17 00:00:00 2001 From: johan12345 Date: Sun, 18 Sep 2022 19:14:28 +0200 Subject: [PATCH] implement initial UI for predictions --- .../net/vonforst/evmap/ui/BarGraphView.kt | 60 ++++++++++++++++++ .../vonforst/evmap/viewmodel/MapViewModel.kt | 62 +++++++++++++++++++ .../main/res/drawable/bar_graph_available.xml | 8 +++ .../res/drawable/bar_graph_unavailable.xml | 8 +++ app/src/main/res/layout/detail_view.xml | 25 +++++++- app/src/main/res/layout/fragment_map.xml | 1 + 6 files changed, 163 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/net/vonforst/evmap/ui/BarGraphView.kt create mode 100644 app/src/main/res/drawable/bar_graph_available.xml create mode 100644 app/src/main/res/drawable/bar_graph_unavailable.xml diff --git a/app/src/main/java/net/vonforst/evmap/ui/BarGraphView.kt b/app/src/main/java/net/vonforst/evmap/ui/BarGraphView.kt new file mode 100644 index 00000000..e5e3fbfb --- /dev/null +++ b/app/src/main/java/net/vonforst/evmap/ui/BarGraphView.kt @@ -0,0 +1,60 @@ +package net.vonforst.evmap.ui + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Rect +import android.util.AttributeSet +import android.view.View +import androidx.appcompat.content.res.AppCompatResources +import net.vonforst.evmap.R +import java.time.ZonedDateTime +import kotlin.math.roundToInt + +class BarGraphView(context: Context, attrs: AttributeSet) : View(context, attrs) { + + var zeroHeight = 4 * context.resources.displayMetrics.density + var barWidth = 16 * context.resources.displayMetrics.density + var barMargin = 2 * context.resources.displayMetrics.density + + var barDrawableUnavailable = + AppCompatResources.getDrawable(context, R.drawable.bar_graph_unavailable)!! + var barDrawableAvailable = + AppCompatResources.getDrawable(context, R.drawable.bar_graph_available)!! + + var data: Map? = null + set(value) { + field = value + invalidate() + } + + var activeAlpha = 0.87f + var inactiveAlpha = 0.60f + + private lateinit var graphBounds: Rect + + override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { + graphBounds = Rect(paddingLeft, paddingTop, w - paddingRight, h - paddingBottom) + } + + override fun onDraw(canvas: Canvas) { + val data = data ?: return + val maxValue = data.maxOf { it.value } + + canvas.apply { + data.toSortedMap().entries.forEachIndexed { i, (t, v) -> + val drawable = if (v > 0) barDrawableAvailable else barDrawableUnavailable + + val height = + zeroHeight + (graphBounds.height() - zeroHeight) * v.toFloat() / maxValue + drawable.setBounds( + graphBounds.left + ((barWidth + barMargin) * i).roundToInt(), + graphBounds.bottom - height.roundToInt(), + graphBounds.left + ((barWidth + barMargin) * i + barWidth).roundToInt(), + graphBounds.bottom + ) + drawable.alpha = (inactiveAlpha * 255).roundToInt() + drawable.draw(canvas) + } + } + } +} \ No newline at end of file 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 ff4ff9a1..829a2d4c 100644 --- a/app/src/main/java/net/vonforst/evmap/viewmodel/MapViewModel.kt +++ b/app/src/main/java/net/vonforst/evmap/viewmodel/MapViewModel.kt @@ -10,9 +10,14 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import kotlinx.parcelize.Parcelize +import net.vonforst.evmap.R +import net.vonforst.evmap.api.availability.AvailabilityDetectorException import net.vonforst.evmap.api.availability.ChargeLocationStatus import net.vonforst.evmap.api.availability.getAvailability import net.vonforst.evmap.api.createApi +import net.vonforst.evmap.api.fronyx.FronyxApi +import net.vonforst.evmap.api.fronyx.FronyxEvseIdResponse +import net.vonforst.evmap.api.fronyx.FronyxStatus import net.vonforst.evmap.api.goingelectric.GEChargepoint import net.vonforst.evmap.api.openchargemap.OCMConnection import net.vonforst.evmap.api.openchargemap.OCMReferenceData @@ -25,6 +30,9 @@ import net.vonforst.evmap.storage.FilterProfile import net.vonforst.evmap.storage.PreferenceDataSource import net.vonforst.evmap.ui.cluster import net.vonforst.evmap.utils.distanceBetween +import retrofit2.HttpException +import java.io.IOException +import java.time.ZonedDateTime @Parcelize data class MapPosition(val bounds: LatLngBounds, val zoom: Float) : Parcelable @@ -202,6 +210,60 @@ class MapViewModel(application: Application, private val state: SavedStateHandle addSource(filteredMinPower, callback) } } + + val predictionApi = FronyxApi.create(application.getString(R.string.fronyx_key)) + + val prediction: LiveData>> by lazy { + availability.switchMap { av -> + av?.data?.evseIds?.let { evseIds -> + liveData { + emit(Resource.loading(null)) + + val allEvseIds = evseIds.flatMap { it.value } + + try { + val result = allEvseIds.map { + predictionApi.getPredictionsForEvseId(it) + } + + emit(Resource.success(result)) + println(result) + } catch (e: IOException) { + emit(Resource.error(e.message, null)) + e.printStackTrace() + } catch (e: HttpException) { + emit(Resource.error(e.message, null)) + e.printStackTrace() + } catch (e: AvailabilityDetectorException) { + emit(Resource.error(e.message, null)) + e.printStackTrace() + } + } + } ?: liveData { emit(Resource.success(null)) } + } + } + + val predictionGraph: LiveData?> by lazy { + prediction.map { + it.data?.let { responses -> + if (responses.isEmpty()) { + null + } else { + val timestamps = responses[0].predictions.map { it.timestamp } + // TODO: implement this in a more elegant way: first ensure that all timestamps are the same, then take them in order + timestamps.associateWith { ts -> + responses.sumOf { + val av = + it.predictions.find { it.timestamp == ts }?.status == FronyxStatus.AVAILABLE + val count = if (av) 1 else 0 + count + } + } + } + } + } + } + val myLocationEnabled: MutableLiveData by lazy { MutableLiveData() } diff --git a/app/src/main/res/drawable/bar_graph_available.xml b/app/src/main/res/drawable/bar_graph_available.xml new file mode 100644 index 00000000..a1ad80b7 --- /dev/null +++ b/app/src/main/res/drawable/bar_graph_available.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bar_graph_unavailable.xml b/app/src/main/res/drawable/bar_graph_unavailable.xml new file mode 100644 index 00000000..3d94953e --- /dev/null +++ b/app/src/main/res/drawable/bar_graph_unavailable.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/detail_view.xml b/app/src/main/res/layout/detail_view.xml index 75889b49..a88c71aa 100644 --- a/app/src/main/res/layout/detail_view.xml +++ b/app/src/main/res/layout/detail_view.xml @@ -5,6 +5,10 @@ + + + + @@ -39,6 +43,10 @@ name="availability" type="Resource<ChargeLocationStatus>" /> + + @@ -325,7 +333,22 @@ app:icon="@drawable/ic_chargeprice" app:layout_constraintEnd_toStartOf="@+id/guideline2" app:layout_constraintStart_toStartOf="@+id/guideline" - app:layout_constraintTop_toBottomOf="@+id/textView13" /> + app:layout_constraintTop_toBottomOf="@+id/prediction" /> + +