Skip to content

Commit

Permalink
Add details about voltage and amperage of chargers
Browse files Browse the repository at this point in the history
shown in connector details dialog
(not supported by GoingElectric)
fixes #151
  • Loading branch information
johan12345 committed Dec 17, 2023
1 parent ca428b4 commit 028e2bc
Show file tree
Hide file tree
Showing 10 changed files with 232 additions and 74 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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?,
Expand All @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -23,15 +30,15 @@ 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
): ConnectorDetailsDialog {
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) })
Expand Down Expand Up @@ -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()
}
Expand Down
20 changes: 8 additions & 12 deletions app/src/main/java/net/vonforst/evmap/fragment/MapFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -811,18 +811,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
Expand Down
32 changes: 23 additions & 9 deletions app/src/main/java/net/vonforst/evmap/model/ChargersModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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()
)
}
}

Expand Down Expand Up @@ -390,31 +393,42 @@ 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)
}
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)"
Expand Down
11 changes: 11 additions & 0 deletions app/src/main/res/drawable/circle.xml
Original file line number Diff line number Diff line change
@@ -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>
15 changes: 8 additions & 7 deletions app/src/main/res/layout/dialog_connector_details.xml
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
115 changes: 115 additions & 0 deletions app/src/main/res/layout/dialog_connector_details_header.xml
Original file line number Diff line number Diff line change
@@ -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>
Loading

0 comments on commit 028e2bc

Please sign in to comment.