implement initial UI for predictions

This commit is contained in:
johan12345
2022-09-18 19:14:28 +02:00
committed by Johan von Forstner
parent 2625acffda
commit b0371c1b20
6 changed files with 163 additions and 1 deletions

View File

@@ -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<ZonedDateTime, Int>? = 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)
}
}
}
}

View File

@@ -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<Resource<List<FronyxEvseIdResponse>>> 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<Map<ZonedDateTime, Int>?> 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<Boolean> by lazy {
MutableLiveData<Boolean>()
}

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/available" />
<corners
android:topLeftRadius="2dp"
android:topRightRadius="2dp" />
</shape>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/unavailable" />
<corners
android:topLeftRadius="2dp"
android:topRightRadius="2dp" />
</shape>

View File

@@ -5,6 +5,10 @@
<data>
<import type="java.util.Map" />
<import type="java.time.ZonedDateTime" />
<import type="net.vonforst.evmap.model.ChargeLocation" />
<import type="net.vonforst.evmap.model.Chargepoint" />
@@ -39,6 +43,10 @@
name="availability"
type="Resource&lt;ChargeLocationStatus&gt;" />
<variable
name="predictionGraph"
type="Map&lt;ZonedDateTime, Integer&gt;" />
<variable
name="filteredAvailability"
type="Resource&lt;ChargeLocationStatus&gt;" />
@@ -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" />
<net.vonforst.evmap.ui.BarGraphView
android:id="@+id/prediction"
android:layout_width="0dp"
android:layout_height="80dp"
android:layout_marginTop="16dp"
app:goneUnless="@{predictionGraph != null}"
app:data="@{predictionGraph}"
app:layout_constraintEnd_toStartOf="@+id/guideline2"
app:layout_constraintStart_toStartOf="@+id/guideline"
app:layout_constraintTop_toBottomOf="@+id/textView13"
tools:itemCount="3"
tools:layoutManager="LinearLayoutManager"
tools:listitem="@layout/item_connector"
tools:orientation="horizontal" />
<ImageView
android:id="@+id/imgVerified"

View File

@@ -195,6 +195,7 @@
app:charger="@{vm.charger}"
app:availability="@{vm.availability}"
app:filteredAvailability="@{vm.filteredAvailability}"
app:predictionGraph="@{vm.predictionGraph}"
app:chargeCards="@{vm.chargeCardMap}"
app:filteredChargeCards="@{vm.filteredChargeCards}"
app:distance="@{vm.chargerDistance}"