Compare commits

...

13 Commits
0.1.9 ... 0.2.0

Author SHA1 Message Date
johan12345
7fec02b468 Release 0.2.0 2020-06-22 08:31:30 +02:00
johan12345
8eacee8a71 implement dialog with list of all payment methods (fixes #26) 2020-06-21 20:03:50 +02:00
johan12345
95dd8cce52 add database migrations 2020-06-21 19:36:33 +02:00
Johan von Forstner
45dd40faa7 show compatible payment methods in details (#26) 2020-06-21 12:33:53 +02:00
Johan von Forstner
e9ac39301d add splash screen (fixes #27) 2020-06-20 20:35:21 +02:00
Johan von Forstner
8b8713e4c5 save filter enabled/disabled state in SharedPreferences 2020-06-20 13:20:57 +02:00
johan12345
d023facb2f add icon to map marker to show fault reports 2020-06-17 22:46:14 +02:00
johan12345
e2e15692bb add filter to exclude chargers with reported faults 2020-06-17 22:16:10 +02:00
johan12345
abde18d61f allow multiple lines for detail title
(necessary on narrow screens)
2020-06-17 21:44:38 +02:00
johan12345
b32fa6600d support HTML for fault reports 2020-06-17 21:43:18 +02:00
johan12345
1de1699d51 swap colors for >= 11kW and < 11kW
(similar to GE website and Wattfinder)
2020-06-17 21:38:41 +02:00
johan12345
a618c4106f Add filters 24/7 and barrier free 2020-06-17 21:36:07 +02:00
johan12345
6ad8389ecf Power filter: add additional step at 75 kW 2020-06-17 08:54:02 +02:00
27 changed files with 312 additions and 47 deletions

View File

@@ -13,8 +13,8 @@ android {
applicationId "net.vonforst.evmap"
minSdkVersion 21
targetSdkVersion 29
versionCode 17
versionName "0.1.9"
versionCode 18
versionName "0.2.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

View File

@@ -27,7 +27,8 @@
<activity
android:name=".MapsActivity"
android:label="@string/title_activity_maps">
android:label="@string/title_activity_maps"
android:theme="@style/AppTheme.LaunchScreen">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

View File

@@ -45,6 +45,8 @@ class MapsActivity : AppCompatActivity() {
}
override fun onCreate(savedInstanceState: Bundle?) {
// set theme to AppTheme to end launch screen
setTheme(R.style.AppTheme)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_maps)

View File

@@ -5,6 +5,7 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.core.text.HtmlCompat
import androidx.core.view.children
import androidx.databinding.DataBindingUtil
import androidx.databinding.Observable
@@ -16,9 +17,7 @@ import com.google.android.material.chip.Chip
import net.vonforst.evmap.BR
import net.vonforst.evmap.R
import net.vonforst.evmap.api.availability.ChargepointStatus
import net.vonforst.evmap.api.goingelectric.ChargeLocation
import net.vonforst.evmap.api.goingelectric.Chargepoint
import net.vonforst.evmap.api.goingelectric.OpeningHoursDays
import net.vonforst.evmap.api.goingelectric.*
import net.vonforst.evmap.databinding.ItemFilterMultipleChoiceBinding
import net.vonforst.evmap.databinding.ItemFilterMultipleChoiceLargeBinding
import net.vonforst.evmap.databinding.ItemFilterSliderBinding
@@ -114,7 +113,11 @@ class DetailAdapter : DataBindingAdapter<DetailAdapter.Detail>() {
}
}
fun buildDetails(loc: ChargeLocation?, ctx: Context): List<DetailAdapter.Detail> {
fun buildDetails(
loc: ChargeLocation?,
chargeCards: Map<Long, ChargeCard>?,
ctx: Context
): List<DetailAdapter.Detail> {
if (loc == null) return emptyList()
return listOfNotNull(
@@ -138,12 +141,16 @@ fun buildDetails(loc: ChargeLocation?, ctx: Context): List<DetailAdapter.Detail>
R.drawable.ic_fault_report,
R.string.fault_report,
loc.faultReport.created?.let {
ctx.getString(R.string.fault_report_date,
ctx.getString(
R.string.fault_report_date,
loc.faultReport.created
.atZone(ZoneId.systemDefault())
.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT)))
.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT))
)
} ?: "",
loc.faultReport.description?.let {
HtmlCompat.fromHtml(it, HtmlCompat.FROM_HTML_MODE_LEGACY)
} ?: "",
loc.faultReport.description ?: "",
clickable = true
) else null,
if (loc.openinghours != null && !loc.openinghours.isEmpty) DetailAdapter.Detail(
@@ -160,6 +167,16 @@ fun buildDetails(loc: ChargeLocation?, ctx: Context): List<DetailAdapter.Detail>
loc.cost.descriptionLong ?: loc.cost.descriptionShort
)
else null,
if (loc.chargecards != null && loc.chargecards.isNotEmpty()) DetailAdapter.Detail(
R.drawable.ic_payment,
R.string.charge_cards,
ctx.resources.getQuantityString(
R.plurals.charge_cards_compatible_num,
loc.chargecards.size, loc.chargecards.size
),
formatChargeCards(loc.chargecards, chargeCards, ctx),
clickable = true
) else null,
DetailAdapter.Detail(
R.drawable.ic_location,
R.string.coordinates,
@@ -171,6 +188,25 @@ fun buildDetails(loc: ChargeLocation?, ctx: Context): List<DetailAdapter.Detail>
)
}
fun formatChargeCards(
chargecards: List<ChargeCardId>,
chargecardData: Map<Long, ChargeCard>?,
ctx: Context
): String {
if (chargecardData == null) return ""
val maxItems = 5
var result = chargecards
.take(maxItems)
.mapNotNull { chargecardData[it.id]?.name }
.joinToString()
if (chargecards.size > maxItems) {
result += " " + ctx.getString(R.string.and_n_others, chargecards.size - maxItems)
}
return result
}
class FavoritesAdapter(val vm: FavoritesViewModel) :
DataBindingAdapter<FavoritesViewModel.FavoritesListItem>() {

View File

@@ -75,14 +75,14 @@ internal class JsonObjectOrFalseAdapter<T> private constructor(
type: Type,
annotations: Set<Annotation>?,
moshi: Moshi
): JsonAdapter<*>? {
): JsonAdapter<Any>? {
val clazz = Types.getRawType(type)
return when (hasJsonObjectOrFalseAnnotation(
annotations
)) {
false -> null
true -> JsonObjectOrFalseAdapter(
moshi.adapter(clazz), clazz
moshi.adapter(type), clazz
)
}
}
@@ -101,6 +101,7 @@ internal class JsonObjectOrFalseAdapter<T> private constructor(
}
}
JsonReader.Token.BEGIN_OBJECT -> objectDelegate.fromJson(reader)
JsonReader.Token.BEGIN_ARRAY -> objectDelegate.fromJson(reader)
JsonReader.Token.STRING -> objectDelegate.fromJson(reader)
JsonReader.Token.NUMBER -> objectDelegate.fromJson(reader)
else ->

