Add details about voltage and amperage of chargers

shown in connector details dialog
(not supported by GoingElectric)
fixes #151
This commit is contained in:
johan12345
2023-12-17 22:23:58 +01:00
parent 4b38c0de2d
commit c6395feaa3
10 changed files with 232 additions and 74 deletions

View File

@@ -97,7 +97,6 @@ class ConnectorAdapter : DataBindingAdapter<ConnectorAdapter.ChargepointWithAvai
class ConnectorDetailsAdapter : DataBindingAdapter<ConnectorDetailsAdapter.ConnectorDetails>() {
data class ConnectorDetails(
val chargepoint: Chargepoint,
val status: ChargepointStatus?,
val evseId: String?,
val label: String?,
@@ -108,7 +107,7 @@ class ConnectorDetailsAdapter : DataBindingAdapter<ConnectorDetailsAdapter.Conne
override fun getItemViewType(position: Int): Int = R.layout.dialog_connector_details_item
}
class ChargepriceAdapter() :
class ChargepriceAdapter :
DataBindingAdapter<ChargePrice>() {
val viewPool = RecyclerView.RecycledViewPool()

View File

@@ -159,7 +159,9 @@ data class OCMConnection(
fun convert(refData: OCMReferenceData) = Chargepoint(
convertConnectionTypeFromOCM(connectionTypeId, refData),
power,
quantity ?: 1
quantity ?: 1,
voltage?.toDouble(),
amps?.toDouble()
)
companion object {

View File

@@ -5,12 +5,19 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.os.BundleCompat
import androidx.databinding.DataBindingUtil
import androidx.recyclerview.widget.ConcatAdapter
import androidx.recyclerview.widget.LinearLayoutManager
import net.vonforst.evmap.R
import net.vonforst.evmap.adapter.ConnectorAdapter
import net.vonforst.evmap.adapter.ConnectorDetailsAdapter
import net.vonforst.evmap.adapter.SingleViewAdapter
import net.vonforst.evmap.api.availability.ChargeLocationStatus
import net.vonforst.evmap.api.availability.ChargepointStatus
import net.vonforst.evmap.databinding.DialogConnectorDetailsBinding
import net.vonforst.evmap.databinding.DialogConnectorDetailsHeaderBinding
import net.vonforst.evmap.databinding.DialogDataSourceSelectBinding
import net.vonforst.evmap.databinding.FragmentChargepriceHeaderBinding
import net.vonforst.evmap.model.Chargepoint
import net.vonforst.evmap.model.FILTERS_DISABLED
import net.vonforst.evmap.storage.PreferenceDataSource
@@ -23,7 +30,7 @@ class ConnectorDetailsDialog : MaterialDialogFragment() {
companion object {
fun getInstance(
chargepoint: Chargepoint,
status: List<ChargepointStatus>,
status: List<ChargepointStatus>?,
evseIds: List<String>? = null,
labels: List<String?>? = null,
lastChange: List<Instant?>? = null
@@ -31,7 +38,7 @@ class ConnectorDetailsDialog : MaterialDialogFragment() {
val dialog = ConnectorDetailsDialog()
dialog.arguments = Bundle().apply {
putParcelable("chargepoint", chargepoint)
putParcelableArrayList("status", ArrayList(status))
putParcelableArrayList("status", status?.let { ArrayList(status) })
putStringArrayList("evseIds", evseIds?.let { ArrayList(it) })
putStringArrayList("labels", labels?.let { ArrayList(it) })
putSerializable("lastChange", lastChange?.let { ArrayList(it) })
@@ -62,25 +69,39 @@ class ConnectorDetailsDialog : MaterialDialogFragment() {
val labels = args.getStringArrayList("labels")
val lastChange = args.getSerializable("lastChange") as ArrayList<Instant>?
val items = List(chargepoint.count) { i ->
ConnectorDetailsAdapter.ConnectorDetails(
chargepoint,
status?.get(i),
evseIds?.get(i),
labels?.get(i),
lastChange?.get(i)
)
}.sortedBy { it.evseId ?: it.label }
val items = if (status != null) {
List(chargepoint.count) { i ->
ConnectorDetailsAdapter.ConnectorDetails(
status.get(i),
evseIds?.get(i),
labels?.get(i),
lastChange?.get(i)
)
}.sortedBy { it.evseId ?: it.label }
} else emptyList()
binding.list.apply {
adapter = ConnectorDetailsAdapter().apply {
submitList(items)
}
itemAnimator = null
layoutManager =
LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
}
val headerBinding = DataBindingUtil.inflate<DialogConnectorDetailsHeaderBinding>(
LayoutInflater.from(context),
R.layout.dialog_connector_details_header, binding.list, false
)
if (items.isEmpty()) headerBinding.divider.visibility = View.GONE
val joinedAdapter = ConcatAdapter(
SingleViewAdapter(headerBinding.root),
ConnectorDetailsAdapter().apply {
submitList(items)
}
)
binding.list.adapter = joinedAdapter
headerBinding.item = ConnectorAdapter.ChargepointWithAvailability(chargepoint, status)
binding.btnClose.setOnClickListener {
dismiss()
}

View File

@@ -816,18 +816,14 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
binding.detailView.connectors.apply {
adapter = ConnectorAdapter().apply {
onClickListener = { item ->
vm.availability.value?.data?.let {
item.status?.let { status ->
val dialog = ConnectorDetailsDialog.getInstance(
item.chargepoint,
status,
it.evseIds?.get(item.chargepoint),
it.labels?.get(item.chargepoint),
it.lastChange?.get(item.chargepoint),
)
dialog.show(parentFragmentManager, null)
}
}
val dialog = ConnectorDetailsDialog.getInstance(
item.chargepoint,
item.status,
vm.availability.value?.data?.evseIds?.get(item.chargepoint),
vm.availability.value?.data?.labels?.get(item.chargepoint),
vm.availability.value?.data?.lastChange?.get(item.chargepoint),
)
dialog.show(parentFragmentManager, null)
}
}
itemAnimator = null

View File

@@ -18,7 +18,7 @@ import java.time.LocalDate
import java.time.LocalTime
import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle
import java.util.*
import kotlin.math.abs
sealed class ChargepointListItem
@@ -127,10 +127,13 @@ data class ChargeLocation(
get() {
val variants = chargepoints.distinctBy { it.power to it.type }
return variants.map { variant ->
val count = chargepoints
val filtered = chargepoints
.filter { it.type == variant.type && it.power == variant.power }
.sumOf { it.count }
Chargepoint(variant.type, variant.power, count)
val count = filtered.sumOf { it.count }
Chargepoint(variant.type, variant.power, count,
filtered.map { it.voltage }.distinct().singleOrNull(),
filtered.map { it.current }.distinct().singleOrNull()
)
}
}
@@ -390,24 +393,29 @@ data class Address(
@Parcelize
@JsonClass(generateAdapter = true)
data class Chargepoint(
// The chargepoint type (use one of the constants in the companion object)
// The connector type (use one of the constants in the companion object if applicable)
val type: String,
// Power in kW (or null if unknown)
val power: Double?,
// How many instances of this plug/socket are available?
val count: Int,
// Max current in A (or null if unknown)
val current: Double? = null,
// Max voltage in V (or null if unknown).
// note that for DC chargers: current * voltage may be larger than power
// (each of the three can be separately limited)
val voltage: Double? = null
) : Equatable, Parcelable {
fun hasKnownPower(): Boolean = power != null
fun hasKnownVoltageAndCurrent(): Boolean = voltage != null && current != null
/**
* If chargepoint power is defined, format it into a string.
* Otherwise, return null.
*/
fun formatPower(): String? {
if (power == null) {
return null
}
val powerFmt = if (power - power.toInt() == 0.0) {
if (power == null) return null
val powerFmt = if (abs(power - power.toInt()) < 0.1) {
"%.0f".format(power)
} else {
"%.1f".format(power)
@@ -415,6 +423,12 @@ data class Chargepoint(
return "$powerFmt kW"
}
fun formatVoltageAndCurrent(): String? {
if (current == null || voltage == null) return null
return "%.0f V · %.0f A".format(voltage, current)
}
companion object {
const val TYPE_1 = "Type 1"
const val TYPE_2_UNKNOWN = "Type 2 (either plug or socket)"

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="oval">
<solid android:color="#FFFFFFFF" />
<size
android:height="12dp"
android:width="12dp" />
</shape>
</item>
</layer-list>

View File

@@ -1,28 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="8dp"
android:paddingBottom="8dp">
android:layout_height="wrap_content">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/list"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="0dp"
android:paddingTop="8dp"
android:paddingBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:listitem="@layout/dialog_connector_details_item" />
tools:itemCount="1"
tools:listitem="@layout/dialog_connector_details_preview" />
<ImageButton
android:id="@+id/btnClose"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="0dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="12dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/close"

View File

@@ -0,0 +1,115 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<import type="net.vonforst.evmap.adapter.ConnectorAdapter.ChargepointWithAvailability" />
<import type="net.vonforst.evmap.ui.BindingAdaptersKt" />
<import type="net.vonforst.evmap.api.UtilsKt" />
<import type="net.vonforst.evmap.api.ChargepointApiKt" />
<variable
name="item"
type="ChargepointWithAvailability" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?attr/listPreferredItemHeightSmall"
android:layout_marginStart="?attr/dialogPreferredPadding"
android:layout_marginEnd="?attr/dialogPreferredPadding">
<ImageView
android:id="@+id/imageView"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="16dp"
android:contentDescription="@{item.chargepoint.type}"
android:tintMode="src_in"
app:connectorIcon="@{item.chargepoint.type}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@+id/divider"
app:tintAvailability="@{item.status}"
tools:srcCompat="@drawable/ic_connector_typ2"
tools:tint="@color/available" />
<TextView
android:id="@+id/textView5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="38dp"
android:layout_marginTop="38dp"
android:text="@{String.format(&quot;\u00D7 %d&quot;, item.chargepoint.count)}"
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
app:goneUnless="@{item.status == null}"
app:layout_constraintStart_toStartOf="@+id/imageView"
app:layout_constraintTop_toTopOf="@+id/imageView"
tools:text="×99"
tools:visibility="gone" />
<TextView
android:id="@+id/textView7"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="30dp"
android:layout_marginTop="30dp"
android:background="@drawable/rounded_rect"
android:padding="2dp"
android:text="@{String.format(&quot;%s/%d&quot;, BindingAdaptersKt.availabilityText(item.status), item.chargepoint.count)}"
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
android:textColor="@android:color/white"
app:backgroundTintAvailability="@{item.status}"
app:goneUnless="@{item.status != null}"
app:layout_constraintStart_toStartOf="@+id/imageView"
app:layout_constraintTop_toTopOf="@+id/imageView"
tools:backgroundTint="@color/available"
tools:text="80/99" />
<TextView
android:id="@+id/textView6"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="36dp"
android:layout_marginTop="4dp"
android:text="@{UtilsKt.nameForPlugType(ChargepointApiKt.stringProvider(context), item.chargepoint.type) + &quot; · &quot; + item.chargepoint.formatPower()}"
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
app:goneUnless="@{item.chargepoint.hasKnownPower()}"
app:layout_constraintBottom_toTopOf="@id/textView8"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/imageView"
app:layout_constraintTop_toTopOf="@id/imageView"
tools:text="CCS · 350 kW" />
<TextView
android:id="@+id/textView8"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:text="@{item.chargepoint.formatVoltageAndCurrent()}"
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
app:goneUnless="@{item.chargepoint.hasKnownVoltageAndCurrent()}"
app:layout_constraintBottom_toBottomOf="@+id/imageView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/textView6"
app:layout_constraintTop_toBottomOf="@id/textView6"
tools:text="1000 V · 500 A" />
<View
android:id="@+id/divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginBottom="4dp"
android:background="?android:attr/listDivider"
app:layout_constraintTop_toBottomOf="@+id/imageView"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View File

@@ -8,6 +8,7 @@
<import type="net.vonforst.evmap.adapter.ConnectorDetailsAdapter.ConnectorDetails" />
<import type="net.vonforst.evmap.ui.BindingAdaptersKt" />
<import type="java.time.Instant" />
<variable
name="item"
@@ -23,47 +24,27 @@
<ImageView
android:id="@+id/imageView"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_width="24dp"
android:layout_height="24dp"
android:contentDescription="@{BindingAdaptersKt.availabilityText(item.status, (Instant) null, context)}"
android:scaleType="center"
android:tintMode="src_in"
android:contentDescription="@{item.chargepoint.type}"
app:connectorIcon="@{item.chargepoint.type}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:tintAvailability="@{item.status}"
tools:tint="@color/available"
tools:srcCompat="@drawable/ic_connector_typ2" />
<TextView
android:id="@+id/textView6"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:layout_marginBottom="8dp"
android:text="@{item.chargepoint.formatPower()}"
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
app:goneUnless="@{item.chargepoint.hasKnownPower()}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@+id/imageView"
app:layout_constraintStart_toStartOf="@+id/imageView"
app:layout_constraintTop_toBottomOf="@+id/imageView"
app:textColorAvailability="@{item.status}"
tools:text="350 kW"
tools:textColor="@color/available" />
app:srcCompat="@drawable/circle"
tools:tint="@color/available" />
<TextView
android:id="@+id/txtEvseid"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="18dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="14dp"
android:text="@{(item.label != null &amp;&amp; item.evseId != null) ? item.label + &quot; · &quot; + item.evseId : (item.label ?? item.evseId)}"
app:layout_constraintBottom_toBottomOf="@+id/imageView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/imageView"
app:layout_constraintTop_toTopOf="parent"
@@ -75,7 +56,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="14dp"
android:layout_marginBottom="8dp"
android:text="@{BindingAdaptersKt.availabilityText(item.status, item.lastChange, context)}"
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
app:goneUnless="@{item.status != null}"

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="@layout/dialog_connector_details_header" />
<include layout="@layout/dialog_connector_details_item" />
<include layout="@layout/dialog_connector_details_item" />
<include layout="@layout/dialog_connector_details_item" />
<include layout="@layout/dialog_connector_details_item" />
<include layout="@layout/dialog_connector_details_item" />
</LinearLayout>