Chargeprice: detect which connectors are compatible with vehicle before sending request (#82)

This commit is contained in:
johan12345
2021-04-20 23:20:04 +02:00
parent 52af10d549
commit c106bc40cc
11 changed files with 143 additions and 26 deletions

View File

@@ -132,13 +132,31 @@ class ChargepriceAdapter() :
}
class CheckableConnectorAdapter : DataBindingAdapter<Chargepoint>() {
private var checkedItem: Int = 0
private var checkedItem: Int? = 0
var enabledConnectors: List<String>? = null
get() = field
set(value) {
field = value
checkedItem?.let {
if (value != null && getItem(it).type !in value) {
val index = currentList.indexOfFirst {
it.type in value
}
checkedItem = if (index == -1) null else index
onCheckedItemChangedListener?.invoke(getCheckedItem())
}
}
notifyDataSetChanged()
}
override fun getItemViewType(position: Int): Int = R.layout.item_connector_button
override fun onBindViewHolder(holder: ViewHolder<Chargepoint>, position: Int) {
super.bind(holder, getItem(position))
val item = getItem(position)
super.bind(holder, item)
val binding = holder.binding as ItemConnectorButtonBinding
binding.enabled = enabledConnectors?.let { item.type in it } ?: true
val root = binding.root as CheckableConstraintLayout
root.isChecked = checkedItem == position
root.setOnClickListener {
@@ -148,18 +166,18 @@ class CheckableConnectorAdapter : DataBindingAdapter<Chargepoint>() {
if (checked) {
checkedItem = position
notifyDataSetChanged()
onCheckedItemChangedListener?.invoke(getCheckedItem())
onCheckedItemChangedListener?.invoke(getCheckedItem()!!)
}
}
}
fun getCheckedItem(): Chargepoint = getItem(checkedItem)
fun getCheckedItem(): Chargepoint? = checkedItem?.let { getItem(it) }
fun setCheckedItem(item: Chargepoint) {
checkedItem = currentList.indexOf(item)
fun setCheckedItem(item: Chargepoint?) {
checkedItem = item?.let { currentList.indexOf(item) } ?: null
}
var onCheckedItemChangedListener: ((Chargepoint) -> Unit)? = null
var onCheckedItemChangedListener: ((Chargepoint?) -> Unit)? = null
}
class ChargepriceTagsAdapter() :

View File

@@ -33,13 +33,18 @@ data class ChargepriceStation(
@Json(name = "charge_points") val chargePoints: List<ChargepriceChargepoint>
) {
companion object {
fun fromGoingelectric(geCharger: ChargeLocation): ChargepriceStation {
fun fromGoingelectric(
geCharger: ChargeLocation,
compatibleConnectors: List<String>
): ChargepriceStation {
return ChargepriceStation(
geCharger.coordinates.lng,
geCharger.coordinates.lat,
geCharger.address.country,
geCharger.network,
geCharger.chargepoints.map {
geCharger.chargepoints.filter {
it.type in compatibleConnectors
}.map {
ChargepriceChargepoint(it.power, it.type)
}
)

View File

@@ -107,14 +107,10 @@ class ChargepriceFragment : DialogFragment() {
)
}
vm.chargepriceMetaForChargepoint.observe(viewLifecycleOwner) {
chargepriceAdapter.meta = it.data
chargepriceAdapter.meta = it?.data
}
val connectorsAdapter = CheckableConnectorAdapter()
binding.connectorsList.apply {
adapter = connectorsAdapter
layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
}
val observer: Observer<Chargepoint> = Observer {
connectorsAdapter.setCheckedItem(it)
@@ -126,6 +122,15 @@ class ChargepriceFragment : DialogFragment() {
vm.chargepoint.observe(viewLifecycleOwner, observer)
}
vm.vehicleCompatibleConnectors.observe(viewLifecycleOwner) {
connectorsAdapter.enabledConnectors = it
}
binding.connectorsList.apply {
adapter = connectorsAdapter
layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
}
binding.imgChargepriceLogo.setOnClickListener {
(requireActivity() as MapsActivity).openUrl("https://www.chargeprice.app/?poi_id=${charger.id}&poi_source=going_electric")
}
@@ -150,8 +155,8 @@ class ChargepriceFragment : DialogFragment() {
}
}
vm.chargePricesForChargepoint.observe(viewLifecycleOwner, Observer { res ->
when (res.status) {
vm.chargePricesForChargepoint.observe(viewLifecycleOwner) { res ->
when (res?.status) {
Status.ERROR -> {
connectionErrorSnackbar?.dismiss()
connectionErrorSnackbar = Snackbar
@@ -166,13 +171,13 @@ class ChargepriceFragment : DialogFragment() {
}
connectionErrorSnackbar!!.show()
}
Status.SUCCESS -> {
Status.SUCCESS, null -> {
connectionErrorSnackbar?.dismiss()
}
Status.LOADING -> {
}
}
})
}
}
companion object {

View File

@@ -82,8 +82,11 @@ class SettingsFragment : PreferenceFragmentCompat(),
}
"chargeprice_my_vehicle" -> {
vm.vehicles.value?.data?.let { cars ->
myVehiclePreference.summary = cars.find { it.id == prefs.chargepriceMyVehicle }
?.let { "${it.brand} ${it.name}" }
val vehicle = cars.find { it.id == prefs.chargepriceMyVehicle }
vehicle?.let {
myVehiclePreference.summary = "${it.brand} ${it.name}"
prefs.chargepriceMyVehicleDcChargeports = it.dcChargePorts
}
}
}
}

View File

@@ -104,6 +104,13 @@ class PreferenceDataSource(val context: Context) {
sp.edit().putString("chargeprice_my_vehicle", value).apply()
}
var chargepriceMyVehicleDcChargeports: List<String>?
get() = sp.getString("chargeprice_my_vehicle_dc_chargeports", null)?.split(",")
set(value) {
sp.edit().putString("chargeprice_my_vehicle_dc_chargeports", value?.joinToString(","))
.apply()
}
var chargepriceNoBaseFee: Boolean
get() = sp.getBoolean("chargeprice_no_base_fee", false)
set(value) {

View File

@@ -7,6 +7,7 @@ import android.view.View
import android.view.ViewGroup.MarginLayoutParams
import android.widget.ImageView
import android.widget.TextView
import androidx.annotation.ColorInt
import androidx.core.content.ContextCompat
import androidx.core.content.res.use
import androidx.core.text.HtmlCompat
@@ -272,4 +273,22 @@ fun setRangeSliderListeners(slider: RangeSlider, attrChange: InverseBindingListe
slider.addOnChangeListener { _, _, _ ->
attrChange.onChange()
}
}
@ColorInt
fun colorEnabled(ctx: Context, enabled: Boolean): Int {
val attr = if (enabled) {
android.R.attr.textColorSecondary
} else {
android.R.attr.textColorHint
}
val typedValue = ctx.obtainStyledAttributes(intArrayOf(attr))
val color = typedValue.getColor(0, 0)
typedValue.recycle()
return color
}
@BindingAdapter("app:tint")
fun setImageTintList(view: ImageView, @ColorInt color: Int) {
view.imageTintList = ColorStateList.valueOf(color)
}

View File

@@ -32,6 +32,40 @@ class ChargepriceViewModel(application: Application, chargepriceApiKey: String)
}
}
private val acConnectors = listOf(
Chargepoint.CEE_BLAU,
Chargepoint.CEE_ROT,
Chargepoint.SCHUKO,
Chargepoint.TYPE_1,
Chargepoint.TYPE_2
)
private val plugMapping = mapOf(
"ccs" to Chargepoint.CCS,
"tesla_suc" to Chargepoint.SUPERCHARGER,
"tesla_ccs" to Chargepoint.CCS,
"chademo" to Chargepoint.CHADEMO
)
val vehicleCompatibleConnectors: LiveData<List<String>> by lazy {
MutableLiveData<List<String>>().apply {
value = prefs.chargepriceMyVehicleDcChargeports?.map {
plugMapping.get(it)
}?.filterNotNull()?.plus(acConnectors)
}
}
val noCompatibleConnectors: LiveData<Boolean> by lazy {
MediatorLiveData<Boolean>().apply {
value = false
listOf(charger, vehicleCompatibleConnectors).forEach {
addSource(it) {
val charger = charger.value ?: return@addSource
val connectors = vehicleCompatibleConnectors.value ?: return@addSource
value = !charger.chargepoints.map { it.type }.any { it in connectors }
}
}
}
}
val batteryRange: MutableLiveData<List<Float>> by lazy {
MutableLiveData<List<Float>>().apply {
value = listOf(20f, 80f)
@@ -41,7 +75,7 @@ class ChargepriceViewModel(application: Application, chargepriceApiKey: String)
val chargePrices: MediatorLiveData<Resource<List<ChargePrice>>> by lazy {
MediatorLiveData<Resource<List<ChargePrice>>>().apply {
value = Resource.loading(null)
listOf(charger, vehicle, batteryRange).forEach {
listOf(charger, vehicle, batteryRange, vehicleCompatibleConnectors).forEach {
addSource(it) {
loadPrices()
}
@@ -111,18 +145,21 @@ class ChargepriceViewModel(application: Application, chargepriceApiKey: String)
chargePrices.value = Resource.loading(null)
val geCharger = charger.value
val car = vehicle.value
if (geCharger == null || car == null) {
val compatibleConnectors = vehicleCompatibleConnectors.value
if (geCharger == null || car == null || compatibleConnectors == null) {
chargePrices.value = Resource.error(null, null)
return
}
val cpStation = ChargepriceStation.fromGoingelectric(geCharger, compatibleConnectors)
loadPricesJob?.cancel()
loadPricesJob = viewModelScope.launch {
delay(800)
try {
val result = api.getChargePrices(ChargepriceRequest().apply {
dataAdapter = "going_electric"
station = ChargepriceStation.fromGoingelectric(geCharger)
station = cpStation
vehicle = HasOne(car)
options = ChargepriceOptions(
batteryRange = batteryRange.value!!.map { it.toDouble() },

View File

@@ -130,6 +130,20 @@
app:layout_constraintStart_toStartOf="@+id/charge_prices_list"
app:layout_constraintTop_toTopOf="@+id/charge_prices_list" />
<TextView
android:id="@+id/textView9"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:gravity="center_horizontal"
android:text="@string/chargeprice_no_compatible_connectors"
app:goneUnless="@{vm.noCompatibleConnectors}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/charge_prices_list"
app:layout_constraintTop_toTopOf="@+id/charge_prices_list" />
<TextView
android:id="@+id/textView3"
android:layout_width="0dp"

View File

@@ -12,6 +12,10 @@
<variable
name="item"
type="Chargepoint" />
<variable
name="enabled"
type="boolean" />
</data>
<net.vonforst.evmap.ui.CheckableConstraintLayout
@@ -19,7 +23,7 @@
android:layout_height="wrap_content"
android:background="@drawable/button_outline"
android:foreground="?selectableItemBackground"
android:clickable="true"
android:clickable="@{enabled}"
android:focusable="true"
android:layout_margin="4dp">
@@ -36,7 +40,8 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:tint="?colorControlNormal"
app:tint="@{BindingAdaptersKt.colorEnabled(context, enabled)}"
tools:tint="?colorControlNormal"
tools:srcCompat="@drawable/ic_connector_typ2" />
<TextView
@@ -48,6 +53,7 @@
android:layout_marginEnd="4dp"
android:text="@{String.format(&quot;× %d&quot;, item.count)}"
android:textAppearance="@style/TextAppearance.MaterialComponents.Caption"
android:textColor="@{BindingAdaptersKt.colorEnabled(context, enabled)}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/imageView"
app:layout_constraintTop_toTopOf="@+id/imageView"
@@ -61,6 +67,7 @@
android:layout_marginBottom="4dp"
android:text="@{item.formatPower()}"
android:textAppearance="@style/TextAppearance.MaterialComponents.Caption"
android:textColor="@{BindingAdaptersKt.colorEnabled(context, enabled)}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"

View File

@@ -187,4 +187,5 @@
<string name="close">schließen</string>
<string name="chargeprice_title">Preisvergleich</string>
<string name="chargeprice_connection_error">Could not load prices</string>
<string name="chargeprice_no_compatible_connectors">Keiner der Anschlüsse dieser Ladestation ist mit deinem Fahrzeug kompatibel.</string>
</resources>

View File

@@ -172,7 +172,7 @@
<string name="chargeprice_per_kwh">per kWh</string>
<string name="chargeprice_per_minute">per min</string>
<string name="chargeprice_blocking_fee">Blocking fee >%s</string>
<string name="chargeprice_no_tariffs_found">Keine geeigneten Tarife für diese Ladestation bei Chargeprice.app gefunden.</string>
<string name="chargeprice_no_tariffs_found">Chargeprice.app found no charging plans compatible with this charger.</string>
<string name="powered_by_chargeprice">powered by Chargeprice</string>
<string name="chargeprice_base_fee">Base fee: %2$s%1$.2f/month</string>
<string name="chargeprice_min_spend">Minimum spend: %2$s%1$.2f/month</string>
@@ -186,4 +186,5 @@
<string name="close">close</string>
<string name="chargeprice_title">Prices</string>
<string name="chargeprice_connection_error">Could not load prices</string>
<string name="chargeprice_no_compatible_connectors">None of the connectors on this charging station is compatible with your vehicle.</string>
</resources>