View File

@@ -27,7 +27,10 @@ interface GoingElectricApi {
@Query("plugs") plugs: String? = null,
@Query("chargecards") chargecards: String? = null,
@Query("networks") networks: String? = null,
@Query("startkey") startkey: Int? = null
@Query("startkey") startkey: Int? = null,
@Query("open_twentyfourseven") open247: Boolean = false,
@Query("barrierfree") barrierfree: Boolean = false,
@Query("exclude_faults") excludeFaults: Boolean = false
): Response<ChargepointList>
@GET("chargepoints/")

View File

@@ -52,7 +52,7 @@ data class ChargeLocation(
val chargepoints: List<Chargepoint>,
@JsonObjectOrFalse val network: String?,
val url: String,
@Embedded(prefix="fault_report_") @JsonObjectOrFalse @Json(name = "fault_report") val faultReport: FaultReport?,
@Embedded(prefix = "fault_report_") @JsonObjectOrFalse @Json(name = "fault_report") val faultReport: FaultReport?,
val verified: Boolean,
// only shown in details:
@JsonObjectOrFalse val operator: String?,
@@ -60,7 +60,7 @@ data class ChargeLocation(
@JsonObjectOrFalse @Json(name = "ladeweile") val amenities: String?,
@JsonObjectOrFalse @Json(name = "location_description") val locationDescription: String?,
val photos: List<ChargerPhoto>?,
//val chargecards: Boolean?
@JsonObjectOrFalse val chargecards: List<ChargeCardId>?,
@Embedded val openinghours: OpeningHours?,
@Embedded val cost: Cost?
) : ChargepointListItem(), Equatable {
@@ -279,3 +279,8 @@ data class ChargeCard(
val name: String,
val url: String
)
@JsonClass(generateAdapter = true)
data class ChargeCardId(
val id: Long
)

View File

@@ -14,6 +14,7 @@ import android.view.*
import android.view.inputmethod.InputMethodManager
import android.widget.TextView
import androidx.activity.OnBackPressedCallback
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.PopupMenu
import androidx.core.app.SharedElementCallback
import androidx.core.content.ContextCompat
@@ -385,7 +386,7 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
markers.forEach { (m, c) ->
m.setIcon(
chargerIconGenerator.getBitmapDescriptor(
getMarkerTint(c)
getMarkerTint(c), fault = c.faultReport != null
)
)
}
@@ -396,7 +397,7 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
// highlight this marker
marker.setIcon(
chargerIconGenerator.getBitmapDescriptor(
getMarkerTint(charger), highlight = true
getMarkerTint(charger), highlight = true, fault = charger.faultReport != null
)
)
animator.animateMarkerBounce(marker)
@@ -406,7 +407,7 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
if (m != marker) {
m.setIcon(
chargerIconGenerator.getBitmapDescriptor(
getMarkerTint(c)
getMarkerTint(c), fault = c.faultReport != null
)
)
}
@@ -472,7 +473,7 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
binding.detailView.details.apply {
adapter = DetailAdapter().apply {
onClickListener = {
val charger = vm.chargerSparse.value
val charger = vm.chargerDetails.value?.data
if (charger != null) {
when (it.icon) {
R.drawable.ic_location -> {
@@ -481,6 +482,9 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
R.drawable.ic_fault_report -> {
(activity as? MapsActivity)?.openUrl("https:${charger.url}")
}
R.drawable.ic_payment -> {
showPaymentMethodsDialog(charger)
}
}
}
}
@@ -497,6 +501,21 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
}
}
private fun showPaymentMethodsDialog(charger: ChargeLocation) {
val activity = activity ?: return
val chargecardData = vm.chargeCardMap.value ?: return
val chargecards = charger.chargecards ?: return
val data = chargecards.map { chargecardData[it.id] }.sortedBy { it?.name }
val names = data.map { it?.name ?: "" }
AlertDialog.Builder(activity)
.setTitle(R.string.charge_cards)
.setItems(names.toTypedArray()) { _, i ->
val card = data[i] ?: return@setItems
(activity as? MapsActivity)?.openUrl("https:${card.url}")
}.show()
}
override fun onMapReady(map: GoogleMap) {
this.map = map
map.uiSettings.isTiltGesturesEnabled = false
@@ -630,7 +649,8 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
if (it.key.isVisible) {
val tint = getMarkerTint(it.value)
val highlight = it.value == vm.chargerSparse.value
animator.animateMarkerDisappear(it.key, tint, highlight)
val fault = it.value.faultReport != null
animator.animateMarkerDisappear(it.key, tint, highlight, fault)
} else {
it.key.remove()
}
@@ -643,14 +663,18 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
}.forEach { charger ->
val tint = getMarkerTint(charger)
val highlight = charger == vm.chargerSparse.value
val fault = charger.faultReport != null
val marker = map.addMarker(
MarkerOptions()
.position(LatLng(charger.coordinates.lat, charger.coordinates.lng))
.icon(
chargerIconGenerator.getBitmapDescriptor(tint, highlight = highlight)
chargerIconGenerator.getBitmapDescriptor(
tint, highlight = highlight,
fault = charger.faultReport != null
)
)
)
animator.animateMarkerAppear(marker, tint, highlight)
animator.animateMarkerAppear(marker, tint, highlight, fault)
markers[marker] = charger
}
clusterMarkers = clusters.map { cluster ->

View File

@@ -22,7 +22,7 @@ import net.vonforst.evmap.viewmodel.SliderFilterValue
Plug::class,
Network::class,
ChargeCard::class
], version = 7
], version = 8
)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
@@ -38,7 +38,7 @@ abstract class AppDatabase : RoomDatabase() {
Room.databaseBuilder(context, AppDatabase::class.java, "evmap.db")
.addMigrations(
MIGRATION_2, MIGRATION_3, MIGRATION_4, MIGRATION_5, MIGRATION_6,
MIGRATION_7
MIGRATION_7, MIGRATION_8
)
.build()
}
@@ -114,5 +114,11 @@ abstract class AppDatabase : RoomDatabase() {
db.execSQL("CREATE TABLE IF NOT EXISTS `ChargeCard` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, PRIMARY KEY(`id`))")
}
}
private val MIGRATION_8 = object : Migration(7, 8) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("ALTER TABLE `ChargeLocation` ADD `chargecards` TEXT")
}
}
}
}

