diff --git a/app/src/main/java/net/vonforst/evmap/adapter/DataBindingAdapters.kt b/app/src/main/java/net/vonforst/evmap/adapter/DataBindingAdapters.kt index f52334de..00b7adb1 100644 --- a/app/src/main/java/net/vonforst/evmap/adapter/DataBindingAdapters.kt +++ b/app/src/main/java/net/vonforst/evmap/adapter/DataBindingAdapters.kt @@ -132,13 +132,31 @@ class ChargepriceAdapter() : } class CheckableConnectorAdapter : DataBindingAdapter() { - private var checkedItem: Int = 0 + private var checkedItem: Int? = 0 + + var enabledConnectors: List? = 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, 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() { 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() : diff --git a/app/src/main/java/net/vonforst/evmap/api/chargeprice/ChargepriceModel.kt b/app/src/main/java/net/vonforst/evmap/api/chargeprice/ChargepriceModel.kt index 0235b596..75824e38 100644 --- a/app/src/main/java/net/vonforst/evmap/api/chargeprice/ChargepriceModel.kt +++ b/app/src/main/java/net/vonforst/evmap/api/chargeprice/ChargepriceModel.kt @@ -33,13 +33,18 @@ data class ChargepriceStation( @Json(name = "charge_points") val chargePoints: List ) { companion object { - fun fromGoingelectric(geCharger: ChargeLocation): ChargepriceStation { + fun fromGoingelectric( + geCharger: ChargeLocation, + compatibleConnectors: List + ): 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) } ) diff --git a/app/src/main/java/net/vonforst/evmap/fragment/ChargepriceFragment.kt b/app/src/main/java/net/vonforst/evmap/fragment/ChargepriceFragment.kt index aebc26d0..874c04d0 100644 --- a/app/src/main/java/net/vonforst/evmap/fragment/ChargepriceFragment.kt +++ b/app/src/main/java/net/vonforst/evmap/fragment/ChargepriceFragment.kt @@ -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 = 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 { diff --git a/app/src/main/java/net/vonforst/evmap/fragment/SettingsFragment.kt b/app/src/main/java/net/vonforst/evmap/fragment/SettingsFragment.kt index 390e6c55..3da3c37a 100644 --- a/app/src/main/java/net/vonforst/evmap/fragment/SettingsFragment.kt +++ b/app/src/main/java/net/vonforst/evmap/fragment/SettingsFragment.kt @@ -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 + } } } } 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 486b7dc7..0ed3d474 100644 --- a/app/src/main/java/net/vonforst/evmap/storage/PreferenceDataSource.kt +++ b/app/src/main/java/net/vonforst/evmap/storage/PreferenceDataSource.kt @@ -104,6 +104,13 @@ class PreferenceDataSource(val context: Context) { sp.edit().putString("chargeprice_my_vehicle", value).apply() } + var chargepriceMyVehicleDcChargeports: List? + 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) { diff --git a/app/src/main/java/net/vonforst/evmap/ui/BindingAdapters.kt b/app/src/main/java/net/vonforst/evmap/ui/BindingAdapters.kt index f68b5259..6befc9f0 100644 --- a/app/src/main/java/net/vonforst/evmap/ui/BindingAdapters.kt +++ b/app/src/main/java/net/vonforst/evmap/ui/BindingAdapters.kt @@ -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) } \ No newline at end of file diff --git a/app/src/main/java/net/vonforst/evmap/viewmodel/ChargepriceViewModel.kt b/app/src/main/java/net/vonforst/evmap/viewmodel/ChargepriceViewModel.kt index 84fa0c72..6f254765 100644 --- a/app/src/main/java/net/vonforst/evmap/viewmodel/ChargepriceViewModel.kt +++ b/app/src/main/java/net/vonforst/evmap/viewmodel/ChargepriceViewModel.kt @@ -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> by lazy { + MutableLiveData>().apply { + value = prefs.chargepriceMyVehicleDcChargeports?.map { + plugMapping.get(it) + }?.filterNotNull()?.plus(acConnectors) + } + } + + val noCompatibleConnectors: LiveData by lazy { + MediatorLiveData().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> by lazy { MutableLiveData>().apply { value = listOf(20f, 80f) @@ -41,7 +75,7 @@ class ChargepriceViewModel(application: Application, chargepriceApiKey: String) val chargePrices: MediatorLiveData>> by lazy { MediatorLiveData>>().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() }, diff --git a/app/src/main/res/layout/fragment_chargeprice.xml b/app/src/main/res/layout/fragment_chargeprice.xml index 51c1846a..de12d0e6 100644 --- a/app/src/main/res/layout/fragment_chargeprice.xml +++ b/app/src/main/res/layout/fragment_chargeprice.xml @@ -130,6 +130,20 @@ app:layout_constraintStart_toStartOf="@+id/charge_prices_list" app:layout_constraintTop_toTopOf="@+id/charge_prices_list" /> + + + + @@ -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" /> schließen Preisvergleich Could not load prices + Keiner der Anschlüsse dieser Ladestation ist mit deinem Fahrzeug kompatibel. diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c2e58464..21e70bb2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -172,7 +172,7 @@ per kWh per min Blocking fee >%s - Keine geeigneten Tarife für diese Ladestation bei Chargeprice.app gefunden. + Chargeprice.app found no charging plans compatible with this charger. powered by Chargeprice Base fee: %2$s%1$.2f/month Minimum spend: %2$s%1$.2f/month @@ -186,4 +186,5 @@ close Prices Could not load prices + None of the connectors on this charging station is compatible with your vehicle.