mirror of
https://github.com/ev-map/EVMap.git
synced 2026-04-18 13:17:38 -04:00
implement initial UI for predictions
This commit is contained in:
committed by
Johan von Forstner
parent
2625acffda
commit
b0371c1b20
60
app/src/main/java/net/vonforst/evmap/ui/BarGraphView.kt
Normal file
60
app/src/main/java/net/vonforst/evmap/ui/BarGraphView.kt
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>()
|
||||
}
|
||||
|
||||
8
app/src/main/res/drawable/bar_graph_available.xml
Normal file
8
app/src/main/res/drawable/bar_graph_available.xml
Normal 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>
|
||||
8
app/src/main/res/drawable/bar_graph_unavailable.xml
Normal file
8
app/src/main/res/drawable/bar_graph_unavailable.xml
Normal 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>
|
||||
@@ -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<ChargeLocationStatus>" />
|
||||
|
||||
<variable
|
||||
name="predictionGraph"
|
||||
type="Map<ZonedDateTime, Integer>" />
|
||||
|
||||
<variable
|
||||
name="filteredAvailability"
|
||||
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" />
|
||||
|
||||
<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"
|
||||
|
||||
@@ -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}"
|
||||
|
||||
Reference in New Issue
Block a user