View File

@@ -31,6 +31,12 @@ class PreferenceDataSource(context: Context) {
sp.edit().putLong("last_chargecard_update", value.toEpochMilli()).apply()
}
var filtersActive: Boolean
get() = sp.getBoolean("filters_active", true)
set(value) {
sp.edit().putBoolean("filters_active", value).apply()
}
val language: String
get() = sp.getString("language", "default")!!
}

View File

@@ -3,6 +3,7 @@ package net.vonforst.evmap.storage
import androidx.room.TypeConverter
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import net.vonforst.evmap.api.goingelectric.ChargeCardId
import net.vonforst.evmap.api.goingelectric.Chargepoint
import net.vonforst.evmap.api.goingelectric.ChargerPhoto
import java.time.Instant
@@ -18,6 +19,10 @@ class Converters {
val type = Types.newParameterizedType(List::class.java, ChargerPhoto::class.java)
moshi.adapter<List<ChargerPhoto>>(type)
}
private val chargeCardIdListAdapter by lazy {
val type = Types.newParameterizedType(List::class.java, ChargeCardId::class.java)
moshi.adapter<List<ChargeCardId>>(type)
}
private val stringSetAdapter by lazy {
val type = Types.newParameterizedType(Set::class.java, String::class.java)
moshi.adapter<Set<String>>(type)
@@ -43,6 +48,16 @@ class Converters {
return chargerPhotoListAdapter.fromJson(value)
}
@TypeConverter
fun fromChargeCardIdList(value: List<ChargeCardId>?): String {
return chargeCardIdListAdapter.toJson(value)
}
@TypeConverter
fun toChargeCardIdList(value: String?): List<ChargeCardId>? {
return value?.let { chargeCardIdListAdapter.fromJson(it) }
}
@TypeConverter
fun fromLocalTime(value: LocalTime?): String? {
return value?.toString()

View File

@@ -42,17 +42,24 @@ class ClusterIconGenerator(context: Context) : IconGenerator(context) {
class ChargerIconGenerator(val context: Context) {
data class BitmapData(val tint: Int, val scale: Int, val alpha: Int, val highlight: Boolean)
data class BitmapData(
val tint: Int,
val scale: Int,
val alpha: Int,
val highlight: Boolean,
val fault: Boolean
)
val cacheSize = 4 * 1024 * 1024; // 4MiB
val cacheSize = 8 * 1024 * 1024; // 8MiB
val cache = object : LruCache<BitmapData, Bitmap>(cacheSize) {
override fun sizeOf(key: BitmapData, value: Bitmap): Int {
return value.byteCount
}
}
val oversize = 1f // increase to add padding for overshoot scale animation
val oversize = 1.4f // increase to add padding for fault icon or scale > 1
val icon = R.drawable.ic_map_marker_charging
val highlightIcon = R.drawable.ic_map_marker_highlight
val faultIcon = R.drawable.ic_map_marker_fault
init {
preloadCache()
@@ -67,11 +74,13 @@ class ChargerIconGenerator(val context: Context) {
R.color.charger_11kw,
R.color.charger_low
)
for (highlight in listOf(false, true)) {
for (tint in tints) {
for (scale in 0..20) {
val data = BitmapData(tint, scale, 255, highlight)
cache.put(data, generateBitmap(data))
for (fault in listOf(false, true)) {
for (highlight in listOf(false, true)) {
for (tint in tints) {
for (scale in 0..20) {
val data = BitmapData(tint, scale, 255, highlight, fault)
cache.put(data, generateBitmap(data))
}
}
}
}
@@ -81,9 +90,10 @@ class ChargerIconGenerator(val context: Context) {
@ColorRes tint: Int,
scale: Int = 20,
alpha: Int = 255,
highlight: Boolean = false
highlight: Boolean = false,
fault: Boolean = false
): BitmapDescriptor? {
val data = BitmapData(tint, scale, alpha, highlight)
val data = BitmapData(tint, scale, alpha, highlight, fault)
val cachedImg = cache[data]
return if (cachedImg != null) {
BitmapDescriptorFactory.fromBitmap(cachedImg)
@@ -136,6 +146,21 @@ class ChargerIconGenerator(val context: Context) {
highlightDrawable.draw(canvas)
}
if (data.fault) {
val faultDrawable = context.getDrawable(faultIcon)!!
val faultSize = 0.75
val faultShift = 0.25
val base = vd.intrinsicWidth
faultDrawable.setBounds(
(leftPadding.toInt() + base * (1 - faultSize + faultShift)).toInt(),
(topPadding.toInt() - base * faultShift).toInt(),
(leftPadding.toInt() + base * (1 + faultShift)).toInt(),
(topPadding.toInt() + base * (faultSize - faultShift)).toInt()
)
faultDrawable.alpha = data.alpha
faultDrawable.draw(canvas)
}
return bm
}
}

View File

@@ -24,7 +24,8 @@ class MarkerAnimator(val gen: ChargerIconGenerator) {
fun animateMarkerAppear(
marker: Marker,
tint: Int,
highlight: Boolean
highlight: Boolean,
fault: Boolean
) {
animatingMarkers[marker]?.cancel()
animatingMarkers.remove(marker)
@@ -40,7 +41,12 @@ class MarkerAnimator(val gen: ChargerIconGenerator) {
}
val scale = animationState.animatedValue as Int
marker.setIcon(
gen.getBitmapDescriptor(tint, scale = scale, highlight = highlight)
gen.getBitmapDescriptor(
tint,
scale = scale,
highlight = highlight,
fault = fault
)
)
}
addListener(onEnd = {
@@ -54,7 +60,8 @@ class MarkerAnimator(val gen: ChargerIconGenerator) {
fun animateMarkerDisappear(
marker: Marker,
tint: Int,
highlight: Boolean
highlight: Boolean,
fault: Boolean
) {
animatingMarkers[marker]?.cancel()
animatingMarkers.remove(marker)
@@ -70,7 +77,12 @@ class MarkerAnimator(val gen: ChargerIconGenerator) {
}
val scale = animationState.animatedValue as Int
marker.setIcon(
gen.getBitmapDescriptor(tint, scale = scale, highlight = highlight)
gen.getBitmapDescriptor(
tint,
scale = scale,
highlight = highlight,
fault = fault
)
)
}
addListener(onEnd = {

View File

@@ -18,7 +18,7 @@ import kotlin.math.abs
import kotlin.reflect.KClass
import kotlin.reflect.full.cast
val powerSteps = listOf(0, 2, 3, 7, 11, 22, 43, 50, 100, 150, 200, 250, 300, 350)
val powerSteps = listOf(0, 2, 3, 7, 11, 22, 43, 50, 75, 100, 150, 200, 250, 300, 350)
internal fun mapPower(i: Int) = powerSteps[i]
internal fun mapPowerInverse(power: Int) = powerSteps
.mapIndexed { index, v -> abs(v - power) to index }
@@ -65,6 +65,7 @@ private fun MediatorLiveData<List<Filter<FilterValue>>>.buildFilters(
value = listOf(
BooleanFilter(application.getString(R.string.filter_free), "freecharging"),
BooleanFilter(application.getString(R.string.filter_free_parking), "freeparking"),
BooleanFilter(application.getString(R.string.filter_open_247), "open_247"),
SliderFilter(
application.getString(R.string.filter_min_power), "min_power",
powerSteps.size - 1,
@@ -86,10 +87,12 @@ private fun MediatorLiveData<List<Filter<FilterValue>>>.buildFilters(
application.getString(R.string.filter_networks), "networks",
networkMap, manyChoices = true
),
BooleanFilter(application.getString(R.string.filter_barrierfree), "barrierfree"),
MultipleChoiceFilter(
application.getString(R.string.filter_chargecards), "chargecards",
chargecardMap, manyChoices = true
)
),
BooleanFilter(application.getString(R.string.filter_exclude_faults), "exclude_faults")
)
}

View File

@@ -61,6 +61,17 @@ class MapViewModel(application: Application, geApiKey: String) : AndroidViewMode
filtersWithValue(filters, filterValues, filtersActive)
}
val chargeCardMap: LiveData<Map<Long, ChargeCard>> by lazy {
MediatorLiveData<Map<Long, ChargeCard>>().apply {
value = null
addSource(chargeCards) {
value = chargeCards.value?.map {
it.id to it
}?.toMap()
}
}
}
val filtersCount: LiveData<Int> by lazy {
MediatorLiveData<Int>().apply {
value = 0
@@ -153,7 +164,10 @@ class MapViewModel(application: Application, geApiKey: String) : AndroidViewMode
val filtersActive: MutableLiveData<Boolean> by lazy {
MutableLiveData<Boolean>().apply {
value = true
value = prefs.filtersActive
observeForever {
prefs.filtersActive = it
}
}
}
@@ -200,6 +214,9 @@ class MapViewModel(application: Application, geApiKey: String) : AndroidViewMode
): Resource<List<ChargepointListItem>> {
val freecharging = getBooleanValue(filters, "freecharging")
val freeparking = getBooleanValue(filters, "freeparking")
val open247 = getBooleanValue(filters, "open_247")
val barrierfree = getBooleanValue(filters, "barrierfree")
val excludeFaults = getBooleanValue(filters, "exclude_faults")
val minPower = getSliderValue(filters, "min_power")
val minConnectors = getSliderValue(filters, "min_connectors")
@@ -246,6 +263,9 @@ class MapViewModel(application: Application, geApiKey: String) : AndroidViewMode
freecharging = freecharging,
minPower = minPower,
freeparking = freeparking,
open247 = open247,
barrierfree = barrierfree,
excludeFaults = excludeFaults,
plugs = connectors,
chargecards = chargeCards,
networks = networks,

View File

@@ -0,0 +1,49 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="144.3dp"
android:height="270.5dp"
android:viewportWidth="144.3"
android:viewportHeight="270.5">
<path
android:pathData="M33.9,100l-2.5,-21.7l-3.8,0.4l2.5,21.7L33.9,100zM47.4,98.5l-2.5,-21.7l-3.8,0.4l2.5,21.7L47.4,98.5z"
android:fillColor="#FFB300" />
<path
android:pathData="M54.5,128c-1.2,1.4 -2.1,2.4 -2.2,2.5c-3.4,2.7 -6.1,3.5 -8.4,2.5c-3.9,-2 -3.7,-9.3 -3.5,-10.1l2.7,0.1c-0.1,2.1 0.3,6.5 2.1,7.5c1,0.5 2.9,-0.1 5.2,-2.1l0,0c0,0 7.6,-7.6 6,-13.6c-1.8,-7.2 6.5,-17.5 9.3,-21.1l0.4,-0.4l2.2,1.7l-0.4,0.5c-8.5,10.5 -9.4,15.8 -8.8,18.6C60.5,119.4 57,125 54.5,128z"
android:fillColor="#90A4AE" />
<path
android:pathData="M25.6,99.8l1,8.9l8.2,5.5L46,113l6.8,-7.2l-1,-8.9L25.6,99.8z"
android:fillColor="#90A4AE" />
<path
android:pathData="M45.8,113l-11.1,1.2l2.4,9.8l8.8,-1V113L45.8,113zM53.8,89.4l0.9,8.1l-31.9,3.7l-0.9,-8.1L53.8,89.4z"
android:fillColor="#546E7A" />
<path
android:pathData="M78.8,0C55.9,0 37.3,18.6 37.3,41.5c0,31.3 34.9,47.6 39.1,92.2c0.1,1.3 1.2,2.2 2.5,2.2s2.4,-0.9 2.5,-2.2c4.2,-44.6 39.1,-60.9 39.1,-92.2C120.3,18.4 101.7,0 78.8,0z"
android:fillColor="#00E676" />
<path
android:pathData="M78.8,0.9c22.8,0 41.2,18.3 41.5,40.9c0,-0.1 0,-0.3 0,-0.4C120.3,18.6 101.7,0 78.8,0S37.3,18.4 37.3,41.5c0,0.1 0,0.3 0,0.4C37.6,19.2 56,0.9 78.8,0.9L78.8,0.9z"
android:fillColor="#FFFFFF"
android:fillAlpha="0.2" />
<path
android:pathData="M81.3,132.6c-0.1,1.3 -1.2,2.2 -2.5,2.2c-1.3,0 -2.4,-0.9 -2.5,-2.2c-4.1,-44.5 -38.7,-60.8 -39,-91.7c0,0.3 0,0.4 0,0.7c0,31.3 34.9,47.6 39.1,92.2c0.1,1.3 1.2,2.2 2.5,2.2c1.3,0 2.4,-0.9 2.5,-2.2c4.2,-44.6 39.1,-60.9 39.1,-92.2c0,-0.3 0,-0.4 0,-0.7C120,71.8 85.3,88.1 81.3,132.6L81.3,132.6z"
android:fillColor="#3E2723"
android:fillAlpha="0.2" />
<path
android:fillColor="#FF000000"
android:pathData="M69.3,21.2v25.1h6.8v20.5l16,-27.5h-9.2L92,21.1C92.1,21.2 69.3,21.2 69.3,21.2z"
android:strokeAlpha="0.45"
android:fillAlpha="0.45" />
<path
android:fillColor="?android:textColorSecondary"
android:pathData="M19.2,244.2H2.8v14.1h18.8v2.4H0v-34.1h21.5v2.4H2.8v12.8h16.4V244.2z" />
<path
android:fillColor="?android:textColorSecondary"
android:pathData="M37.2,254.9l0.7,2.3h0.1l0.7,-2.3L49,226.6h3l-12.7,34.1h-2.6l-12.7,-34.1h3L37.2,254.9z" />
<path
android:fillColor="?android:textColorSecondary"
android:pathData="M60.9,226.6l12.5,30h0.1l12.6,-30h3.7v34.1h-2.8v-15.1l0.2,-14.9l-0.1,0l-12.7,30h-1.9l-12.7,-29.9l-0.1,0l0.3,14.8v15.1h-2.8v-34.1H60.9z" />
<path
android:fillColor="?android:textColorSecondary"
android:pathData="M114.1,260.7c-0.2,-0.9 -0.3,-1.6 -0.4,-2.2s-0.1,-1.3 -0.1,-1.9c-0.9,1.3 -2.2,2.4 -3.8,3.3s-3.3,1.3 -5.3,1.3c-2.5,0 -4.4,-0.7 -5.8,-2s-2.1,-3.1 -2.1,-5.3c0,-2.3 1,-4.2 3,-5.6s4.8,-2.1 8.2,-2.1h5.6v-3.1c0,-1.8 -0.6,-3.2 -1.7,-4.3s-2.8,-1.5 -4.9,-1.5c-2,0 -3.6,0.5 -4.9,1.5s-1.9,2.2 -1.9,3.6l-2.6,0l0,-0.1c-0.1,-1.9 0.8,-3.6 2.6,-5.1s4.1,-2.2 6.9,-2.2c2.8,0 5,0.7 6.8,2.1s2.6,3.5 2.6,6.1v12.5c0,0.9 0.1,1.8 0.2,2.6s0.3,1.7 0.5,2.5H114.1zM104.9,258.7c2,0 3.8,-0.5 5.3,-1.4s2.7,-2.2 3.4,-3.6v-5.3H108c-2.5,0 -4.6,0.5 -6.1,1.6s-2.3,2.4 -2.3,4c0,1.4 0.5,2.5 1.4,3.4S103.3,258.7 104.9,258.7z" />
<path
android:fillColor="?android:textColorSecondary"
android:pathData="M144.3,248.7c0,3.8 -0.9,6.8 -2.6,9.1s-4.1,3.4 -7.1,3.4c-1.8,0 -3.3,-0.3 -4.7,-1s-2.4,-1.6 -3.3,-2.9v13.1h-2.8v-35.1h2.4l0.4,3.9c0.8,-1.4 1.9,-2.5 3.3,-3.3s2.9,-1.1 4.7,-1.1c3,0 5.4,1.2 7.1,3.6s2.6,5.7 2.6,9.7V248.7zM141.5,248.2c0,-3.2 -0.6,-5.8 -1.9,-7.9c-1.3,-2 -3.2,-3 -5.6,-3c-1.9,0 -3.4,0.4 -4.6,1.3c-1.2,0.9 -2.1,2.1 -2.7,3.5v12.2c0.6,1.4 1.6,2.5 2.8,3.3s2.7,1.2 4.5,1.2c2.5,0 4.3,-0.9 5.6,-2.8c1.3,-1.8 1.9,-4.3 1.9,-7.3V248.2z" />
</vector>

View File

@@ -0,0 +1,12 @@
<vector android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24"
android:width="24dp"
xmlns:android="http://schemas.android.com/apk/res/android">
<path
android:fillColor="@android:color/black"
android:pathData="M 1 21 h 22 L 12 2 L 1 21 z" />
<path
android:fillColor="#FF9100"
android:pathData="M1,21h22L12,2 1,21zM13,18h-2v-2h2v2zM13,14h-2v-4h2v4z" />
</vector>

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M20,4L4,4c-1.11,0 -1.99,0.89 -1.99,2L2,18c0,1.11 0.89,2 2,2h16c1.11,0 2,-0.89 2,-2L22,6c0,-1.11 -0.89,-2 -2,-2zM20,18L4,18v-6h16v6zM20,8L4,8L4,6h16v2z" />
</vector>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<item
android:drawable="@drawable/ic_appicon_splashscreen"
android:gravity="center" />
</layer-list>

View File

@@ -9,6 +9,8 @@
<import type="net.vonforst.evmap.api.goingelectric.Chargepoint" />
<import type="net.vonforst.evmap.api.goingelectric.ChargeCard" />
<import type="net.vonforst.evmap.api.availability.ChargeLocationStatus" />
<import type="net.vonforst.evmap.adapter.DataBindingAdaptersKt" />
@@ -25,6 +27,10 @@
name="availability"
type="Resource&lt;ChargeLocationStatus&gt;" />
<variable
name="chargeCards"
type="java.util.Map&lt;Long, ChargeCard&gt;" />
</data>
<androidx.cardview.widget.CardView
@@ -182,7 +188,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:nestedScrollingEnabled="false"
app:data="@{DataBindingAdaptersKt.buildDetails(charger.data, context)}"
app:data="@{DataBindingAdaptersKt.buildDetails(charger.data, chargeCards, context)}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="parent"

View File

@@ -126,7 +126,8 @@
android:id="@+id/detail_view"
layout="@layout/detail_view"
app:charger="@{vm.charger}"
app:availability="@{vm.availability}" />
app:availability="@{vm.availability}"
app:chargeCards="@{vm.chargeCardMap}" />
</androidx.core.widget.NestedScrollView>

View File

@@ -25,7 +25,6 @@
android:layout_marginTop="18dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="14dp"
android:maxLines="1"
android:text="@{item.text}"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body2"
app:layout_constraintBottom_toBottomOf="parent"

View File

@@ -31,7 +31,6 @@
android:layout_marginTop="18dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="14dp"
android:maxLines="1"
android:text="@{item.text}"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body2"
app:layout_constraintBottom_toBottomOf="parent"

View File

@@ -94,4 +94,13 @@
<string name="pref_language_summary">App-Sprache ändern</string>
<string name="connection_error">Ladesäulen konnten nicht geladen werden</string>
<string name="retry">Wiederholen</string>
<string name="filter_open_247">24 Stunden geöffnet</string>
<string name="filter_barrierfree">Ohne Vertrag / Registrierung nutzbar</string>
<string name="filter_exclude_faults">Ladesäulen mit Störung ausschließen</string>
<string name="charge_cards">Ladetarife</string>
<string name="and_n_others">und %d weitere</string>
<plurals name="charge_cards_compatible_num">
<item quantity="one">%d kompatibler Ladetarif</item>
<item quantity="other">%d kompatible Ladetarife</item>
</plurals>
</resources>

View File

@@ -7,8 +7,8 @@
<color name="charger_100kw">#ffeb3b</color>
<color name="charger_43kw">#ff9800</color>
<color name="charger_20kw">#03a9f4</color>
<color name="charger_11kw">#607d8b</color>
<color name="charger_low">#9e9e9e</color>
<color name="charger_11kw">#9e9e9e</color>
<color name="charger_low">#607d8b</color>
<color name="available">#4caf50</color>
<color name="unavailable">#f44336</color>
<color name="unknown">#9e9e9e</color>

View File

@@ -93,4 +93,13 @@
<string name="pref_language_summary">Change the app language</string>
<string name="connection_error">Could not load charging stations</string>
<string name="retry">Retry</string>
<string name="filter_open_247">Available 24/7</string>
<string name="filter_barrierfree">Usable without registration</string>
<string name="filter_exclude_faults">Exclude chargers with reported faults</string>
<string name="charge_cards">Payment methods</string>
<string name="and_n_others">and %d others</string>
<plurals name="charge_cards_compatible_num">
<item quantity="one">%d compatible payment method</item>
<item quantity="other">%d compatible payment methods</item>
</plurals>
</resources>

View File

@@ -17,6 +17,10 @@
<style name="AppTheme" parent="AppTheme.Base" />
<style name="AppTheme.LaunchScreen">
<item name="android:windowBackground">@drawable/launch_screen</item>
</style>
<style name="FullScreenDialogStyle" parent="AppTheme">
<item name="android:windowFullscreen">false</item>
<item name="android:windowIsFloating">false</item>