mirror of
https://github.com/ev-map/EVMap.git
synced 2025-12-25 16:17:45 -05:00
Compare commits
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0f7bf7913f | ||
|
|
d11925eb33 | ||
|
|
6ac49fd84d | ||
|
|
097b7941a2 | ||
|
|
23b87e69c0 | ||
|
|
3bb5521c18 | ||
|
|
76f7b97c1f | ||
|
|
50de0009c7 | ||
|
|
f906846fcc | ||
|
|
b50225af32 | ||
|
|
8abd5219aa | ||
|
|
71f9a25c5a | ||
|
|
b5f4314795 | ||
|
|
034196b9fa | ||
|
|
72d7f7dc57 | ||
|
|
7fec02b468 | ||
|
|
8eacee8a71 | ||
|
|
95dd8cce52 | ||
|
|
45dd40faa7 | ||
|
|
e9ac39301d | ||
|
|
8b8713e4c5 | ||
|
|
d023facb2f | ||
|
|
e2e15692bb | ||
|
|
abde18d61f | ||
|
|
b32fa6600d | ||
|
|
1de1699d51 | ||
|
|
a618c4106f | ||
|
|
6ad8389ecf | ||
|
|
38d07abf0e | ||
|
|
884172b9f8 | ||
|
|
2208e093e7 | ||
|
|
a2041653bc | ||
|
|
394cbdfc8b |
@@ -13,8 +13,8 @@ android {
|
||||
applicationId "net.vonforst.evmap"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 29
|
||||
versionCode 16
|
||||
versionName "0.1.8"
|
||||
versionCode 19
|
||||
versionName "0.2.1"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
@@ -76,9 +76,9 @@ dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||
implementation 'androidx.core:core-ktx:1.2.0'
|
||||
implementation 'androidx.core:core-ktx:1.3.0'
|
||||
implementation "androidx.activity:activity-ktx:1.1.0"
|
||||
implementation "androidx.fragment:fragment-ktx:1.2.4"
|
||||
implementation "androidx.fragment:fragment-ktx:1.2.5"
|
||||
implementation 'androidx.cardview:cardview:1.0.0'
|
||||
implementation 'androidx.core:core:1.3.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||
@@ -99,20 +99,22 @@ dependencies {
|
||||
implementation 'io.michaelrocks:bimap:1.0.2'
|
||||
|
||||
// Google Maps v3 Beta
|
||||
implementation name:'maps-sdk-3.0.0-beta', ext:'aar'
|
||||
implementation name:'places-maps-sdk-3.0.0-beta', ext:'aar'
|
||||
implementation 'com.google.android.libraries.maps:maps:3.1.0-beta'
|
||||
implementation name:'places-maps-sdk-3.1.0-beta', ext:'aar'
|
||||
implementation 'com.google.maps.android:android-maps-utils-v3:1.3.3'
|
||||
implementation 'com.google.android.gms:play-services-basement:17.3.0'
|
||||
implementation 'com.android.volley:volley:1.1.1'
|
||||
implementation 'com.google.android.gms:play-services-base:17.3.0'
|
||||
implementation 'com.google.android.gms:play-services-basement:17.3.0'
|
||||
implementation 'com.google.android.gms:play-services-gcm:17.0.0'
|
||||
implementation 'com.google.android.gms:play-services-location:17.0.0'
|
||||
implementation 'com.google.android.gms:play-services-clearcut:17.0.0'
|
||||
implementation 'com.android.volley:volley:1.1.1'
|
||||
implementation 'com.google.android.gms:play-services-tasks:17.1.0'
|
||||
implementation 'com.google.auto.value:auto-value-annotations:1.6.3'
|
||||
implementation 'com.google.code.gson:gson:2.8.6'
|
||||
implementation 'com.github.bumptech.glide:glide:4.11.0'
|
||||
implementation 'com.google.android.datatransport:transport-runtime:2.2.3'
|
||||
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
|
||||
|
||||
// navigation library
|
||||
def nav_version = "2.3.0-beta01"
|
||||
def nav_version = "2.3.0"
|
||||
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
|
||||
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
|
||||
|
||||
@@ -128,7 +130,7 @@ dependencies {
|
||||
implementation "androidx.room:room-ktx:$room_version"
|
||||
|
||||
// billing library
|
||||
def billing_version = "2.2.1"
|
||||
def billing_version = "3.0.0"
|
||||
implementation "com.android.billingclient:billing:$billing_version"
|
||||
implementation "com.android.billingclient:billing-ktx:$billing_version"
|
||||
|
||||
@@ -144,5 +146,5 @@ dependencies {
|
||||
|
||||
kapt "com.squareup.moshi:moshi-kotlin-codegen:1.9.2"
|
||||
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.0.5'
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.0.9'
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
BIN
app/libs/places-maps-sdk-3.1.0-beta.aar
Normal file
BIN
app/libs/places-maps-sdk-3.1.0-beta.aar
Normal file
Binary file not shown.
@@ -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" />
|
||||
|
||||
|
||||
@@ -3,11 +3,14 @@ package net.vonforst.evmap
|
||||
import android.app.Application
|
||||
import com.facebook.stetho.Stetho
|
||||
import com.google.android.libraries.places.api.Places
|
||||
import net.vonforst.evmap.storage.PreferenceDataSource
|
||||
import net.vonforst.evmap.ui.updateNightMode
|
||||
|
||||
class EvMapApplication : Application() {
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
updateNightMode(PreferenceDataSource(this))
|
||||
Stetho.initializeWithDefaults(this);
|
||||
Places.initialize(getApplicationContext(), getString(R.string.google_maps_key));
|
||||
Places.initialize(applicationContext, getString(R.string.google_maps_key));
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package net.vonforst.evmap
|
||||
|
||||
import android.graphics.Typeface
|
||||
import android.os.Bundle
|
||||
import android.text.*
|
||||
import android.text.style.StyleSpan
|
||||
|
||||
fun Bundle.optDouble(name: String): Double? {
|
||||
if (!this.containsKey(name)) return null
|
||||
@@ -14,4 +17,38 @@ fun Bundle.optLong(name: String): Long? {
|
||||
|
||||
val lng = this.getLong(name, Long.MIN_VALUE)
|
||||
return if (lng == Long.MIN_VALUE) null else lng
|
||||
}
|
||||
|
||||
fun <T> Iterable<T>.joinToSpannedString(
|
||||
separator: CharSequence = ", ",
|
||||
prefix: CharSequence = "",
|
||||
postfix: CharSequence = "",
|
||||
limit: Int = -1,
|
||||
truncated: CharSequence = "...",
|
||||
transform: ((T) -> CharSequence)? = null
|
||||
): CharSequence {
|
||||
return SpannedString(
|
||||
joinTo(
|
||||
SpannableStringBuilder(),
|
||||
separator,
|
||||
prefix,
|
||||
postfix,
|
||||
limit,
|
||||
truncated,
|
||||
transform
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
operator fun CharSequence.plus(other: CharSequence): CharSequence {
|
||||
return TextUtils.concat(this, other)
|
||||
}
|
||||
|
||||
fun String.bold(): CharSequence {
|
||||
return SpannableString(this).apply {
|
||||
setSpan(
|
||||
StyleSpan(Typeface.BOLD), 0, this.length,
|
||||
Spannable.SPAN_INCLUSIVE_EXCLUSIVE
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -13,12 +14,9 @@ import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.chip.Chip
|
||||
import net.vonforst.evmap.BR
|
||||
import net.vonforst.evmap.R
|
||||
import net.vonforst.evmap.*
|
||||
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
|
||||
@@ -27,6 +25,7 @@ import net.vonforst.evmap.viewmodel.*
|
||||
import java.time.ZoneId
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.time.format.FormatStyle
|
||||
import kotlin.math.max
|
||||
|
||||
interface Equatable {
|
||||
override fun equals(other: Any?): Boolean;
|
||||
@@ -114,7 +113,12 @@ class DetailAdapter : DataBindingAdapter<DetailAdapter.Detail>() {
|
||||
}
|
||||
}
|
||||
|
||||
fun buildDetails(loc: ChargeLocation?, ctx: Context): List<DetailAdapter.Detail> {
|
||||
fun buildDetails(
|
||||
loc: ChargeLocation?,
|
||||
chargeCards: Map<Long, ChargeCard>?,
|
||||
filteredChargeCards: Set<Long>?,
|
||||
ctx: Context
|
||||
): List<DetailAdapter.Detail> {
|
||||
if (loc == null) return emptyList()
|
||||
|
||||
return listOfNotNull(
|
||||
@@ -138,12 +142,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 +168,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, filteredChargeCards, ctx),
|
||||
clickable = true
|
||||
) else null,
|
||||
DetailAdapter.Detail(
|
||||
R.drawable.ic_location,
|
||||
R.string.coordinates,
|
||||
@@ -171,6 +189,33 @@ fun buildDetails(loc: ChargeLocation?, ctx: Context): List<DetailAdapter.Detail>
|
||||
)
|
||||
}
|
||||
|
||||
fun formatChargeCards(
|
||||
chargecards: List<ChargeCardId>,
|
||||
chargecardData: Map<Long, ChargeCard>?,
|
||||
filteredChargeCards: Set<Long>?,
|
||||
ctx: Context
|
||||
): CharSequence {
|
||||
if (chargecardData == null) return ""
|
||||
|
||||
val maxItems = 5
|
||||
var result = chargecards
|
||||
.sortedByDescending { filteredChargeCards?.contains(it.id) }
|
||||
.take(maxItems)
|
||||
.mapNotNull {
|
||||
val name = chargecardData[it.id]?.name ?: return@mapNotNull null
|
||||
if (filteredChargeCards?.contains(it.id) == true) {
|
||||
name.bold()
|
||||
} else {
|
||||
name
|
||||
}
|
||||
}.joinToSpannedString()
|
||||
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>() {
|
||||
@@ -342,15 +387,15 @@ class FiltersAdapter : DataBindingAdapter<FilterWithValue<FilterValue>>() {
|
||||
filter: SliderFilter,
|
||||
value: SliderFilterValue
|
||||
) {
|
||||
binding.progress = filter.inverseMapping(value.value)
|
||||
binding.mappedValue = value.value
|
||||
binding.progress = max(filter.inverseMapping(value.value) - filter.min, 0)
|
||||
binding.mappedValue = filter.mapping(binding.progress + filter.min)
|
||||
|
||||
binding.addOnPropertyChangedCallback(object :
|
||||
Observable.OnPropertyChangedCallback() {
|
||||
override fun onPropertyChanged(sender: Observable?, propertyId: Int) {
|
||||
when (propertyId) {
|
||||
BR.progress -> {
|
||||
val mapped = filter.mapping(binding.progress)
|
||||
val mapped = filter.mapping(binding.progress + filter.min)
|
||||
value.value = mapped
|
||||
binding.mappedValue = mapped
|
||||
}
|
||||
|
||||
@@ -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 ->
|
||||
|
||||
@@ -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/")
|
||||
|
||||
@@ -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,15 +60,26 @@ 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 {
|
||||
/**
|
||||
* maximum power available from this charger.
|
||||
*/
|
||||
val maxPower: Double
|
||||
get() {
|
||||
return chargepoints.map { it.power }.max() ?: 0.0
|
||||
return maxPower()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the maximum power available from certain connectors of this charger.
|
||||
*/
|
||||
fun maxPower(connectors: Set<String>? = null): Double {
|
||||
return chargepoints.filter { connectors?.contains(it.type) ?: true }
|
||||
.map { it.power }.max() ?: 0.0
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges chargepoints if they have the same plug and power
|
||||
*
|
||||
@@ -279,3 +290,8 @@ data class ChargeCard(
|
||||
val name: String,
|
||||
val url: String
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ChargeCardId(
|
||||
val id: Long
|
||||
)
|
||||
@@ -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
|
||||
@@ -90,6 +91,7 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
private var clusterMarkers: List<Marker> = emptyList()
|
||||
private var searchResultMarker: Marker? = null
|
||||
private var connectionErrorSnackbar: Snackbar? = null
|
||||
private var previousChargepointIds: Set<Long>? = null
|
||||
|
||||
private lateinit var clusterIconGenerator: ClusterIconGenerator
|
||||
private lateinit var chargerIconGenerator: ChargerIconGenerator
|
||||
@@ -385,7 +387,7 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
markers.forEach { (m, c) ->
|
||||
m.setIcon(
|
||||
chargerIconGenerator.getBitmapDescriptor(
|
||||
getMarkerTint(c)
|
||||
getMarkerTint(c, vm.filteredConnectors.value), fault = c.faultReport != null
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -396,7 +398,9 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
// highlight this marker
|
||||
marker.setIcon(
|
||||
chargerIconGenerator.getBitmapDescriptor(
|
||||
getMarkerTint(charger), highlight = true
|
||||
getMarkerTint(charger, vm.filteredConnectors.value),
|
||||
highlight = true,
|
||||
fault = charger.faultReport != null
|
||||
)
|
||||
)
|
||||
animator.animateMarkerBounce(marker)
|
||||
@@ -406,7 +410,7 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
if (m != marker) {
|
||||
m.setIcon(
|
||||
chargerIconGenerator.getBitmapDescriptor(
|
||||
getMarkerTint(c)
|
||||
getMarkerTint(c, vm.filteredConnectors.value), fault = c.faultReport != null
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -472,7 +476,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 +485,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 +504,30 @@ 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 filteredChargeCards = vm.filteredChargeCards.value
|
||||
|
||||
val data = chargecards.mapNotNull { chargecardData[it.id] }
|
||||
.sortedBy { it.name }
|
||||
.sortedByDescending { filteredChargeCards?.contains(it.id) }
|
||||
val names = data.map {
|
||||
if (filteredChargeCards?.contains(it.id) == true) {
|
||||
it.name.bold()
|
||||
} else {
|
||||
it.name
|
||||
}
|
||||
}
|
||||
AlertDialog.Builder(activity)
|
||||
.setTitle(R.string.charge_cards)
|
||||
.setItems(names.toTypedArray()) { _, i ->
|
||||
val card = data[i]
|
||||
(activity as? MapsActivity)?.openUrl("https:${card.url}")
|
||||
}.show()
|
||||
}
|
||||
|
||||
override fun onMapReady(map: GoogleMap) {
|
||||
this.map = map
|
||||
map.uiSettings.isTiltGesturesEnabled = false
|
||||
@@ -616,6 +647,7 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
) == PackageManager.PERMISSION_GRANTED
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun updateMap(chargepoints: List<ChargepointListItem>) {
|
||||
val map = this.map ?: return
|
||||
clusterMarkers.forEach { it.remove() }
|
||||
@@ -624,34 +656,54 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
val chargers = chargepoints.filterIsInstance<ChargeLocation>()
|
||||
|
||||
val chargepointIds = chargers.map { it.id }.toSet()
|
||||
// remove markers that disappeared
|
||||
markers.entries.toList().forEach {
|
||||
if (!chargepointIds.contains(it.value.id)) {
|
||||
if (it.key.isVisible) {
|
||||
val tint = getMarkerTint(it.value)
|
||||
val highlight = it.value == vm.chargerSparse.value
|
||||
animator.animateMarkerDisappear(it.key, tint, highlight)
|
||||
} else {
|
||||
it.key.remove()
|
||||
}
|
||||
markers.remove(it.key)
|
||||
}
|
||||
}
|
||||
// add new markers
|
||||
chargers.filter {
|
||||
!markers.containsValue(it)
|
||||
}.forEach { charger ->
|
||||
val tint = getMarkerTint(charger)
|
||||
val highlight = charger == vm.chargerSparse.value
|
||||
val marker = map.addMarker(
|
||||
MarkerOptions()
|
||||
.position(LatLng(charger.coordinates.lat, charger.coordinates.lng))
|
||||
.icon(
|
||||
chargerIconGenerator.getBitmapDescriptor(tint, highlight = highlight)
|
||||
)
|
||||
|
||||
// update icons of existing markers (connector filter may have changed)
|
||||
for ((marker, charger) in markers) {
|
||||
marker.setIcon(
|
||||
chargerIconGenerator.getBitmapDescriptor(
|
||||
getMarkerTint(charger, vm.filteredConnectors.value),
|
||||
highlight = charger == vm.chargerSparse.value,
|
||||
fault = charger.faultReport != null
|
||||
)
|
||||
)
|
||||
animator.animateMarkerAppear(marker, tint, highlight)
|
||||
markers[marker] = charger
|
||||
}
|
||||
|
||||
if (chargers.toSet() != markers.values) {
|
||||
// remove markers that disappeared
|
||||
val bounds = map.projection.visibleRegion.latLngBounds
|
||||
markers.entries.toList().forEach {
|
||||
val marker = it.key
|
||||
val charger = it.value
|
||||
if (!chargepointIds.contains(charger.id)) {
|
||||
// animate marker if it is visible, otherwise remove immediately
|
||||
if (bounds.contains(marker.position)) {
|
||||
val tint = getMarkerTint(charger, vm.filteredConnectors.value)
|
||||
val highlight = charger == vm.chargerSparse.value
|
||||
val fault = charger.faultReport != null
|
||||
animator.animateMarkerDisappear(marker, tint, highlight, fault)
|
||||
} else {
|
||||
animator.deleteMarker(marker)
|
||||
}
|
||||
markers.remove(marker)
|
||||
}
|
||||
}
|
||||
// add new markers
|
||||
val map1 = markers.values.map { it.id }
|
||||
for (charger in chargers) {
|
||||
if (!map1.contains(charger.id)) {
|
||||
val tint = getMarkerTint(charger, vm.filteredConnectors.value)
|
||||
val highlight = charger == vm.chargerSparse.value
|
||||
val fault = charger.faultReport != null
|
||||
val marker = map.addMarker(
|
||||
MarkerOptions()
|
||||
.position(LatLng(charger.coordinates.lat, charger.coordinates.lng))
|
||||
.visible(false)
|
||||
)
|
||||
animator.animateMarkerAppear(marker, tint, highlight, fault)
|
||||
markers[marker] = charger
|
||||
}
|
||||
}
|
||||
previousChargepointIds = chargepointIds
|
||||
}
|
||||
clusterMarkers = clusters.map { cluster ->
|
||||
map.addMarker(
|
||||
|
||||
@@ -64,7 +64,7 @@ class MultiSelectDialog : AppCompatDialogFragment() {
|
||||
list.adapter = adapter
|
||||
list.layoutManager = LinearLayoutManager(view.context)
|
||||
|
||||
items = data.entries.toList().sortedBy { it.key }.map {
|
||||
items = data.entries.toList().sortedBy { it.value }.map {
|
||||
MultiSelectItem(it.key, it.value, it.key in selected)
|
||||
}
|
||||
adapter.submitList(items)
|
||||
|
||||
@@ -10,13 +10,18 @@ import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import net.vonforst.evmap.MapsActivity
|
||||
import net.vonforst.evmap.R
|
||||
import net.vonforst.evmap.storage.PreferenceDataSource
|
||||
import net.vonforst.evmap.ui.updateNightMode
|
||||
|
||||
|
||||
class SettingsFragment : PreferenceFragmentCompat(),
|
||||
SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
private lateinit var prefs: PreferenceDataSource
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
val toolbar = view.findViewById(R.id.toolbar) as Toolbar
|
||||
prefs = PreferenceDataSource(requireContext())
|
||||
|
||||
val navController = findNavController()
|
||||
toolbar.setupWithNavController(
|
||||
@@ -43,6 +48,9 @@ class SettingsFragment : PreferenceFragmentCompat(),
|
||||
it.startActivity(it.intent);
|
||||
}
|
||||
}
|
||||
"darkmode" -> {
|
||||
updateNightMode(prefs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,15 @@ 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")!!
|
||||
|
||||
val darkmode: String
|
||||
get() = sp.getString("darkmode", "default")!!
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -42,23 +42,26 @@ 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 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 cacheSize = 420; // 420 items: 21 sizes, 5 colors, highlight on/off, fault on/off
|
||||
val cache = LruCache<BitmapData, BitmapDescriptor>(cacheSize)
|
||||
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()
|
||||
}
|
||||
|
||||
fun preloadCache() {
|
||||
private fun preloadCache() {
|
||||
// pre-generates images for scale from 0 to 255 for all possible tint colors
|
||||
val tints = listOf(
|
||||
R.color.charger_100kw,
|
||||
@@ -67,11 +70,12 @@ 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) {
|
||||
getBitmapDescriptor(tint, scale, 255, highlight, fault)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -81,16 +85,18 @@ 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)
|
||||
cachedImg
|
||||
} else {
|
||||
val bitmap = generateBitmap(data)
|
||||
cache.put(data, bitmap)
|
||||
BitmapDescriptorFactory.fromBitmap(bitmap)
|
||||
val bmd = BitmapDescriptorFactory.fromBitmap(bitmap)
|
||||
cache.put(data, bmd)
|
||||
bmd
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,6 +142,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
|
||||
}
|
||||
}
|
||||
@@ -10,96 +10,121 @@ import net.vonforst.evmap.R
|
||||
import net.vonforst.evmap.api.goingelectric.ChargeLocation
|
||||
import kotlin.math.max
|
||||
|
||||
fun getMarkerTint(charger: ChargeLocation): Int = when {
|
||||
charger.maxPower >= 100 -> R.color.charger_100kw
|
||||
charger.maxPower >= 43 -> R.color.charger_43kw
|
||||
charger.maxPower >= 20 -> R.color.charger_20kw
|
||||
charger.maxPower >= 11 -> R.color.charger_11kw
|
||||
fun getMarkerTint(
|
||||
charger: ChargeLocation,
|
||||
connectors: Set<String>?
|
||||
): Int = when {
|
||||
charger.maxPower(connectors) >= 100 -> R.color.charger_100kw
|
||||
charger.maxPower(connectors) >= 43 -> R.color.charger_43kw
|
||||
charger.maxPower(connectors) >= 20 -> R.color.charger_20kw
|
||||
charger.maxPower(connectors) >= 11 -> R.color.charger_11kw
|
||||
else -> R.color.charger_low
|
||||
}
|
||||
|
||||
class MarkerAnimator(val gen: ChargerIconGenerator) {
|
||||
val animatingMarkers = hashMapOf<Marker, ValueAnimator>()
|
||||
private val animatingMarkers = hashMapOf<String, ValueAnimator>()
|
||||
|
||||
fun animateMarkerAppear(
|
||||
marker: Marker,
|
||||
tint: Int,
|
||||
highlight: Boolean
|
||||
highlight: Boolean,
|
||||
fault: Boolean
|
||||
) {
|
||||
animatingMarkers[marker]?.cancel()
|
||||
animatingMarkers.remove(marker)
|
||||
animatingMarkers[marker.id]?.let {
|
||||
it.cancel()
|
||||
animatingMarkers.remove(marker.id)
|
||||
}
|
||||
|
||||
val anim = ValueAnimator.ofInt(0, 20).apply {
|
||||
duration = 250
|
||||
interpolator = LinearOutSlowInInterpolator()
|
||||
addUpdateListener { animationState ->
|
||||
if (!marker.isVisible) {
|
||||
cancel()
|
||||
animatingMarkers.remove(marker)
|
||||
return@addUpdateListener
|
||||
}
|
||||
val scale = animationState.animatedValue as Int
|
||||
marker.setIcon(
|
||||
gen.getBitmapDescriptor(tint, scale = scale, highlight = highlight)
|
||||
gen.getBitmapDescriptor(
|
||||
tint,
|
||||
scale = scale,
|
||||
highlight = highlight,
|
||||
fault = fault
|
||||
)
|
||||
)
|
||||
marker.isVisible = true
|
||||
}
|
||||
addListener(onEnd = {
|
||||
animatingMarkers.remove(marker)
|
||||
animatingMarkers.remove(marker.id)
|
||||
}, onCancel = {
|
||||
animatingMarkers.remove(marker.id)
|
||||
})
|
||||
}
|
||||
animatingMarkers[marker] = anim
|
||||
animatingMarkers[marker.id] = anim
|
||||
anim.start()
|
||||
}
|
||||
|
||||
fun animateMarkerDisappear(
|
||||
marker: Marker,
|
||||
tint: Int,
|
||||
highlight: Boolean
|
||||
highlight: Boolean,
|
||||
fault: Boolean
|
||||
) {
|
||||
animatingMarkers[marker]?.cancel()
|
||||
animatingMarkers.remove(marker)
|
||||
animatingMarkers[marker.id]?.let {
|
||||
it.cancel()
|
||||
animatingMarkers.remove(marker.id)
|
||||
}
|
||||
|
||||
val anim = ValueAnimator.ofInt(20, 0).apply {
|
||||
duration = 200
|
||||
interpolator = FastOutLinearInInterpolator()
|
||||
addUpdateListener { animationState ->
|
||||
if (!marker.isVisible) {
|
||||
cancel()
|
||||
animatingMarkers.remove(marker)
|
||||
return@addUpdateListener
|
||||
}
|
||||
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 = {
|
||||
animatingMarkers.remove(marker)
|
||||
marker.remove()
|
||||
animatingMarkers.remove(marker.id)
|
||||
}, onCancel = {
|
||||
marker.remove()
|
||||
animatingMarkers.remove(marker.id)
|
||||
})
|
||||
}
|
||||
animatingMarkers[marker] = anim
|
||||
animatingMarkers[marker.id] = anim
|
||||
anim.start()
|
||||
}
|
||||
|
||||
fun deleteMarker(marker: Marker) {
|
||||
animatingMarkers[marker.id]?.let {
|
||||
it.cancel()
|
||||
animatingMarkers.remove(marker.id)
|
||||
}
|
||||
marker.remove()
|
||||
}
|
||||
|
||||
fun animateMarkerBounce(marker: Marker) {
|
||||
animatingMarkers[marker]?.cancel()
|
||||
animatingMarkers.remove(marker)
|
||||
animatingMarkers[marker.id]?.let {
|
||||
it.cancel()
|
||||
animatingMarkers.remove(marker.id)
|
||||
}
|
||||
|
||||
val anim = ValueAnimator.ofFloat(0f, 1f).apply {
|
||||
duration = 700
|
||||
interpolator = BounceInterpolator()
|
||||
addUpdateListener { state ->
|
||||
if (!marker.isVisible) {
|
||||
cancel()
|
||||
animatingMarkers.remove(marker)
|
||||
return@addUpdateListener
|
||||
}
|
||||
val t = max(1f - state.animatedValue as Float, 0f) / 2
|
||||
marker.setAnchor(0.5f, 1.0f + t)
|
||||
}
|
||||
addListener(onEnd = {
|
||||
animatingMarkers.remove(marker.id)
|
||||
}, onCancel = {
|
||||
animatingMarkers.remove(marker.id)
|
||||
})
|
||||
}
|
||||
animatingMarkers[marker] = anim
|
||||
animatingMarkers[marker.id] = anim
|
||||
anim.start()
|
||||
}
|
||||
}
|
||||
14
app/src/main/java/net/vonforst/evmap/ui/NightModeUtils.kt
Normal file
14
app/src/main/java/net/vonforst/evmap/ui/NightModeUtils.kt
Normal file
@@ -0,0 +1,14 @@
|
||||
package net.vonforst.evmap.ui
|
||||
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import net.vonforst.evmap.storage.PreferenceDataSource
|
||||
|
||||
fun updateNightMode(prefs: PreferenceDataSource) {
|
||||
AppCompatDelegate.setDefaultNightMode(
|
||||
when (prefs.darkmode) {
|
||||
"on" -> AppCompatDelegate.MODE_NIGHT_YES
|
||||
"off" -> AppCompatDelegate.MODE_NIGHT_NO
|
||||
else -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
package net.vonforst.evmap.utils
|
||||
|
||||
import android.annotation.TargetApi
|
||||
import android.content.Context
|
||||
import android.content.ContextWrapper
|
||||
import android.content.res.Configuration
|
||||
@@ -33,10 +32,5 @@ class LocaleContextWrapper(base: Context?) : ContextWrapper(base) {
|
||||
}
|
||||
return LocaleContextWrapper(ctx)
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.N)
|
||||
fun setSystemLocale(config: Configuration, locale: Locale?) {
|
||||
config.setLocale(locale)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,12 +20,12 @@ class DonateViewModel(application: Application) : AndroidViewModel(application),
|
||||
override fun onBillingServiceDisconnected() {
|
||||
}
|
||||
|
||||
override fun onBillingSetupFinished(p0: BillingResult?) {
|
||||
override fun onBillingSetupFinished(p0: BillingResult) {
|
||||
loadProducts()
|
||||
|
||||
// consume pending purchases
|
||||
val purchases = billingClient.queryPurchases(BillingClient.SkuType.INAPP)
|
||||
purchases.purchasesList.forEach {
|
||||
purchases.purchasesList?.forEach {
|
||||
if (!it.isAcknowledged) {
|
||||
consumePurchase(it.purchaseToken, false)
|
||||
}
|
||||
@@ -53,7 +53,7 @@ class DonateViewModel(application: Application) : AndroidViewModel(application),
|
||||
)
|
||||
.build()
|
||||
billingClient.querySkuDetailsAsync(params) { result, details ->
|
||||
if (result.responseCode == BillingClient.BillingResponseCode.OK) {
|
||||
if (result.responseCode == BillingClient.BillingResponseCode.OK && details != null) {
|
||||
products.value = Resource.success(details
|
||||
.sortedBy { it.priceAmountMicros }
|
||||
.map { DonationItem(it) }
|
||||
|
||||
@@ -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,
|
||||
@@ -80,16 +81,19 @@ private fun MediatorLiveData<List<Filter<FilterValue>>>.buildFilters(
|
||||
SliderFilter(
|
||||
application.getString(R.string.filter_min_connectors),
|
||||
"min_connectors",
|
||||
10
|
||||
10,
|
||||
min = 1
|
||||
),
|
||||
MultipleChoiceFilter(
|
||||
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")
|
||||
)
|
||||
}
|
||||
|
||||
@@ -182,6 +186,7 @@ data class SliderFilter(
|
||||
override val name: String,
|
||||
override val key: String,
|
||||
val max: Int,
|
||||
val min: Int = 0,
|
||||
val mapping: ((Int) -> Int) = { it },
|
||||
val inverseMapping: ((Int) -> Int) = { it },
|
||||
val unit: String? = ""
|
||||
|
||||
@@ -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
|
||||
@@ -82,6 +93,12 @@ class MapViewModel(application: Application, geApiKey: String) : AndroidViewMode
|
||||
}
|
||||
}
|
||||
}
|
||||
val filteredConnectors: MutableLiveData<Set<String>> by lazy {
|
||||
MutableLiveData<Set<String>>()
|
||||
}
|
||||
val filteredChargeCards: MutableLiveData<Set<Long>> by lazy {
|
||||
MutableLiveData<Set<Long>>()
|
||||
}
|
||||
|
||||
val chargerSparse: MutableLiveData<ChargeLocation> by lazy {
|
||||
MutableLiveData<ChargeLocation>()
|
||||
@@ -153,7 +170,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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,10 +206,15 @@ class MapViewModel(application: Application, geApiKey: String) : AndroidViewMode
|
||||
chargepointLoader?.cancel()
|
||||
|
||||
chargepoints.value = Resource.loading(chargepoints.value?.data)
|
||||
filteredConnectors.value = null
|
||||
filteredChargeCards.value = null
|
||||
val bounds = mapPosition.bounds
|
||||
val zoom = mapPosition.zoom
|
||||
chargepointLoader = viewModelScope.launch {
|
||||
chargepoints.value = getChargepointsWithFilters(bounds, zoom, filters)
|
||||
val result = getChargepointsWithFilters(bounds, zoom, filters)
|
||||
filteredConnectors.value = result.second
|
||||
filteredChargeCards.value = result.third
|
||||
chargepoints.value = result.first
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,30 +222,36 @@ class MapViewModel(application: Application, geApiKey: String) : AndroidViewMode
|
||||
bounds: LatLngBounds,
|
||||
zoom: Float,
|
||||
filters: List<FilterWithValue<out FilterValue>>
|
||||
): Resource<List<ChargepointListItem>> {
|
||||
): Triple<Resource<List<ChargepointListItem>>, Set<String>?, Set<Long>?> {
|
||||
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")
|
||||
|
||||
val connectorsVal = getMultipleChoiceValue(filters, "connectors")
|
||||
if (connectorsVal.values.isEmpty() && !connectorsVal.all) {
|
||||
// no connectors chosen
|
||||
return Resource.success(emptyList())
|
||||
return Triple(Resource.success(emptyList()), null, null)
|
||||
}
|
||||
val connectors = formatMultipleChoice(connectorsVal)
|
||||
val filteredConnectors = if (connectorsVal.all) null else connectorsVal.values
|
||||
|
||||
val chargeCardsVal = getMultipleChoiceValue(filters, "chargecards")
|
||||
if (chargeCardsVal.values.isEmpty() && !chargeCardsVal.all) {
|
||||
// no chargeCards chosen
|
||||
return Resource.success(emptyList())
|
||||
return Triple(Resource.success(emptyList()), filteredConnectors, null)
|
||||
}
|
||||
val chargeCards = formatMultipleChoice(chargeCardsVal)
|
||||
val filteredChargeCards =
|
||||
if (chargeCardsVal.all) null else chargeCardsVal.values.map { it.toLong() }.toSet()
|
||||
|
||||
val networksVal = getMultipleChoiceValue(filters, "networks")
|
||||
if (networksVal.values.isEmpty() && !networksVal.all) {
|
||||
// no networks chosen
|
||||
return Resource.success(emptyList())
|
||||
return Triple(Resource.success(emptyList()), filteredConnectors, filteredChargeCards)
|
||||
}
|
||||
val networks = formatMultipleChoice(networksVal)
|
||||
|
||||
@@ -246,20 +277,31 @@ 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,
|
||||
startkey = startkey
|
||||
)
|
||||
if (!response.isSuccessful || response.body()!!.status != "ok") {
|
||||
return Resource.error(response.message(), chargepoints.value?.data)
|
||||
return Triple(
|
||||
Resource.error(response.message(), chargepoints.value?.data),
|
||||
null,
|
||||
null
|
||||
)
|
||||
} else {
|
||||
val body = response.body()!!
|
||||
data.addAll(body.chargelocations)
|
||||
startkey = body.startkey
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
return Resource.error(e.message, chargepoints.value?.data)
|
||||
return Triple(
|
||||
Resource.error(e.message, chargepoints.value?.data),
|
||||
filteredConnectors,
|
||||
filteredChargeCards
|
||||
)
|
||||
}
|
||||
} while (startkey != null && startkey < 10000)
|
||||
|
||||
@@ -281,7 +323,7 @@ class MapViewModel(application: Application, geApiKey: String) : AndroidViewMode
|
||||
}
|
||||
}
|
||||
|
||||
return Resource.success(result)
|
||||
return Triple(Resource.success(result), filteredConnectors, filteredChargeCards)
|
||||
}
|
||||
|
||||
private fun formatMultipleChoice(connectorsVal: MultipleChoiceFilterValue) =
|
||||
@@ -297,6 +339,11 @@ class MapViewModel(application: Application, geApiKey: String) : AndroidViewMode
|
||||
key: String
|
||||
) = (filters.find { it.value.key == key }!!.value as SliderFilterValue).value
|
||||
|
||||
private fun getMultipleChoiceFilter(
|
||||
filters: List<FilterWithValue<out FilterValue>>,
|
||||
key: String
|
||||
) = filters.find { it.value.key == key }!!.filter as MultipleChoiceFilter
|
||||
|
||||
private fun getMultipleChoiceValue(
|
||||
filters: List<FilterWithValue<out FilterValue>>,
|
||||
key: String
|
||||
|
||||
49
app/src/main/res/drawable/ic_appicon_splashscreen.xml
Normal file
49
app/src/main/res/drawable/ic_appicon_splashscreen.xml
Normal 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>
|
||||
12
app/src/main/res/drawable/ic_map_marker_fault.xml
Normal file
12
app/src/main/res/drawable/ic_map_marker_fault.xml
Normal 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>
|
||||
10
app/src/main/res/drawable/ic_payment.xml
Normal file
10
app/src/main/res/drawable/ic_payment.xml
Normal 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>
|
||||
8
app/src/main/res/drawable/launch_screen.xml
Normal file
8
app/src/main/res/drawable/launch_screen.xml
Normal 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>
|
||||
@@ -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,14 @@
|
||||
name="availability"
|
||||
type="Resource<ChargeLocationStatus>" />
|
||||
|
||||
<variable
|
||||
name="chargeCards"
|
||||
type="java.util.Map<Long, ChargeCard>" />
|
||||
|
||||
<variable
|
||||
name="filteredChargeCards"
|
||||
type="java.util.Set<Long>" />
|
||||
|
||||
</data>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
@@ -182,7 +192,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, filteredChargeCards, context)}"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="1.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
|
||||
@@ -86,7 +86,9 @@
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/etSearch"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
android:layout_height="wrap_content"
|
||||
android:imeOptions="actionDone"
|
||||
android:singleLine="true" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -126,7 +126,9 @@
|
||||
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}"
|
||||
app:filteredChargeCards="@{vm.filteredChargeCards}" />
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:max="@{((SliderFilter) item.filter).max}"
|
||||
android:max="@{((SliderFilter) item.filter).max - ((SliderFilter) item.filter).min}"
|
||||
android:progress="@={progress}"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/textView18"
|
||||
|
||||
@@ -5,4 +5,9 @@
|
||||
<item>Englisch</item>
|
||||
<item>Deutsch</item>
|
||||
</string-array>
|
||||
<string-array name="pref_darkmode_names">
|
||||
<item>Geräteeinstellung verwenden</item>
|
||||
<item>immer an</item>
|
||||
<item>immer aus</item>
|
||||
</string-array>
|
||||
</resources>
|
||||
@@ -92,6 +92,17 @@
|
||||
<string name="ok">OK</string>
|
||||
<string name="pref_language">Sprache</string>
|
||||
<string name="pref_language_summary">App-Sprache ändern</string>
|
||||
<string name="pref_darkmode">Dunkles Design</string>
|
||||
<string name="pref_darkmode_summary">Einstellen, wann der Nachtmodus genutzt wird</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>
|
||||
@@ -10,4 +10,14 @@
|
||||
<item>en</item>
|
||||
<item>de</item>
|
||||
</string-array>
|
||||
<string-array name="pref_darkmode_names">
|
||||
<item>Device default</item>
|
||||
<item>always on</item>
|
||||
<item>always off</item>
|
||||
</string-array>
|
||||
<string-array name="pref_darkmode_values" tranlatable="false">
|
||||
<item>default</item>
|
||||
<item>on</item>
|
||||
<item>off</item>
|
||||
</string-array>
|
||||
</resources>
|
||||
@@ -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>
|
||||
|
||||
@@ -91,6 +91,17 @@
|
||||
<string name="ok">OK</string>
|
||||
<string name="pref_language">Language</string>
|
||||
<string name="pref_language_summary">Change the app language</string>
|
||||
<string name="pref_darkmode">Dark mode</string>
|
||||
<string name="pref_darkmode_summary">Set when dark mode is activated</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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -18,5 +18,13 @@
|
||||
android:defaultValue="default"
|
||||
android:summary="@string/pref_language_summary" />
|
||||
|
||||
<ListPreference
|
||||
android:key="darkmode"
|
||||
android:title="@string/pref_darkmode"
|
||||
android:entries="@array/pref_darkmode_names"
|
||||
android:entryValues="@array/pref_darkmode_values"
|
||||
android:defaultValue="default"
|
||||
android:summary="@string/pref_darkmode_summary" />
|
||||
|
||||
</PreferenceCategory>
|
||||
</PreferenceScreen>
|
||||
Reference in New Issue
Block a user