mirror of
https://github.com/ev-map/EVMap.git
synced 2025-12-27 09:07:46 -05:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9df24081d4 | ||
|
|
255001b768 | ||
|
|
55af84b7de | ||
|
|
4f6f09dc83 | ||
|
|
7f6d0c1391 | ||
|
|
96b60d0f49 | ||
|
|
2824f0b5c3 | ||
|
|
af0921ed20 | ||
|
|
a5b55479cb | ||
|
|
a93bacd9b3 | ||
|
|
9d7278e0e2 | ||
|
|
f6d9c615a0 | ||
|
|
a8ee3f5b7d | ||
|
|
826b4f89f1 | ||
|
|
5675d065e3 | ||
|
|
3e3531551d | ||
|
|
5d7d881729 | ||
|
|
23c73e3d7e | ||
|
|
7835aa8d78 | ||
|
|
f06b712090 | ||
|
|
317695954d | ||
|
|
24cfd1c10b | ||
|
|
775faa2f55 | ||
|
|
08bd2bdf5a |
@@ -13,8 +13,8 @@ android {
|
||||
applicationId "net.vonforst.evmap"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 31
|
||||
versionCode 65
|
||||
versionName "1.1.1"
|
||||
versionCode 67
|
||||
versionName "1.1.3"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
@@ -103,21 +103,25 @@ android {
|
||||
variant.resValue "string", "chargeprice_key", chargepriceKey
|
||||
}
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
disable 'NullSafeMutableLiveData'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
implementation 'androidx.appcompat:appcompat:1.3.1'
|
||||
implementation 'androidx.core:core-ktx:1.6.0'
|
||||
implementation 'androidx.core:core-ktx:1.7.0'
|
||||
implementation 'androidx.core:core-splashscreen:1.0.0-alpha02'
|
||||
implementation "androidx.activity:activity-ktx:1.3.1"
|
||||
implementation "androidx.activity:activity-ktx:1.4.0"
|
||||
implementation "androidx.fragment:fragment-ktx:1.3.6"
|
||||
implementation 'androidx.cardview:cardview:1.0.0'
|
||||
implementation 'androidx.preference:preference-ktx:1.1.1'
|
||||
implementation 'com.google.android.material:material:1.4.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.1'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.2.1'
|
||||
implementation 'androidx.browser:browser:1.3.0'
|
||||
implementation 'androidx.browser:browser:1.4.0'
|
||||
implementation 'com.github.johan12345:CustomBottomSheetBehavior:f69f532660'
|
||||
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
|
||||
implementation 'com.squareup.retrofit2:converter-moshi:2.9.0'
|
||||
@@ -139,8 +143,8 @@ dependencies {
|
||||
implementation 'com.github.romandanylyk:PageIndicatorView:b1bad589b5'
|
||||
|
||||
// Android Auto
|
||||
googleImplementation 'androidx.car.app:app:1.1.0-beta01'
|
||||
googleImplementation 'androidx.car.app:app-projected:1.1.0-beta01'
|
||||
googleImplementation 'androidx.car.app:app:1.1.0-rc01'
|
||||
googleImplementation 'androidx.car.app:app-projected:1.1.0-rc01'
|
||||
|
||||
// AnyMaps
|
||||
def anyMapsVersion = '751daec281'
|
||||
@@ -161,7 +165,7 @@ dependencies {
|
||||
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
|
||||
|
||||
// viewmodel library
|
||||
def lifecycle_version = "2.3.1"
|
||||
def lifecycle_version = "2.4.0"
|
||||
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
|
||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
|
||||
|
||||
@@ -185,7 +189,7 @@ dependencies {
|
||||
// debug tools
|
||||
implementation 'com.facebook.stetho:stetho:1.5.1'
|
||||
implementation 'com.facebook.stetho:stetho-okhttp3:1.5.1'
|
||||
testImplementation 'junit:junit:4.13.1'
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
testImplementation "com.squareup.okhttp3:mockwebserver:4.9.0"
|
||||
//noinspection GradleDependency
|
||||
testImplementation 'org.json:json:20080701'
|
||||
|
||||
@@ -7,10 +7,16 @@ import com.google.android.gms.common.ConnectionResult
|
||||
import com.google.android.gms.common.GoogleApiAvailability
|
||||
import com.google.android.gms.maps.MapsInitializer
|
||||
import com.google.android.libraries.places.api.Places
|
||||
import net.vonforst.evmap.storage.PreferenceDataSource
|
||||
import net.vonforst.evmap.utils.LocaleContextWrapper
|
||||
|
||||
fun init(context: Context) {
|
||||
Places.initialize(context, context.getString(R.string.google_maps_key))
|
||||
MapsInitializer.initialize(context.applicationContext, MapsInitializer.Renderer.LATEST, null)
|
||||
|
||||
val localeContext = LocaleContextWrapper.wrap(
|
||||
context.applicationContext, PreferenceDataSource(context).language
|
||||
)
|
||||
MapsInitializer.initialize(localeContext, MapsInitializer.Renderer.LATEST, null)
|
||||
}
|
||||
|
||||
fun checkPlayServices(activity: Activity): Boolean {
|
||||
|
||||
@@ -14,14 +14,20 @@ import androidx.car.app.model.*
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.graphics.drawable.IconCompat
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import moe.banana.jsonapi2.HasMany
|
||||
import moe.banana.jsonapi2.HasOne
|
||||
import moe.banana.jsonapi2.JsonBuffer
|
||||
import moe.banana.jsonapi2.ResourceIdentifier
|
||||
import net.vonforst.evmap.*
|
||||
import net.vonforst.evmap.api.chargeprice.*
|
||||
import net.vonforst.evmap.model.ChargeLocation
|
||||
import net.vonforst.evmap.storage.AppDatabase
|
||||
import net.vonforst.evmap.storage.PreferenceDataSource
|
||||
import net.vonforst.evmap.ui.currency
|
||||
import java.io.IOException
|
||||
|
||||
class ChargepriceScreen(ctx: CarContext, val charger: ChargeLocation) : Screen(ctx) {
|
||||
private val prefs = PreferenceDataSource(ctx)
|
||||
@@ -164,94 +170,120 @@ class ChargepriceScreen(ctx: CarContext, val charger: ChargeLocation) : Screen(c
|
||||
val manufacturer = model?.manufacturer?.value
|
||||
val modelName = model?.name?.value
|
||||
lifecycleScope.launch {
|
||||
var vehicles = api.getVehicles().filter {
|
||||
it.id in prefs.chargepriceMyVehicles
|
||||
}
|
||||
if (vehicles.isEmpty()) {
|
||||
errorMessage = carContext.getString(R.string.chargeprice_select_car_first)
|
||||
invalidate()
|
||||
return@launch
|
||||
} else if (vehicles.size > 1) {
|
||||
if (manufacturer != null && modelName != null) {
|
||||
vehicles = vehicles.filter {
|
||||
it.brand == manufacturer && it.name.startsWith(modelName)
|
||||
}
|
||||
if (vehicles.isEmpty()) {
|
||||
errorMessage = carContext.getString(
|
||||
R.string.auto_chargeprice_vehicle_unknown,
|
||||
manufacturer,
|
||||
modelName
|
||||
)
|
||||
invalidate()
|
||||
return@launch
|
||||
} else if (vehicles.size > 1) {
|
||||
errorMessage = carContext.getString(
|
||||
R.string.auto_chargeprice_vehicle_ambiguous,
|
||||
manufacturer,
|
||||
modelName
|
||||
)
|
||||
try {
|
||||
var vehicles = api.getVehicles().filter {
|
||||
it.id in prefs.chargepriceMyVehicles
|
||||
}
|
||||
if (vehicles.isEmpty()) {
|
||||
errorMessage = carContext.getString(R.string.chargeprice_select_car_first)
|
||||
invalidate()
|
||||
return@launch
|
||||
} else if (vehicles.size > 1) {
|
||||
if (manufacturer != null && modelName != null) {
|
||||
vehicles = vehicles.filter {
|
||||
it.brand == manufacturer && it.name.startsWith(modelName)
|
||||
}
|
||||
if (vehicles.isEmpty()) {
|
||||
errorMessage = carContext.getString(
|
||||
R.string.auto_chargeprice_vehicle_unknown,
|
||||
manufacturer,
|
||||
modelName
|
||||
)
|
||||
invalidate()
|
||||
return@launch
|
||||
} else if (vehicles.size > 1) {
|
||||
errorMessage = carContext.getString(
|
||||
R.string.auto_chargeprice_vehicle_ambiguous,
|
||||
manufacturer,
|
||||
modelName
|
||||
)
|
||||
invalidate()
|
||||
return@launch
|
||||
}
|
||||
} else {
|
||||
errorMessage =
|
||||
carContext.getString(R.string.auto_chargeprice_vehicle_unavailable)
|
||||
invalidate()
|
||||
return@launch
|
||||
}
|
||||
} else {
|
||||
}
|
||||
val car = vehicles[0]
|
||||
|
||||
val cpStation = ChargepriceStation.fromEvmap(charger, car.compatibleEvmapConnectors)
|
||||
val result = api.getChargePrices(ChargepriceRequest().apply {
|
||||
this.dataAdapter = dataAdapter
|
||||
station = cpStation
|
||||
vehicle = HasOne(car)
|
||||
tariffs = if (!prefs.chargepriceMyTariffsAll) {
|
||||
val myTariffs = prefs.chargepriceMyTariffs ?: emptySet()
|
||||
HasMany<ChargepriceTariff>(*myTariffs.map {
|
||||
ResourceIdentifier(
|
||||
"tariff",
|
||||
it
|
||||
)
|
||||
}.toTypedArray()).apply {
|
||||
meta = JsonBuffer.create(
|
||||
ChargepriceApi.moshi.adapter(ChargepriceRequestTariffMeta::class.java),
|
||||
ChargepriceRequestTariffMeta(ChargepriceInclude.ALWAYS)
|
||||
)
|
||||
}
|
||||
} else null
|
||||
options = ChargepriceOptions(
|
||||
batteryRange = batteryRange,
|
||||
providerCustomerTariffs = prefs.chargepriceShowProviderCustomerTariffs,
|
||||
maxMonthlyFees = if (prefs.chargepriceNoBaseFee) 0.0 else null,
|
||||
currency = prefs.chargepriceCurrency
|
||||
)
|
||||
}, ChargepriceApi.getChargepriceLanguage())
|
||||
|
||||
val myTariffs = prefs.chargepriceMyTariffs
|
||||
|
||||
// choose the highest power chargepoint compatible with the car
|
||||
val chargepoint = cpStation.chargePoints.filterIndexed { i, cp ->
|
||||
charger.chargepointsMerged[i].type in car.compatibleEvmapConnectors
|
||||
}.maxByOrNull { it.power }
|
||||
if (chargepoint == null) {
|
||||
errorMessage =
|
||||
carContext.getString(R.string.auto_chargeprice_vehicle_unavailable)
|
||||
carContext.getString(R.string.chargeprice_no_compatible_connectors)
|
||||
invalidate()
|
||||
return@launch
|
||||
}
|
||||
}
|
||||
val car = vehicles[0]
|
||||
meta =
|
||||
(result.meta.get<ChargepriceMeta>(ChargepriceApi.moshi.adapter(ChargepriceMeta::class.java)) as ChargepriceMeta).chargePoints.filterIndexed { i, cp ->
|
||||
charger.chargepointsMerged[i].type in car.compatibleEvmapConnectors
|
||||
}.maxByOrNull {
|
||||
it.power
|
||||
}
|
||||
|
||||
val cpStation = ChargepriceStation.fromEvmap(charger, car.compatibleEvmapConnectors)
|
||||
val result = api.getChargePrices(ChargepriceRequest().apply {
|
||||
this.dataAdapter = dataAdapter
|
||||
station = cpStation
|
||||
vehicle = HasOne(car)
|
||||
options = ChargepriceOptions(
|
||||
batteryRange = batteryRange,
|
||||
providerCustomerTariffs = prefs.chargepriceShowProviderCustomerTariffs,
|
||||
maxMonthlyFees = if (prefs.chargepriceNoBaseFee) 0.0 else null,
|
||||
currency = prefs.chargepriceCurrency
|
||||
)
|
||||
}, ChargepriceApi.getChargepriceLanguage())
|
||||
|
||||
val myTariffs = prefs.chargepriceMyTariffs
|
||||
|
||||
// choose the highest power chargepoint compatible with the car
|
||||
val chargepoint = cpStation.chargePoints.filterIndexed { i, cp ->
|
||||
charger.chargepointsMerged[i].type in car.compatibleEvmapConnectors
|
||||
}.maxByOrNull { it.power }
|
||||
if (chargepoint == null) {
|
||||
errorMessage = carContext.getString(R.string.chargeprice_no_compatible_connectors)
|
||||
prices = result.map { cp ->
|
||||
val filteredPrices =
|
||||
cp.chargepointPrices.filter {
|
||||
it.plug == chargepoint.plug && it.power == chargepoint.power
|
||||
}
|
||||
if (filteredPrices.isEmpty()) {
|
||||
null
|
||||
} else {
|
||||
cp.clone().apply {
|
||||
chargepointPrices = filteredPrices
|
||||
}
|
||||
}
|
||||
}.filterNotNull()
|
||||
.sortedBy { it.chargepointPrices.first().price }
|
||||
.sortedByDescending {
|
||||
prefs.chargepriceMyTariffsAll ||
|
||||
myTariffs != null && it.tariff?.get()?.id in myTariffs
|
||||
}
|
||||
invalidate()
|
||||
return@launch
|
||||
} catch (e: IOException) {
|
||||
withContext(Dispatchers.Main) {
|
||||
CarToast.makeText(
|
||||
carContext,
|
||||
R.string.chargeprice_connection_error,
|
||||
CarToast.LENGTH_LONG
|
||||
)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
meta =
|
||||
(result.meta.get<ChargepriceMeta>(ChargepriceApi.moshi.adapter(ChargepriceMeta::class.java)) as ChargepriceMeta).chargePoints.filterIndexed { i, cp ->
|
||||
charger.chargepointsMerged[i].type in car.compatibleEvmapConnectors
|
||||
}.maxByOrNull {
|
||||
it.power
|
||||
}
|
||||
|
||||
prices = result.map { cp ->
|
||||
val filteredPrices =
|
||||
cp.chargepointPrices.filter {
|
||||
it.plug == chargepoint.plug && it.power == chargepoint.power
|
||||
}
|
||||
if (filteredPrices.isEmpty()) {
|
||||
null
|
||||
} else {
|
||||
cp.clone().apply {
|
||||
chargepointPrices = filteredPrices
|
||||
}
|
||||
}
|
||||
}.filterNotNull()
|
||||
.sortedBy { it.chargepointPrices.first().price }
|
||||
.sortedByDescending {
|
||||
prefs.chargepriceMyTariffsAll ||
|
||||
myTariffs != null && it.tariff?.get()?.id in myTariffs
|
||||
}
|
||||
invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -84,10 +84,10 @@ fun buildDetails(
|
||||
loc.openinghours.getStatusText(ctx)
|
||||
else
|
||||
loc.openinghours.description ?: "",
|
||||
if (loc.openinghours.days != null) loc.openinghours.description else null,
|
||||
if (loc.openinghours.days != null || loc.openinghours.twentyfourSeven) loc.openinghours.description else null,
|
||||
hoursDays = loc.openinghours.days
|
||||
) else null,
|
||||
if (loc.cost != null) DetailsAdapter.Detail(
|
||||
if (loc.cost != null && !loc.cost.isEmpty) DetailsAdapter.Detail(
|
||||
R.drawable.ic_cost,
|
||||
R.string.cost,
|
||||
loc.cost.getStatusText(ctx),
|
||||
|
||||
@@ -65,10 +65,20 @@ abstract class BaseAvailabilityDetector(private val client: OkHttpClient) : Avai
|
||||
connectors: Map<Long, Pair<Double, String>>,
|
||||
chargepoints: List<Chargepoint>
|
||||
): Map<Chargepoint, Set<Long>> {
|
||||
var chargepoints = chargepoints
|
||||
|
||||
// iterate over each connector type
|
||||
val types = connectors.map { it.value.second }.distinct().toSet()
|
||||
val equivalentTypes = types.map { equivalentPlugTypes(it).plus(it) }.cartesianProduct()
|
||||
val geTypes = chargepoints.map { it.type }.distinct().toSet()
|
||||
var geTypes = chargepoints.map { it.type }.distinct().toSet()
|
||||
if (!equivalentTypes.any { it == geTypes } && geTypes.size > 1 && geTypes.contains(
|
||||
Chargepoint.SCHUKO
|
||||
)) {
|
||||
// If charger has household plugs and other plugs, try removing the household plugs
|
||||
// (common e.g. in Hamburg -> 2x Type 2 + 2x Schuko, but NM only lists Type 2)
|
||||
geTypes = geTypes.filter { it != Chargepoint.SCHUKO }.toSet()
|
||||
chargepoints = chargepoints.filter { it.type != Chargepoint.SCHUKO }
|
||||
}
|
||||
if (!equivalentTypes.any { it == geTypes }) throw AvailabilityDetectorException("chargepoints do not match")
|
||||
return types.flatMap { type ->
|
||||
// find connectors of this type
|
||||
@@ -92,7 +102,7 @@ abstract class BaseAvailabilityDetector(private val client: OkHttpClient) : Avai
|
||||
chargepoint to ids
|
||||
}
|
||||
} else if (powers.size == 1 && gePowers.size == 2
|
||||
&& chargepoints.sumBy { it.count } == connsOfType.size
|
||||
&& chargepoints.sumOf { it.count } == connsOfType.size
|
||||
) {
|
||||
// special case: dual charger(s) with load balancing
|
||||
// GoingElectric shows 2 different powers, NewMotion just one
|
||||
|
||||
@@ -205,6 +205,9 @@ class ChargePrice : Resource(), Equatable, Cloneable {
|
||||
@field:Json(name = "charge_point_prices")
|
||||
lateinit var chargepointPrices: List<ChargepointPrice>
|
||||
|
||||
@field:Json(name = "branding")
|
||||
var branding: ChargepriceBranding? = null
|
||||
|
||||
var tariff: HasOne<ChargepriceTariff>? = null
|
||||
|
||||
|
||||
@@ -238,6 +241,7 @@ class ChargePrice : Resource(), Equatable, Cloneable {
|
||||
if (startTime != other.startTime) return false
|
||||
if (tags != other.tags) return false
|
||||
if (chargepointPrices != other.chargepointPrices) return false
|
||||
if (branding != other.branding) return false
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -256,6 +260,7 @@ class ChargePrice : Resource(), Equatable, Cloneable {
|
||||
result = 31 * result + startTime
|
||||
result = 31 * result + tags.hashCode()
|
||||
result = 31 * result + chargepointPrices.hashCode()
|
||||
result = 31 * result + branding.hashCode()
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -274,6 +279,7 @@ class ChargePrice : Resource(), Equatable, Cloneable {
|
||||
totalMonthlyFee = this@ChargePrice.totalMonthlyFee
|
||||
url = this@ChargePrice.url
|
||||
tariff = this@ChargePrice.tariff
|
||||
branding = this@ChargePrice.branding
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -328,6 +334,12 @@ data class ChargepointPrice(
|
||||
}
|
||||
}
|
||||
|
||||
data class ChargepriceBranding(
|
||||
@Json(name = "background_color") val backgroundColor: String,
|
||||
@Json(name = "text_color") val textColor: String,
|
||||
@Json(name = "logo_url") val logoUrl: String
|
||||
)
|
||||
|
||||
data class PriceDistribution(val kwh: Double?, val session: Double?, val minute: Double?) {
|
||||
val isOnlyKwh =
|
||||
kwh != null && kwh > 0 && (session == null || session == 0.0) && (minute == null || minute == 0.0)
|
||||
@@ -339,6 +351,19 @@ data class ChargepriceMeta(
|
||||
@Json(name = "charge_points") val chargePoints: List<ChargepriceChargepointMeta>
|
||||
)
|
||||
|
||||
enum class ChargepriceInclude {
|
||||
@Json(name = "filter")
|
||||
FILTER,
|
||||
@Json(name = "always")
|
||||
ALWAYS,
|
||||
@Json(name = "exclusive")
|
||||
EXCLUSIVE
|
||||
}
|
||||
|
||||
data class ChargepriceRequestTariffMeta(
|
||||
val include: ChargepriceInclude
|
||||
)
|
||||
|
||||
data class ChargepriceChargepointMeta(
|
||||
val power: Double,
|
||||
val plug: String,
|
||||
|
||||
@@ -126,16 +126,21 @@ internal class HoursAdapter {
|
||||
private val regex = Regex("from (.*) till (.*)")
|
||||
|
||||
@FromJson
|
||||
fun fromJson(str: String): GEHours? {
|
||||
fun fromJson(str: String): GEHours {
|
||||
if (str == "closed") {
|
||||
return GEHours(null, null)
|
||||
} else if (str == "around the clock") {
|
||||
return GEHours(LocalTime.MIN, LocalTime.MAX)
|
||||
} else {
|
||||
val match = regex.find(str)
|
||||
if (match != null) {
|
||||
return GEHours(
|
||||
LocalTime.parse(match.groupValues[1]),
|
||||
val start = LocalTime.parse(match.groupValues[1])
|
||||
val end = if (match.groupValues[2] == "24:00") {
|
||||
LocalTime.MAX
|
||||
} else {
|
||||
LocalTime.parse(match.groupValues[2])
|
||||
)
|
||||
}
|
||||
return GEHours(start, end)
|
||||
} else {
|
||||
// I cannot reproduce this case, but it seems to occur once in a while
|
||||
Log.e("GoingElectricApi", "invalid hours value: " + str)
|
||||
|
||||
@@ -87,7 +87,14 @@ data class GECost(
|
||||
@JsonObjectOrFalse @Json(name = "description_short") val descriptionShort: String?,
|
||||
@JsonObjectOrFalse @Json(name = "description_long") val descriptionLong: String?
|
||||
) {
|
||||
fun convert() = Cost(freecharging, freeparking, descriptionShort, descriptionLong)
|
||||
fun convert() = Cost(
|
||||
// In GE, freecharging = false can either mean "paid charging" or "no information
|
||||
// available", only freecharging = true provides useful information. Therefore convert
|
||||
// false to null. Same for freeparking.
|
||||
if (freecharging) freecharging else null,
|
||||
if (freeparking) freeparking else null,
|
||||
descriptionShort, descriptionLong
|
||||
)
|
||||
}
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
@@ -126,7 +133,7 @@ data class GEHours(
|
||||
val start: LocalTime?,
|
||||
val end: LocalTime?
|
||||
) {
|
||||
fun convert() = Hours(start, end)
|
||||
fun convert() = if (start != null && end != null) Hours(start, end) else null
|
||||
}
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package net.vonforst.evmap.autocomplete
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
import com.car2go.maps.model.LatLng
|
||||
import com.car2go.maps.model.LatLngBounds
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
interface AutocompleteProvider {
|
||||
val id: String
|
||||
@@ -183,4 +185,5 @@ enum class AutocompletePlaceType {
|
||||
}
|
||||
}
|
||||
|
||||
data class PlaceWithBounds(val latLng: LatLng, val viewport: LatLngBounds?)
|
||||
@Parcelize
|
||||
data class PlaceWithBounds(val latLng: LatLng, val viewport: LatLngBounds?) : Parcelable
|
||||
@@ -175,6 +175,10 @@ class ChargepriceFragment : DialogFragment() {
|
||||
dismiss()
|
||||
true
|
||||
}
|
||||
R.id.menu_help -> {
|
||||
(activity as? MapsActivity)?.openUrl(getString(R.string.chargeprice_faq_link))
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -910,6 +910,11 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
vm.mapPosition.value = MapPosition(
|
||||
map.projection.visibleRegion.latLngBounds, map.cameraPosition.zoom
|
||||
)
|
||||
|
||||
if (vm.searchResult.value != null) {
|
||||
// show search result (after configuration change)
|
||||
vm.searchResult.postValue(vm.searchResult.value)
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresPermission(anyOf = [ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION])
|
||||
|
||||
@@ -126,6 +126,9 @@ data class Cost(
|
||||
val descriptionShort: String? = null,
|
||||
val descriptionLong: String? = null
|
||||
) : Parcelable {
|
||||
val isEmpty: Boolean
|
||||
get() = descriptionLong == null && descriptionShort == null && freecharging == null && freeparking == null
|
||||
|
||||
fun getStatusText(ctx: Context, emoji: Boolean = false): CharSequence {
|
||||
if (freecharging != null && freeparking != null) {
|
||||
val charging =
|
||||
@@ -137,6 +140,22 @@ data class Cost(
|
||||
} else {
|
||||
HtmlCompat.fromHtml(ctx.getString(R.string.cost_detail, charging, parking), 0)
|
||||
}
|
||||
} else if (freecharging != null) {
|
||||
val charging =
|
||||
if (freecharging) ctx.getString(R.string.free) else ctx.getString(R.string.paid)
|
||||
return if (emoji) {
|
||||
"⚡ $charging"
|
||||
} else {
|
||||
HtmlCompat.fromHtml(ctx.getString(R.string.cost_detail_charging, charging), 0)
|
||||
}
|
||||
} else if (freeparking != null) {
|
||||
val parking =
|
||||
if (freeparking) ctx.getString(R.string.free) else ctx.getString(R.string.paid)
|
||||
return if (emoji) {
|
||||
"⚡ $parking"
|
||||
} else {
|
||||
HtmlCompat.fromHtml(ctx.getString(R.string.cost_detail_parking, parking), 0)
|
||||
}
|
||||
} else if (descriptionShort != null) {
|
||||
return descriptionShort
|
||||
} else if (descriptionLong != null) {
|
||||
@@ -162,9 +181,7 @@ data class OpeningHours(
|
||||
return HtmlCompat.fromHtml(ctx.getString(R.string.open_247), 0)
|
||||
} else if (days != null) {
|
||||
val hours = days.getHoursForDate(LocalDate.now())
|
||||
if (hours.start == null || hours.end == null) {
|
||||
return HtmlCompat.fromHtml(ctx.getString(R.string.closed), 0)
|
||||
}
|
||||
?: return HtmlCompat.fromHtml(ctx.getString(R.string.closed), 0)
|
||||
|
||||
val now = LocalTime.now()
|
||||
if (hours.start.isBefore(now) && hours.end.isAfter(now)) {
|
||||
@@ -192,21 +209,21 @@ data class OpeningHours(
|
||||
|
||||
@Parcelize
|
||||
data class OpeningHoursDays(
|
||||
@Embedded(prefix = "mo") val monday: Hours,
|
||||
@Embedded(prefix = "tu") val tuesday: Hours,
|
||||
@Embedded(prefix = "we") val wednesday: Hours,
|
||||
@Embedded(prefix = "th") val thursday: Hours,
|
||||
@Embedded(prefix = "fr") val friday: Hours,
|
||||
@Embedded(prefix = "sa") val saturday: Hours,
|
||||
@Embedded(prefix = "su") val sunday: Hours,
|
||||
@Embedded(prefix = "ho") val holiday: Hours
|
||||
@Embedded(prefix = "mo") val monday: Hours?,
|
||||
@Embedded(prefix = "tu") val tuesday: Hours?,
|
||||
@Embedded(prefix = "we") val wednesday: Hours?,
|
||||
@Embedded(prefix = "th") val thursday: Hours?,
|
||||
@Embedded(prefix = "fr") val friday: Hours?,
|
||||
@Embedded(prefix = "sa") val saturday: Hours?,
|
||||
@Embedded(prefix = "su") val sunday: Hours?,
|
||||
@Embedded(prefix = "ho") val holiday: Hours?
|
||||
) : Parcelable {
|
||||
fun getHoursForDate(date: LocalDate): Hours {
|
||||
fun getHoursForDate(date: LocalDate): Hours? {
|
||||
// TODO: check for holidays
|
||||
return getHoursForDayOfWeek(date.dayOfWeek)
|
||||
}
|
||||
|
||||
fun getHoursForDayOfWeek(dayOfWeek: DayOfWeek?): Hours {
|
||||
fun getHoursForDayOfWeek(dayOfWeek: DayOfWeek?): Hours? {
|
||||
@Suppress("WHEN_ENUM_CAN_BE_NULL_IN_JAVA")
|
||||
return when (dayOfWeek) {
|
||||
DayOfWeek.MONDAY -> monday
|
||||
@@ -223,16 +240,12 @@ data class OpeningHoursDays(
|
||||
|
||||
@Parcelize
|
||||
data class Hours(
|
||||
val start: LocalTime?,
|
||||
val end: LocalTime?
|
||||
val start: LocalTime,
|
||||
val end: LocalTime
|
||||
) : Parcelable {
|
||||
override fun toString(): String {
|
||||
if (start != null && end != null) {
|
||||
val fmt = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT)
|
||||
return "${start.format(fmt)} - ${end.format(fmt)}"
|
||||
} else {
|
||||
return "closed"
|
||||
}
|
||||
val fmt = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT)
|
||||
return "${start.format(fmt)} - ${end.format(fmt)}"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,10 @@ package net.vonforst.evmap.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.graphics.drawable.LayerDrawable
|
||||
import android.text.SpannableString
|
||||
import android.view.View
|
||||
import android.view.ViewGroup.MarginLayoutParams
|
||||
@@ -18,14 +22,12 @@ import androidx.databinding.InverseBindingListener
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.viewpager2.widget.ViewPager2
|
||||
import coil.load
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
import com.google.android.material.slider.RangeSlider
|
||||
import net.vonforst.evmap.R
|
||||
import net.vonforst.evmap.*
|
||||
import net.vonforst.evmap.api.availability.ChargepointStatus
|
||||
import net.vonforst.evmap.api.iconForPlugType
|
||||
import net.vonforst.evmap.kmPerMile
|
||||
import net.vonforst.evmap.meterPerFt
|
||||
import net.vonforst.evmap.shouldUseImperialUnits
|
||||
import kotlin.math.ceil
|
||||
import kotlin.math.floor
|
||||
import kotlin.math.roundToInt
|
||||
@@ -344,18 +346,68 @@ fun setImageTintList(view: ImageView, @ColorInt color: Int) {
|
||||
view.imageTintList = ColorStateList.valueOf(color)
|
||||
}
|
||||
|
||||
@BindingAdapter("myTariffsBackground")
|
||||
fun myTariffsBackground(view: View, myTariff: Boolean) {
|
||||
if (myTariff) {
|
||||
view.background = ContextCompat.getDrawable(view.context, R.drawable.my_tariff_background)
|
||||
} else {
|
||||
view.context.obtainStyledAttributes(intArrayOf(R.attr.selectableItemBackground)).use {
|
||||
view.background = it.getDrawable(0)
|
||||
fun tariffBackground(context: Context, myTariff: Boolean, brandingColor: String?): Drawable? {
|
||||
when {
|
||||
myTariff -> {
|
||||
return ContextCompat.getDrawable(context, R.drawable.my_tariff_background)
|
||||
}
|
||||
brandingColor != null -> {
|
||||
val drawable = ContextCompat.getDrawable(context, R.drawable.branded_tariff_background)
|
||||
val color = colorToTransparent(Color.parseColor(brandingColor))
|
||||
(drawable as LayerDrawable).setDrawableByLayerId(
|
||||
R.id.background, ColorDrawable(
|
||||
color
|
||||
)
|
||||
)
|
||||
return drawable
|
||||
}
|
||||
else -> {
|
||||
context.obtainStyledAttributes(intArrayOf(R.attr.selectableItemBackground)).use {
|
||||
return it.getDrawable(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun isDarkMode(context: Context) = context.isDarkMode()
|
||||
|
||||
/**
|
||||
* Converts an opaque color to a transparent color, assuming it was on a white background
|
||||
* with a certain opacity targetAlpha.
|
||||
*/
|
||||
private fun colorToTransparent(color: Int, targetAlpha: Float = 31f / 255): Int {
|
||||
if (Color.alpha(color) != 255) return color
|
||||
|
||||
val red = Color.red(color)
|
||||
val green = Color.green(color)
|
||||
val blue = Color.blue(color)
|
||||
|
||||
val newRed = ((red - (1 - targetAlpha) * 255) / targetAlpha).roundToInt()
|
||||
val newGreen = ((green - (1 - targetAlpha) * 255) / targetAlpha).roundToInt()
|
||||
val newBlue = ((blue - (1 - targetAlpha) * 255) / targetAlpha).roundToInt()
|
||||
|
||||
return Color.argb((targetAlpha * 255).roundToInt(), newRed, newGreen, newBlue)
|
||||
}
|
||||
|
||||
@BindingAdapter("imageUrl")
|
||||
fun loadImage(view: ImageView, url: String?) {
|
||||
if (url != null) {
|
||||
view.load(url)
|
||||
} else {
|
||||
view.setImageDrawable(null)
|
||||
}
|
||||
}
|
||||
|
||||
@BindingAdapter("tooltipTextCompat")
|
||||
fun setTooltipTextCompat(view: View, text: String) {
|
||||
TooltipCompat.setTooltipText(view, text)
|
||||
}
|
||||
|
||||
@BindingAdapter("tintNullable")
|
||||
fun setImageTint(view: ImageView, @ColorInt tint: Int?) {
|
||||
if (tint != null) {
|
||||
view.imageTintList = ColorStateList.valueOf(tint)
|
||||
} else {
|
||||
view.imageTintList = null
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,10 @@ import android.app.Application
|
||||
import androidx.lifecycle.*
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import moe.banana.jsonapi2.HasMany
|
||||
import moe.banana.jsonapi2.HasOne
|
||||
import moe.banana.jsonapi2.JsonBuffer
|
||||
import moe.banana.jsonapi2.ResourceIdentifier
|
||||
import net.vonforst.evmap.api.chargeprice.*
|
||||
import net.vonforst.evmap.api.equivalentPlugTypes
|
||||
import net.vonforst.evmap.model.ChargeLocation
|
||||
@@ -100,7 +103,8 @@ class ChargepriceViewModel(application: Application, chargepriceApiKey: String)
|
||||
dataSource,
|
||||
batteryRange,
|
||||
batteryRangeSliderDragging,
|
||||
vehicleCompatibleConnectors
|
||||
vehicleCompatibleConnectors,
|
||||
myTariffs, myTariffsAll
|
||||
).forEach {
|
||||
addSource(it) {
|
||||
if (!batteryRangeSliderDragging.value!!) loadPrices()
|
||||
@@ -208,7 +212,9 @@ class ChargepriceViewModel(application: Application, chargepriceApiKey: String)
|
||||
val car = vehicle.value
|
||||
val compatibleConnectors = vehicleCompatibleConnectors.value
|
||||
val dataSource = dataSource.value
|
||||
if (charger == null || car == null || compatibleConnectors == null || dataSource == null) {
|
||||
val myTariffs = myTariffs.value
|
||||
val myTariffsAll = myTariffsAll.value
|
||||
if (charger == null || car == null || compatibleConnectors == null || dataSource == null || myTariffsAll == null || myTariffsAll == false && myTariffs == null) {
|
||||
chargePrices.value = Resource.error(null, null)
|
||||
return
|
||||
}
|
||||
@@ -222,6 +228,19 @@ class ChargepriceViewModel(application: Application, chargepriceApiKey: String)
|
||||
dataAdapter = dataSource
|
||||
station = cpStation
|
||||
vehicle = HasOne(car)
|
||||
tariffs = if (!myTariffsAll) {
|
||||
HasMany<ChargepriceTariff>(*myTariffs!!.map {
|
||||
ResourceIdentifier(
|
||||
"tariff",
|
||||
it
|
||||
)
|
||||
}.toTypedArray()).apply {
|
||||
meta = JsonBuffer.create(
|
||||
ChargepriceApi.moshi.adapter(ChargepriceRequestTariffMeta::class.java),
|
||||
ChargepriceRequestTariffMeta(ChargepriceInclude.ALWAYS)
|
||||
)
|
||||
}
|
||||
} else null
|
||||
options = ChargepriceOptions(
|
||||
batteryRange = batteryRange.value!!.map { it.toDouble() },
|
||||
providerCustomerTariffs = prefs.chargepriceShowProviderCustomerTariffs,
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
package net.vonforst.evmap.viewmodel
|
||||
|
||||
import android.app.Application
|
||||
import android.os.Parcelable
|
||||
import androidx.lifecycle.*
|
||||
import com.car2go.maps.AnyMap
|
||||
import com.car2go.maps.model.LatLng
|
||||
import com.car2go.maps.model.LatLngBounds
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import net.vonforst.evmap.api.ChargepointApi
|
||||
import net.vonforst.evmap.api.availability.ChargeLocationStatus
|
||||
import net.vonforst.evmap.api.availability.getAvailability
|
||||
@@ -27,7 +29,8 @@ import net.vonforst.evmap.ui.cluster
|
||||
import net.vonforst.evmap.utils.distanceBetween
|
||||
import java.io.IOException
|
||||
|
||||
data class MapPosition(val bounds: LatLngBounds, val zoom: Float)
|
||||
@Parcelize
|
||||
data class MapPosition(val bounds: LatLngBounds, val zoom: Float) : Parcelable
|
||||
|
||||
internal fun getClusterDistance(zoom: Float): Int? {
|
||||
return when (zoom) {
|
||||
@@ -39,7 +42,8 @@ internal fun getClusterDistance(zoom: Float): Int? {
|
||||
}
|
||||
}
|
||||
|
||||
class MapViewModel(application: Application) : AndroidViewModel(application) {
|
||||
class MapViewModel(application: Application, private val state: SavedStateHandle) :
|
||||
AndroidViewModel(application) {
|
||||
val apiType: Class<ChargepointApi<ReferenceData>>
|
||||
get() = api.javaClass
|
||||
val apiName: String
|
||||
@@ -50,11 +54,11 @@ class MapViewModel(application: Application) : AndroidViewModel(application) {
|
||||
private var api: ChargepointApi<ReferenceData> = createApi(prefs.dataSource, application)
|
||||
|
||||
val bottomSheetState: MutableLiveData<Int> by lazy {
|
||||
MutableLiveData<Int>()
|
||||
state.getLiveData("bottomSheetState")
|
||||
}
|
||||
|
||||
val mapPosition: MutableLiveData<MapPosition> by lazy {
|
||||
MutableLiveData<MapPosition>()
|
||||
state.getLiveData("mapPosition")
|
||||
}
|
||||
val filterStatus: MutableLiveData<Long> by lazy {
|
||||
MutableLiveData<Long>().apply {
|
||||
@@ -125,7 +129,7 @@ class MapViewModel(application: Application) : AndroidViewModel(application) {
|
||||
}
|
||||
|
||||
val chargerSparse: MutableLiveData<ChargeLocation> by lazy {
|
||||
MutableLiveData<ChargeLocation>()
|
||||
state.getLiveData("chargerSparse")
|
||||
}
|
||||
val chargerDetails: MediatorLiveData<Resource<ChargeLocation>> by lazy {
|
||||
MediatorLiveData<Resource<ChargeLocation>>().apply {
|
||||
@@ -223,7 +227,7 @@ class MapViewModel(application: Application) : AndroidViewModel(application) {
|
||||
}
|
||||
|
||||
val searchResult: MutableLiveData<PlaceWithBounds> by lazy {
|
||||
MutableLiveData<PlaceWithBounds>()
|
||||
state.getLiveData("searchResult")
|
||||
}
|
||||
|
||||
val mapType: MutableLiveData<AnyMap.Type> by lazy {
|
||||
|
||||
7
app/src/main/res/drawable/branded_tariff_background.xml
Normal file
7
app/src/main/res/drawable/branded_tariff_background.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:id="@+id/background"
|
||||
android:drawable="@color/chip_background" />
|
||||
<item android:drawable="?selectableItemBackground" />
|
||||
</layer-list>
|
||||
11
app/src/main/res/drawable/ic_help.xml
Normal file
11
app/src/main/res/drawable/ic_help.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<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"
|
||||
android:autoMirrored="true">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M11,18h2v-2h-2v2zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM12,6c-2.21,0 -4,1.79 -4,4h2c0,-1.1 0.9,-2 2,-2s2,0.9 2,2c0,2 -3,1.75 -3,5h2c0,-2.25 3,-2.5 3,-5 0,-2.21 -1.79,-4 -4,-4z" />
|
||||
</vector>
|
||||
@@ -37,17 +37,16 @@
|
||||
android:paddingTop="8dp"
|
||||
android:paddingRight="16dp"
|
||||
android:paddingBottom="8dp"
|
||||
app:myTariffsBackground="@{!myTariffsAll && myTariffs.contains(item.tariff.get().id)}">
|
||||
android:background="@{BindingAdaptersKt.tariffBackground(context,!myTariffsAll && myTariffs.contains(item.tariff.get().id), item.branding.backgroundColor)}">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtTariff"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:text="@{item.tariffName}"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Body2"
|
||||
app:layout_constraintBottom_toTopOf="@+id/txtProvider"
|
||||
app:layout_constraintEnd_toStartOf="@+id/guideline5"
|
||||
app:layout_constraintEnd_toStartOf="@+id/ivLogo"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_chainStyle="packed"
|
||||
@@ -57,12 +56,11 @@
|
||||
android:id="@+id/txtProvider"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:text="@{item.provider}"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Caption"
|
||||
app:goneUnless="@{!item.tariffName.toLowerCase().startsWith(item.provider.toLowerCase())}"
|
||||
app:layout_constraintBottom_toTopOf="@+id/rvTags"
|
||||
app:layout_constraintEnd_toStartOf="@+id/guideline5"
|
||||
app:layout_constraintEnd_toStartOf="@+id/ivLogo"
|
||||
app:layout_constraintStart_toStartOf="@+id/txtTariff"
|
||||
app:layout_constraintTop_toBottomOf="@+id/txtTariff"
|
||||
tools:text="Cheap Charging Co." />
|
||||
@@ -71,11 +69,10 @@
|
||||
android:id="@+id/rvTags"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:nestedScrollingEnabled="false"
|
||||
app:data="@{item.tags}"
|
||||
app:layout_constraintBottom_toTopOf="@+id/txtProviderCustomerTariff"
|
||||
app:layout_constraintEnd_toStartOf="@+id/guideline5"
|
||||
app:layout_constraintEnd_toStartOf="@+id/ivLogo"
|
||||
app:layout_constraintStart_toStartOf="@+id/txtTariff"
|
||||
app:layout_constraintTop_toBottomOf="@+id/txtProvider"
|
||||
tools:itemCount="1"
|
||||
@@ -85,12 +82,12 @@
|
||||
android:id="@+id/txtProviderCustomerTariff"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:text="@string/chargeprice_provider_customer_tariff"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Caption"
|
||||
app:goneUnless="@{item.providerCustomerTariff}"
|
||||
app:layout_constraintBottom_toTopOf="@id/txtMonthlyFee"
|
||||
app:layout_constraintEnd_toStartOf="@+id/guideline5"
|
||||
app:layout_constraintEnd_toStartOf="@+id/ivLogo"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toStartOf="@+id/txtTariff"
|
||||
app:layout_constraintTop_toBottomOf="@+id/rvTags" />
|
||||
|
||||
@@ -98,12 +95,11 @@
|
||||
android:id="@+id/txtMonthlyFee"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:text="@{item.formatMonthlyFees(context)}"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Caption"
|
||||
app:goneUnless="@{item.totalMonthlyFee > 0 || item.monthlyMinSales > 0}"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/guideline5"
|
||||
app:layout_constraintEnd_toStartOf="@+id/ivLogo"
|
||||
app:layout_constraintStart_toStartOf="@+id/txtTariff"
|
||||
app:layout_constraintTop_toBottomOf="@+id/txtProviderCustomerTariff"
|
||||
tools:text="Base fee 1 €/month" />
|
||||
@@ -160,5 +156,19 @@
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintGuide_percent="0.65" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ivLogo"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_margin="8dp"
|
||||
android:scaleType="fitCenter"
|
||||
app:goneUnless="@{item.branding.logoUrl != null}"
|
||||
app:imageUrl="@{item.branding.logoUrl}"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/guideline5"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:tintNullable="@{BindingAdaptersKt.isDarkMode(context) ? @android:color/white : null}"
|
||||
tools:srcCompat="@tools:sample/avatars" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</layout>
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="100dp"
|
||||
android:text="@{hours.getHoursForDayOfWeek(dayOfWeek).toString().equals("closed") ? @string/closed_unfmt : hours.getHoursForDayOfWeek(dayOfWeek).toString()}"
|
||||
android:text="@{hours.getHoursForDayOfWeek(dayOfWeek) == null ? @string/closed_unfmt : hours.getHoursForDayOfWeek(dayOfWeek).toString()}"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Caption"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
|
||||
@@ -2,6 +2,12 @@
|
||||
<menu xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_help"
|
||||
android:icon="@drawable/ic_help"
|
||||
android:title="@string/help"
|
||||
app:showAsAction="always" />
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_close"
|
||||
android:icon="@drawable/ic_close"
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
<string name="closed_opensat"><![CDATA[<b>Geschlossen</b> · Öffnet um %s]]></string>
|
||||
<string name="cost">Kosten</string>
|
||||
<string name="cost_detail"><![CDATA[<b>Laden:</b> %s · <b>Parken:</b> %s]]></string>
|
||||
<string name="cost_detail_charging"><![CDATA[<b>%s laden</b>]]></string>
|
||||
<string name="cost_detail_parking"><![CDATA[<b>%s parken</b>]]></string>
|
||||
<string name="free">Kostenlos</string>
|
||||
<string name="paid">Kostenpflichtig</string>
|
||||
<string name="amenities">Ladeweile</string>
|
||||
@@ -192,7 +194,7 @@
|
||||
<string name="chargeprice_vehicle">Fahrzeug</string>
|
||||
<string name="edit_on_goingelectric_info">Falls hier nur eine leere Seite erscheint, logge dich bitte zuerst bei GoingElectric.de ein.</string>
|
||||
<string name="close">schließen</string>
|
||||
<string name="chargeprice_title">Preisvergleich</string>
|
||||
<string name="chargeprice_title">Preise</string>
|
||||
<string name="chargeprice_connection_error">Preise konnten nicht geladen werden</string>
|
||||
<string name="chargeprice_no_compatible_connectors">Keiner der Anschlüsse dieser Ladestation ist mit deinem Fahrzeug kompatibel.</string>
|
||||
<string name="pref_chargeprice_currency">Währung</string>
|
||||
@@ -241,9 +243,11 @@
|
||||
<string name="unnamed_filter_profile">Unbenanntes Filterprofil</string>
|
||||
<string name="privacy_link">https://evmap.vonforst.net/de/privacy.html</string>
|
||||
<string name="faq_link">https://evmap.vonforst.net/de/faq.html</string>
|
||||
<string name="chargeprice_faq_link">https://evmap.vonforst.net/de/chargeprice_faq.html</string>
|
||||
<string name="required">erforderlich</string>
|
||||
<string name="edit_filter_profile">„%s“ bearbeiten</string>
|
||||
<string name="pref_search_delete_recent">Suchverlauf löschen</string>
|
||||
<string name="deleted_recent_search_results">Suchverlauf wurde gelöscht</string>
|
||||
<string name="settings_data_sources">Datenquellen</string>
|
||||
<string name="help">Hilfe</string>
|
||||
</resources>
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
<string name="holiday">Holiday</string>
|
||||
<string name="cost">Cost</string>
|
||||
<string name="cost_detail"><![CDATA[<b>Charging:</b> %s · <b>Parking:</b> %s]]></string>
|
||||
<string name="cost_detail_charging"><![CDATA[<b>%s charging</b>]]></string>
|
||||
<string name="cost_detail_parking"><![CDATA[<b>%s parking</b>]]></string>
|
||||
<string name="free">Free</string>
|
||||
<string name="paid">Paid</string>
|
||||
<string name="amenities">Amenities</string>
|
||||
@@ -226,9 +228,11 @@
|
||||
<string name="unnamed_filter_profile">Unnamed filter profile</string>
|
||||
<string name="privacy_link">https://evmap.vonforst.net/en/privacy.html</string>
|
||||
<string name="faq_link">https://evmap.vonforst.net/en/faq.html</string>
|
||||
<string name="chargeprice_faq_link">https://evmap.vonforst.net/en/chargeprice_faq.html</string>
|
||||
<string name="required">required</string>
|
||||
<string name="edit_filter_profile">Edit “%s”</string>
|
||||
<string name="pref_search_delete_recent">Delete recent search results</string>
|
||||
<string name="deleted_recent_search_results">Recent search results have been deleted</string>
|
||||
<string name="settings_data_sources">Data sources</string>
|
||||
<string name="help">Help</string>
|
||||
</resources>
|
||||
|
||||
@@ -8,13 +8,16 @@ class AvailabilityDetectorTest {
|
||||
@Test
|
||||
fun testMatchChargepointsSingleCorrect() {
|
||||
// single charger with 2 22kW chargepoints
|
||||
val chargepoints = listOf(Chargepoint("Typ2", 22.0, 2))
|
||||
val chargepoints = listOf(Chargepoint(Chargepoint.TYPE_2_UNKNOWN, 22.0, 2))
|
||||
|
||||
// correct data in NewMotion
|
||||
assertEquals(
|
||||
mapOf(chargepoints[0] to setOf(0L, 1L)),
|
||||
BaseAvailabilityDetector.matchChargepoints(
|
||||
mapOf(0L to (22.0 to "Typ2"), 1L to (22.0 to "Typ2")),
|
||||
mapOf(
|
||||
0L to (22.0 to Chargepoint.TYPE_2_UNKNOWN),
|
||||
1L to (22.0 to Chargepoint.TYPE_2_UNKNOWN)
|
||||
),
|
||||
chargepoints
|
||||
)
|
||||
)
|
||||
@@ -23,13 +26,16 @@ class AvailabilityDetectorTest {
|
||||
@Test
|
||||
fun testMatchChargepointsSingleWrongPower() {
|
||||
// single charger with 2 22kW chargepoints
|
||||
val chargepoints = listOf(Chargepoint("Typ2", 22.0, 2))
|
||||
val chargepoints = listOf(Chargepoint(Chargepoint.TYPE_2_UNKNOWN, 22.0, 2))
|
||||
|
||||
// wrong power in NewMotion
|
||||
assertEquals(
|
||||
mapOf(chargepoints[0] to setOf(0L, 1L)),
|
||||
BaseAvailabilityDetector.matchChargepoints(
|
||||
mapOf(0L to (27.0 to "Typ2"), 1L to (27.0 to "Typ2")),
|
||||
mapOf(
|
||||
0L to (27.0 to Chargepoint.TYPE_2_UNKNOWN),
|
||||
1L to (27.0 to Chargepoint.TYPE_2_UNKNOWN)
|
||||
),
|
||||
chargepoints
|
||||
)
|
||||
)
|
||||
@@ -38,11 +44,15 @@ class AvailabilityDetectorTest {
|
||||
@Test(expected = AvailabilityDetectorException::class)
|
||||
fun testMatchChargepointsSingleWrong() {
|
||||
// single charger with 2 22kW chargepoints
|
||||
val chargepoints = listOf(Chargepoint("Typ2", 22.0, 2))
|
||||
val chargepoints = listOf(Chargepoint(Chargepoint.TYPE_2_UNKNOWN, 22.0, 2))
|
||||
|
||||
// non-matching data in NewMotion
|
||||
BaseAvailabilityDetector.matchChargepoints(
|
||||
mapOf(0L to (27.0 to "Typ2"), 1L to (27.0 to "Typ2"), 2L to (50.0 to "CCS")),
|
||||
mapOf(
|
||||
0L to (27.0 to Chargepoint.TYPE_2_UNKNOWN),
|
||||
1L to (27.0 to Chargepoint.TYPE_2_UNKNOWN),
|
||||
2L to (50.0 to Chargepoint.CCS_UNKNOWN)
|
||||
),
|
||||
chargepoints
|
||||
)
|
||||
}
|
||||
@@ -51,11 +61,11 @@ class AvailabilityDetectorTest {
|
||||
fun testMatchChargepointsComplex() {
|
||||
// charger with many different connectors
|
||||
val chargepoints = listOf(
|
||||
Chargepoint("Typ2", 43.0, 1),
|
||||
Chargepoint("CCS", 50.0, 1),
|
||||
Chargepoint("CHAdeMO", 50.0, 2),
|
||||
Chargepoint("CCS", 160.0, 1),
|
||||
Chargepoint("CCS", 320.0, 2)
|
||||
Chargepoint(Chargepoint.TYPE_2_UNKNOWN, 43.0, 1),
|
||||
Chargepoint(Chargepoint.CCS_UNKNOWN, 50.0, 1),
|
||||
Chargepoint(Chargepoint.CHADEMO, 50.0, 2),
|
||||
Chargepoint(Chargepoint.CCS_UNKNOWN, 160.0, 1),
|
||||
Chargepoint(Chargepoint.CCS_UNKNOWN, 320.0, 2)
|
||||
)
|
||||
|
||||
// partly wrong power in NewMotion
|
||||
@@ -70,15 +80,15 @@ class AvailabilityDetectorTest {
|
||||
BaseAvailabilityDetector.matchChargepoints(
|
||||
mapOf(
|
||||
// CHAdeMO + CCS HPC
|
||||
0L to (50.0 to "CHAdeMO"),
|
||||
1L to (200.0 to "CCS"),
|
||||
0L to (50.0 to Chargepoint.CHADEMO),
|
||||
1L to (200.0 to Chargepoint.CCS_UNKNOWN),
|
||||
// dual CCS HPC
|
||||
2L to (80.0 to "CCS"),
|
||||
3L to (200.0 to "CCS"),
|
||||
2L to (80.0 to Chargepoint.CCS_UNKNOWN),
|
||||
3L to (200.0 to Chargepoint.CCS_UNKNOWN),
|
||||
// 50kW triple charger
|
||||
4L to (50.0 to "CCS"),
|
||||
5L to (50.0 to "CHAdeMO"),
|
||||
6L to (43.0 to "Typ2")
|
||||
4L to (50.0 to Chargepoint.CCS_UNKNOWN),
|
||||
5L to (50.0 to Chargepoint.CHADEMO),
|
||||
6L to (43.0 to Chargepoint.TYPE_2_UNKNOWN)
|
||||
),
|
||||
chargepoints
|
||||
)
|
||||
@@ -89,15 +99,18 @@ class AvailabilityDetectorTest {
|
||||
fun testMatchChargepointsDifferentPower() {
|
||||
// single charger with 1 22kW and 1 11kW chargepoint (common when load balancing is applied)
|
||||
val chargepoints = listOf(
|
||||
Chargepoint("Typ2", 22.0, 1),
|
||||
Chargepoint("Typ2", 11.0, 1)
|
||||
Chargepoint(Chargepoint.TYPE_2_UNKNOWN, 22.0, 1),
|
||||
Chargepoint(Chargepoint.TYPE_2_UNKNOWN, 11.0, 1)
|
||||
)
|
||||
|
||||
// both have 27 kW power in NewMotion
|
||||
assertEquals(
|
||||
mapOf(chargepoints[1] to setOf(0L), chargepoints[0] to setOf(1L)),
|
||||
BaseAvailabilityDetector.matchChargepoints(
|
||||
mapOf(0L to (27.0 to "Typ2"), 1L to (27.0 to "Typ2")),
|
||||
mapOf(
|
||||
0L to (27.0 to Chargepoint.TYPE_2_UNKNOWN),
|
||||
1L to (27.0 to Chargepoint.TYPE_2_UNKNOWN)
|
||||
),
|
||||
chargepoints
|
||||
)
|
||||
)
|
||||
@@ -107,8 +120,8 @@ class AvailabilityDetectorTest {
|
||||
fun testMatchChargepointsDifferentPower2() {
|
||||
// two chargers with 1 22kW and 1 11kW chargepoint (common when load balancing is applied)
|
||||
val chargepoints = listOf(
|
||||
Chargepoint("Typ2", 22.0, 2),
|
||||
Chargepoint("Typ2", 11.0, 2)
|
||||
Chargepoint(Chargepoint.TYPE_2_UNKNOWN, 22.0, 2),
|
||||
Chargepoint(Chargepoint.TYPE_2_UNKNOWN, 11.0, 2)
|
||||
)
|
||||
|
||||
// both have 27 kW power in NewMotion
|
||||
@@ -116,10 +129,54 @@ class AvailabilityDetectorTest {
|
||||
mapOf(chargepoints[1] to setOf(0L, 1L), chargepoints[0] to setOf(2L, 3L)),
|
||||
BaseAvailabilityDetector.matchChargepoints(
|
||||
mapOf(
|
||||
0L to (27.0 to "Typ2"),
|
||||
1L to (27.0 to "Typ2"),
|
||||
2L to (27.0 to "Typ2"),
|
||||
3L to (27.0 to "Typ2")
|
||||
0L to (27.0 to Chargepoint.TYPE_2_UNKNOWN),
|
||||
1L to (27.0 to Chargepoint.TYPE_2_UNKNOWN),
|
||||
2L to (27.0 to Chargepoint.TYPE_2_UNKNOWN),
|
||||
3L to (27.0 to Chargepoint.TYPE_2_UNKNOWN)
|
||||
),
|
||||
chargepoints
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun testMatchChargepointsMissingSchuko() {
|
||||
// single charger with 2 22kw chargepoints and two Schuko sockets
|
||||
val chargepoints = listOf(
|
||||
Chargepoint(Chargepoint.TYPE_2_UNKNOWN, 22.0, 2),
|
||||
Chargepoint(Chargepoint.SCHUKO, 2.3, 2)
|
||||
)
|
||||
|
||||
// NewMotion only includes the Type 2 sockets
|
||||
assertEquals(
|
||||
mapOf(chargepoints[0] to setOf(0L, 1L)),
|
||||
BaseAvailabilityDetector.matchChargepoints(
|
||||
mapOf(
|
||||
0L to (27.0 to Chargepoint.TYPE_2_UNKNOWN),
|
||||
1L to (27.0 to Chargepoint.TYPE_2_UNKNOWN)
|
||||
),
|
||||
chargepoints
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMatchChargepointsMissingSchukoDifferentPower() {
|
||||
// single charger with 2 22kw chargepoints with load balancing and two Schuko sockets
|
||||
val chargepoints = listOf(
|
||||
Chargepoint(Chargepoint.TYPE_2_UNKNOWN, 22.0, 1),
|
||||
Chargepoint(Chargepoint.TYPE_2_UNKNOWN, 11.0, 1),
|
||||
Chargepoint(Chargepoint.SCHUKO, 2.3, 2)
|
||||
)
|
||||
|
||||
// NewMotion only includes the Type 2 sockets
|
||||
assertEquals(
|
||||
mapOf(chargepoints[1] to setOf(0L), chargepoints[0] to setOf(1L)),
|
||||
BaseAvailabilityDetector.matchChargepoints(
|
||||
mapOf(
|
||||
0L to (27.0 to Chargepoint.TYPE_2_UNKNOWN),
|
||||
1L to (27.0 to Chargepoint.TYPE_2_UNKNOWN)
|
||||
),
|
||||
chargepoints
|
||||
)
|
||||
|
||||
@@ -10,6 +10,7 @@ import okhttp3.mockwebserver.RecordedRequest
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import java.time.LocalTime
|
||||
|
||||
class GoingElectricApiTest {
|
||||
val api: GoingElectricApi
|
||||
@@ -63,6 +64,21 @@ class GoingElectricApiTest {
|
||||
assertEquals(2105, charger.id)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLoadChargepointDetail2() {
|
||||
val response = runBlocking { api.getChargepointDetail(34210) }
|
||||
assertTrue(response.isSuccessful)
|
||||
val body = response.body()!!
|
||||
assertEquals("ok", body.status)
|
||||
assertEquals(null, body.startkey)
|
||||
assertEquals(1, body.chargelocations.size)
|
||||
val charger = body.chargelocations[0] as GEChargeLocation
|
||||
assertEquals(34210, charger.id)
|
||||
assertEquals(LocalTime.MIN, charger.openinghours!!.days!!.monday.start)
|
||||
assertEquals(LocalTime.MAX, charger.openinghours!!.days!!.monday.end)
|
||||
assertEquals(LocalTime.MAX, charger.openinghours!!.days!!.tuesday.end)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLoadChargepointList() {
|
||||
val response = runBlocking {
|
||||
|
||||
362
app/src/test/resources/chargers/34210.json
Normal file
362
app/src/test/resources/chargers/34210.json
Normal file
@@ -0,0 +1,362 @@
|
||||
{
|
||||
"status": "ok",
|
||||
"chargelocations": [
|
||||
{
|
||||
"ge_id": 34210,
|
||||
"name": "Langendreer Marktplatz",
|
||||
"address": {
|
||||
"city": "Bochum",
|
||||
"country": "Deutschland",
|
||||
"postcode": "44892",
|
||||
"street": "Oberstraße 2"
|
||||
},
|
||||
"coordinates": {
|
||||
"lat": 51.473454,
|
||||
"lng": 7.325153
|
||||
},
|
||||
"chargepoints": [
|
||||
{
|
||||
"type": "typ2_socket",
|
||||
"voltage": "400",
|
||||
"amperage": "32",
|
||||
"current": "acthree",
|
||||
"power": 22,
|
||||
"count": 2
|
||||
}
|
||||
],
|
||||
"network": "be.energised",
|
||||
"operator": "Stadtwerke Bochum GmbH",
|
||||
"cost": {
|
||||
"freecharging": false,
|
||||
"freeparking": true,
|
||||
"description_short": false,
|
||||
"description_long": false
|
||||
},
|
||||
"fault_report": false,
|
||||
"verified": true,
|
||||
"barrierfree": true,
|
||||
"openinghours": {
|
||||
"24/7": false,
|
||||
"description": "Markttage: Dienstag und Freitag",
|
||||
"days": {
|
||||
"monday": "around the clock",
|
||||
"tuesday": "from 15:00 till 24:00",
|
||||
"wednesday": "around the clock",
|
||||
"thursday": "around the clock",
|
||||
"friday": "from 15:00 till 24:00",
|
||||
"saturday": "around the clock",
|
||||
"sunday": "around the clock",
|
||||
"holiday": "closed"
|
||||
}
|
||||
},
|
||||
"url": "//www.goingelectric.de/stromtankstellen/Deutschland/Bochum/Langendreer-Marktplatz-Oberstrasse-2/34210/",
|
||||
"ladeweile": false,
|
||||
"location_description": false,
|
||||
"general_information": false,
|
||||
"photos": [
|
||||
{
|
||||
"id": 77283
|
||||
},
|
||||
{
|
||||
"id": 77284
|
||||
},
|
||||
{
|
||||
"id": 77285
|
||||
},
|
||||
{
|
||||
"id": 77286
|
||||
}
|
||||
],
|
||||
"chargecards": [
|
||||
{
|
||||
"id": 274
|
||||
},
|
||||
{
|
||||
"id": 11
|
||||
},
|
||||
{
|
||||
"id": 7
|
||||
},
|
||||
{
|
||||
"id": 349
|
||||
},
|
||||
{
|
||||
"id": 9
|
||||
},
|
||||
{
|
||||
"id": 75
|
||||
},
|
||||
{
|
||||
"id": 12
|
||||
},
|
||||
{
|
||||
"id": 319
|
||||
},
|
||||
{
|
||||
"id": 36
|
||||
},
|
||||
{
|
||||
"id": 368
|
||||
},
|
||||
{
|
||||
"id": 207
|
||||
},
|
||||
{
|
||||
"id": 124
|
||||
},
|
||||
{
|
||||
"id": 43
|
||||
},
|
||||
{
|
||||
"id": 251
|
||||
},
|
||||
{
|
||||
"id": 233
|
||||
},
|
||||
{
|
||||
"id": 200
|
||||
},
|
||||
{
|
||||
"id": 13
|
||||
},
|
||||
{
|
||||
"id": 44
|
||||
},
|
||||
{
|
||||
"id": 201
|
||||
},
|
||||
{
|
||||
"id": 242
|
||||
},
|
||||
{
|
||||
"id": 332
|
||||
},
|
||||
{
|
||||
"id": 338
|
||||
},
|
||||
{
|
||||
"id": 216
|
||||
},
|
||||
{
|
||||
"id": 312
|
||||
},
|
||||
{
|
||||
"id": 248
|
||||
},
|
||||
{
|
||||
"id": 139
|
||||
},
|
||||
{
|
||||
"id": 402
|
||||
},
|
||||
{
|
||||
"id": 249
|
||||
},
|
||||
{
|
||||
"id": 120
|
||||
},
|
||||
{
|
||||
"id": 223
|
||||
},
|
||||
{
|
||||
"id": 188
|
||||
},
|
||||
{
|
||||
"id": 326
|
||||
},
|
||||
{
|
||||
"id": 151
|
||||
},
|
||||
{
|
||||
"id": 288
|
||||
},
|
||||
{
|
||||
"id": 355
|
||||
},
|
||||
{
|
||||
"id": 336
|
||||
},
|
||||
{
|
||||
"id": 195
|
||||
},
|
||||
{
|
||||
"id": 297
|
||||
},
|
||||
{
|
||||
"id": 358
|
||||
},
|
||||
{
|
||||
"id": 333
|
||||
},
|
||||
{
|
||||
"id": 356
|
||||
},
|
||||
{
|
||||
"id": 257
|
||||
},
|
||||
{
|
||||
"id": 351
|
||||
},
|
||||
{
|
||||
"id": 343
|
||||
},
|
||||
{
|
||||
"id": 103
|
||||
},
|
||||
{
|
||||
"id": 346
|
||||
},
|
||||
{
|
||||
"id": 386
|
||||
},
|
||||
{
|
||||
"id": 256
|
||||
},
|
||||
{
|
||||
"id": 408
|
||||
},
|
||||
{
|
||||
"id": 392
|
||||
},
|
||||
{
|
||||
"id": 330
|
||||
},
|
||||
{
|
||||
"id": 347
|
||||
},
|
||||
{
|
||||
"id": 503
|
||||
},
|
||||
{
|
||||
"id": 511
|
||||
},
|
||||
{
|
||||
"id": 518
|
||||
},
|
||||
{
|
||||
"id": 512
|
||||
},
|
||||
{
|
||||
"id": 513
|
||||
},
|
||||
{
|
||||
"id": 514
|
||||
},
|
||||
{
|
||||
"id": 515
|
||||
},
|
||||
{
|
||||
"id": 516
|
||||
},
|
||||
{
|
||||
"id": 517
|
||||
},
|
||||
{
|
||||
"id": 535
|
||||
},
|
||||
{
|
||||
"id": 519
|
||||
},
|
||||
{
|
||||
"id": 520
|
||||
},
|
||||
{
|
||||
"id": 524
|
||||
},
|
||||
{
|
||||
"id": 525
|
||||
},
|
||||
{
|
||||
"id": 528
|
||||
},
|
||||
{
|
||||
"id": 533
|
||||
},
|
||||
{
|
||||
"id": 498
|
||||
},
|
||||
{
|
||||
"id": 541
|
||||
},
|
||||
{
|
||||
"id": 543
|
||||
},
|
||||
{
|
||||
"id": 544
|
||||
},
|
||||
{
|
||||
"id": 499
|
||||
},
|
||||
{
|
||||
"id": 482
|
||||
},
|
||||
{
|
||||
"id": 497
|
||||
},
|
||||
{
|
||||
"id": 433
|
||||
},
|
||||
{
|
||||
"id": 299
|
||||
},
|
||||
{
|
||||
"id": 366
|
||||
},
|
||||
{
|
||||
"id": 414
|
||||
},
|
||||
{
|
||||
"id": 419
|
||||
},
|
||||
{
|
||||
"id": 428
|
||||
},
|
||||
{
|
||||
"id": 429
|
||||
},
|
||||
{
|
||||
"id": 430
|
||||
},
|
||||
{
|
||||
"id": 432
|
||||
},
|
||||
{
|
||||
"id": 456
|
||||
},
|
||||
{
|
||||
"id": 492
|
||||
},
|
||||
{
|
||||
"id": 464
|
||||
},
|
||||
{
|
||||
"id": 469
|
||||
},
|
||||
{
|
||||
"id": 474
|
||||
},
|
||||
{
|
||||
"id": 479
|
||||
},
|
||||
{
|
||||
"id": 342
|
||||
},
|
||||
{
|
||||
"id": 486
|
||||
},
|
||||
{
|
||||
"id": 487
|
||||
},
|
||||
{
|
||||
"id": 488
|
||||
},
|
||||
{
|
||||
"id": 489
|
||||
},
|
||||
{
|
||||
"id": 545
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
10
build.gradle
10
build.gradle
@@ -2,15 +2,15 @@
|
||||
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.5.31'
|
||||
ext.about_libs_version = '8.8.5'
|
||||
ext.nav_version = '2.4.0-alpha10'
|
||||
ext.about_libs_version = '8.9.4'
|
||||
ext.nav_version = '2.4.0-beta02'
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.0.2'
|
||||
classpath 'com.android.tools.build:gradle:7.0.3'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
classpath "com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin:$about_libs_version"
|
||||
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
|
||||
@@ -27,10 +27,6 @@ allprojects {
|
||||
//noinspection JcenterRepositoryObsolete
|
||||
jcenter() // still required for https://github.com/kamikat/moshi-jsonapi
|
||||
maven { url 'https://jitpack.io' }
|
||||
maven { url "https://oss.sonatype.org/content/repositories/snapshots" }
|
||||
flatDir {
|
||||
dirs 'libs'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
11
fastlane/metadata/android/de-DE/changelogs/66.txt
Normal file
11
fastlane/metadata/android/de-DE/changelogs/66.txt
Normal file
@@ -0,0 +1,11 @@
|
||||
Verbesserungen:
|
||||
- Echtzeitdaten für weitere AC-Ladestationen
|
||||
- FAQ-Seite zum Preisvergleich
|
||||
- GoingElectric: irreführendes "Laden/Parken: kostenpflichtig" entfernt
|
||||
|
||||
Fehlerbehebungen:
|
||||
- GoingElectric: Beschreibung zu Öffnungszeiten wurde nicht immer angezeigt
|
||||
- Leere Detailansicht, nachdem die App längere Zeit im Hintergrund war
|
||||
- Preisvergleich: Exklusive Energiekunden-Tarife unter "Meine Tarife" wurden nicht angezeigt
|
||||
- Eingestellte Sprache wurde nicht für Google Maps genutzt
|
||||
- Abstürze behoben
|
||||
3
fastlane/metadata/android/de-DE/changelogs/67.txt
Normal file
3
fastlane/metadata/android/de-DE/changelogs/67.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
Fehlerbehebungen:
|
||||
- Preisvergleich funktionierte nicht, wenn "meine Tarife" noch nicht ausgewählt waren
|
||||
- Abstürze behoben
|
||||
11
fastlane/metadata/android/en-US/changelogs/66.txt
Normal file
11
fastlane/metadata/android/en-US/changelogs/66.txt
Normal file
@@ -0,0 +1,11 @@
|
||||
Improvements:
|
||||
- Realtime data for additional AC chargers
|
||||
- FAQ page about price comparison
|
||||
- GoingElectric: removed misleading "charging/parking: paid"
|
||||
|
||||
Bug fixes:
|
||||
- GoingElectric: Description text for opening hours was not always shown
|
||||
- Empty detail view after app was in background for a longer time
|
||||
- Price comparison: provider exclusive plans were not shown even if selected under "my plans"
|
||||
- Selected language was not used for Google Maps
|
||||
- Fixed various crashes
|
||||
3
fastlane/metadata/android/en-US/changelogs/67.txt
Normal file
3
fastlane/metadata/android/en-US/changelogs/67.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
Bug fixes:
|
||||
- Price comparison was not working when "my charging plans" had not yet been selected
|
||||
- Fixed crashes
|
||||
Reference in New Issue
Block a user