mirror of
https://github.com/ev-map/EVMap.git
synced 2025-12-25 08:07:46 -05:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fb5da76834 | ||
|
|
ad922f0667 | ||
|
|
773b35d9ce | ||
|
|
a3347c9d62 | ||
|
|
da671b8dd3 | ||
|
|
6d877e13e4 | ||
|
|
8ab1d7170c | ||
|
|
1f75d722cd | ||
|
|
11bd4b2cec |
@@ -21,8 +21,8 @@ android {
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 33
|
||||
// NOTE: always increase versionCode by 2 since automotive flavor uses versionCode + 1
|
||||
versionCode 142
|
||||
versionName "1.4.2"
|
||||
versionCode 148
|
||||
versionName "1.4.3"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
resConfigs supportedLocales.split(",")
|
||||
@@ -171,7 +171,7 @@ dependencies {
|
||||
implementation 'androidx.recyclerview:recyclerview:1.2.1'
|
||||
implementation 'androidx.browser:browser:1.4.0'
|
||||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
|
||||
implementation 'com.github.johan12345:CustomBottomSheetBehavior:3529a5a9f1'
|
||||
implementation 'com.github.johan12345:CustomBottomSheetBehavior:f4f641aab5'
|
||||
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
|
||||
implementation 'com.squareup.retrofit2:converter-moshi:2.9.0'
|
||||
implementation 'com.squareup.okhttp3:okhttp:4.9.0'
|
||||
|
||||
@@ -77,34 +77,44 @@ class ChargepriceScreen(ctx: CarContext, val charger: ChargeLocation) : Screen(c
|
||||
carContext.stringProvider(),
|
||||
chargepoint.type
|
||||
)
|
||||
} ${chargepoint.formatPower()} " + carContext.getString(
|
||||
R.string.chargeprice_stats,
|
||||
meta.energy,
|
||||
time(meta.duration.roundToInt()),
|
||||
meta.energy / meta.duration * 60
|
||||
)
|
||||
} ${chargepoint.formatPower()} ${
|
||||
carContext.getString(
|
||||
R.string.chargeprice_stats,
|
||||
meta.energy,
|
||||
time(meta.duration.roundToInt()),
|
||||
meta.energy / meta.duration * 60
|
||||
)
|
||||
}"
|
||||
}
|
||||
}
|
||||
val myTariffs = prefs.chargepriceMyTariffs
|
||||
val myTariffsAll = prefs.chargepriceMyTariffsAll
|
||||
val list = ItemList.Builder().apply {
|
||||
setNoItemsMessage(
|
||||
errorMessage ?: carContext.getString(R.string.chargeprice_no_tariffs_found)
|
||||
|
||||
val prices = prices?.take(maxRows)
|
||||
if (prices != null && prices.isNotEmpty() && !myTariffsAll && myTariffs != null) {
|
||||
val (myPrices, otherPrices) = prices.partition { price -> price.tariffId in myTariffs }
|
||||
val myPricesList = buildPricesList(myPrices)
|
||||
val otherPricesList = buildPricesList(otherPrices)
|
||||
addSectionedList(
|
||||
SectionedItemList.create(
|
||||
myPricesList,
|
||||
(header?.let { it + "\n" } ?: "") +
|
||||
carContext.getString(R.string.chargeprice_header_my_tariffs)
|
||||
)
|
||||
)
|
||||
addSectionedList(
|
||||
SectionedItemList.create(
|
||||
otherPricesList,
|
||||
carContext.getString(R.string.chargeprice_header_other_tariffs)
|
||||
)
|
||||
)
|
||||
prices?.take(maxRows)?.forEach { price ->
|
||||
addItem(Row.Builder().apply {
|
||||
setTitle(formatProvider(price))
|
||||
addText(formatPrice(price))
|
||||
if (carContext.carAppApiLevel >= 5) {
|
||||
setEnabled(myTariffsAll || myTariffs != null && price.tariffId in myTariffs)
|
||||
}
|
||||
}.build())
|
||||
}
|
||||
}.build()
|
||||
if (header != null && list.items.isNotEmpty()) {
|
||||
addSectionedList(SectionedItemList.create(list, header))
|
||||
} else {
|
||||
setSingleList(list)
|
||||
val list = buildPricesList(prices)
|
||||
if (header != null) {
|
||||
addSectionedList(SectionedItemList.create(list, header))
|
||||
} else {
|
||||
setSingleList(list)
|
||||
}
|
||||
}
|
||||
}
|
||||
setActionStrip(
|
||||
@@ -155,6 +165,21 @@ class ChargepriceScreen(ctx: CarContext, val charger: ChargeLocation) : Screen(c
|
||||
}.build()
|
||||
}
|
||||
|
||||
private fun buildPricesList(prices: List<ChargePrice>?): ItemList {
|
||||
return ItemList.Builder().apply {
|
||||
setNoItemsMessage(
|
||||
errorMessage
|
||||
?: carContext.getString(R.string.chargeprice_no_tariffs_found)
|
||||
)
|
||||
prices?.forEach { price ->
|
||||
addItem(Row.Builder().apply {
|
||||
setTitle(formatProvider(price))
|
||||
addText(formatPrice(price))
|
||||
}.build())
|
||||
}
|
||||
}.build()
|
||||
}
|
||||
|
||||
private fun formatProvider(price: ChargePrice): String {
|
||||
if (!price.tariffName.startsWith(price.provider)) {
|
||||
return price.provider + " " + price.tariffName
|
||||
|
||||
@@ -263,7 +263,9 @@ class MapScreen(ctx: CarContext, val session: EVMapSession) :
|
||||
}
|
||||
.build())
|
||||
.build())
|
||||
if (carContext.carAppApiLevel >= 5) {
|
||||
if (carContext.carAppApiLevel >= 5 ||
|
||||
(BuildConfig.FLAVOR_automotive == "automotive" && carContext.carAppApiLevel >= 4)
|
||||
) {
|
||||
setOnContentRefreshListener(this@MapScreen)
|
||||
}
|
||||
}.build()
|
||||
|
||||
@@ -105,15 +105,15 @@ class PlaceSearchScreen(ctx: CarContext, val session: EVMapSession) : Screen(ctx
|
||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||
)
|
||||
addText(text)
|
||||
}
|
||||
|
||||
setOnClickListener {
|
||||
lifecycleScope.launch {
|
||||
val placeDetails = getDetails(place.id)
|
||||
prefs.placeSearchResultAndroidAuto = placeDetails.latLng
|
||||
prefs.placeSearchResultAndroidAutoName =
|
||||
place.primaryText.toString()
|
||||
screenManager.popTo(MapScreen.MARKER)
|
||||
}
|
||||
setOnClickListener {
|
||||
lifecycleScope.launch {
|
||||
val placeDetails = getDetails(place.id)
|
||||
prefs.placeSearchResultAndroidAuto = placeDetails.latLng
|
||||
prefs.placeSearchResultAndroidAutoName =
|
||||
place.primaryText.toString()
|
||||
screenManager.popTo(MapScreen.MARKER)
|
||||
}
|
||||
}
|
||||
}.build())
|
||||
|
||||
@@ -14,13 +14,18 @@ import retrofit2.http.GET
|
||||
import retrofit2.http.Path
|
||||
import retrofit2.http.Query
|
||||
|
||||
interface FronyxApi {
|
||||
private interface FronyxApiRetrofit {
|
||||
@GET("predictions/evse-id/{evseId}")
|
||||
suspend fun getPredictionsForEvseId(
|
||||
@Path("evseId") evseId: String,
|
||||
@Query("timeframe") timeframe: Int? = null
|
||||
): FronyxEvseIdResponse
|
||||
|
||||
@GET("predictions/evses")
|
||||
suspend fun getPredictionsForEvseIds(
|
||||
@Query("evseIds", encoded = true) evseIds: String // comma-separated
|
||||
): List<FronyxEvseIdResponse>
|
||||
|
||||
companion object {
|
||||
private val cacheSize = 1L * 1024 * 1024 // 1MB
|
||||
|
||||
@@ -32,7 +37,7 @@ interface FronyxApi {
|
||||
apikey: String,
|
||||
baseurl: String = "https://api.fronyx.io/api/",
|
||||
context: Context? = null
|
||||
): FronyxApi {
|
||||
): FronyxApiRetrofit {
|
||||
val client = OkHttpClient.Builder().apply {
|
||||
addInterceptor { chain ->
|
||||
// add API key to every request
|
||||
@@ -56,9 +61,28 @@ interface FronyxApi {
|
||||
.addConverterFactory(MoshiConverterFactory.create(moshi))
|
||||
.client(client)
|
||||
.build()
|
||||
return retrofit.create(FronyxApi::class.java)
|
||||
return retrofit.create(FronyxApiRetrofit::class.java)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class FronyxApi(
|
||||
apikey: String,
|
||||
baseurl: String = "https://api.fronyx.io/api/",
|
||||
context: Context? = null
|
||||
) {
|
||||
private val api = FronyxApiRetrofit.create(apikey, baseurl, context)
|
||||
|
||||
suspend fun getPredictionsForEvseId(
|
||||
evseId: String,
|
||||
timeframe: Int? = null
|
||||
): FronyxEvseIdResponse = api.getPredictionsForEvseId(evseId, timeframe)
|
||||
|
||||
suspend fun getPredictionsForEvseIds(
|
||||
evseIds: List<String>
|
||||
): List<FronyxEvseIdResponse> = api.getPredictionsForEvseIds(evseIds.joinToString(","))
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Checks if a chargepoint is supported by Fronyx.
|
||||
*
|
||||
@@ -6,7 +6,8 @@ import java.time.ZonedDateTime
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class FronyxEvseIdResponse(
|
||||
val evseId: String,
|
||||
val predictions: List<FronyxPrediction>
|
||||
val predictions: List<FronyxPrediction>,
|
||||
val locationId: String?
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
|
||||
@@ -167,7 +167,7 @@ class ChargepriceFragment : Fragment() {
|
||||
chargepriceAdapter.myTariffsAll = it
|
||||
}
|
||||
vm.chargePricesForChargepoint.observe(viewLifecycleOwner) {
|
||||
chargepriceAdapter.submitList(it.data)
|
||||
it?.data?.let { chargepriceAdapter.submitList(it) }
|
||||
}
|
||||
|
||||
val connectorsAdapter = CheckableConnectorAdapter()
|
||||
|
||||
@@ -229,7 +229,7 @@ class MapViewModel(application: Application, private val state: SavedStateHandle
|
||||
}
|
||||
}
|
||||
|
||||
val predictionApi = FronyxApi.create(application.getString(R.string.fronyx_key))
|
||||
val predictionApi = FronyxApi(application.getString(R.string.fronyx_key))
|
||||
|
||||
val prediction: LiveData<Resource<List<FronyxEvseIdResponse>>> by lazy {
|
||||
availability.switchMap { av ->
|
||||
@@ -249,14 +249,13 @@ class MapViewModel(application: Application, private val state: SavedStateHandle
|
||||
).any { filtered.contains(it) }
|
||||
} ?: true
|
||||
}.flatMap { it.value }
|
||||
|
||||
try {
|
||||
val result = allEvseIds.map {
|
||||
predictionApi.getPredictionsForEvseId(it)
|
||||
val result = predictionApi.getPredictionsForEvseIds(allEvseIds)
|
||||
if (result.size == allEvseIds.size) {
|
||||
emit(Resource.success(result))
|
||||
} else {
|
||||
emit(Resource.error("not all EVSEIDs found", null))
|
||||
}
|
||||
|
||||
emit(Resource.success(result))
|
||||
println(result)
|
||||
} catch (e: IOException) {
|
||||
emit(Resource.error(e.message, null))
|
||||
e.printStackTrace()
|
||||
|
||||
@@ -149,7 +149,7 @@
|
||||
<string name="reorder">Reihenfolge ändern</string>
|
||||
<string name="delete">Löschen</string>
|
||||
<string name="save_as_profile">Als Profil speichern</string>
|
||||
<string name="save_profile_enter_name">Geben Sie den Namen des Filterprofils ein:</string>
|
||||
<string name="save_profile_enter_name">Gib den Namen des Filterprofils ein:</string>
|
||||
<string name="filterprofiles_empty_state">Du hast keine Filterprofile gespeichert</string>
|
||||
<string name="welcome_to_evmap">Willkommen bei EVMap</string>
|
||||
<string name="welcome_1">Finde Ladestationen für Elektroautos in deiner Nähe</string>
|
||||
@@ -286,4 +286,6 @@
|
||||
<string name="data_source_switched_to">Datenquelle zu %s umgeschaltet</string>
|
||||
<string name="pref_applink_associate">Unterstützte Links öffnen</string>
|
||||
<string name="pref_applink_associate_summary">von goingelectric.de und openchargemap.org</string>
|
||||
<string name="chargeprice_header_my_tariffs">Meine Tarife</string>
|
||||
<string name="chargeprice_header_other_tariffs">Andere Tarife</string>
|
||||
</resources>
|
||||
@@ -285,4 +285,6 @@
|
||||
<string name="data_source_switched_to">Data source switched to %s</string>
|
||||
<string name="pref_applink_associate">Open supported links</string>
|
||||
<string name="pref_applink_associate_summary">from goingelectric.de and openchargemap.org</string>
|
||||
<string name="chargeprice_header_my_tariffs">My plans</string>
|
||||
<string name="chargeprice_header_other_tariffs">Other plans</string>
|
||||
</resources>
|
||||
|
||||
@@ -20,7 +20,7 @@ class FronyxApiTest {
|
||||
webServer.start()
|
||||
|
||||
val apikey = ""
|
||||
fronyx = FronyxApi.create(
|
||||
fronyx = FronyxApi(
|
||||
apikey,
|
||||
webServer.url("/").toString()
|
||||
)
|
||||
@@ -36,6 +36,14 @@ class FronyxApiTest {
|
||||
val id = segments[2]
|
||||
return okResponse("/fronyx/${id.replace("*", "_")}.json")
|
||||
}
|
||||
"predictions/evses" -> {
|
||||
val ids = request.requestUrl!!.queryParameter("evseIds")!!.split(",")
|
||||
return okResponse(
|
||||
"/fronyx/${
|
||||
ids.map { it.replace("*", "_") }.joinToString(",")
|
||||
}.json"
|
||||
)
|
||||
}
|
||||
else -> return notFoundResponse
|
||||
}
|
||||
}
|
||||
@@ -43,7 +51,7 @@ class FronyxApiTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun apiTest() {
|
||||
fun apiTestSingle() {
|
||||
val evseId = "DE*ION*E202102"
|
||||
|
||||
runBlocking {
|
||||
@@ -57,4 +65,25 @@ class FronyxApiTest {
|
||||
assertEquals(FronyxStatus.AVAILABLE, result.predictions[0].status)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun apiTestMultiple() {
|
||||
val evseIds = listOf("DE*ION*E202101", "DE*ION*E202102")
|
||||
|
||||
runBlocking {
|
||||
val results = fronyx.getPredictionsForEvseIds(evseIds)
|
||||
results.forEachIndexed { i, result ->
|
||||
assertEquals(result.evseId, evseIds[i])
|
||||
assertEquals(25, result.predictions.size)
|
||||
assertEquals(
|
||||
ZonedDateTime.of(2022, 11, 16, 18, 0, 0, 0, ZoneOffset.UTC),
|
||||
result.predictions[0].timestamp
|
||||
)
|
||||
assertEquals(
|
||||
if (i == 0) FronyxStatus.UNAVAILABLE else FronyxStatus.AVAILABLE,
|
||||
result.predictions[0].status
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
214
app/src/test/resources/fronyx/DE_ION_E202101,DE_ION_E202102.json
Normal file
214
app/src/test/resources/fronyx/DE_ION_E202101,DE_ION_E202102.json
Normal file
@@ -0,0 +1,214 @@
|
||||
[
|
||||
{
|
||||
"evseId": "DE*ION*E202101",
|
||||
"locationId": "DE-ION-03e2876e-0fd0-4e9d-abc1-d2caa1473947",
|
||||
"predictions": [
|
||||
{
|
||||
"timestamp": "2022-11-16T18:00:00.000Z",
|
||||
"status": "UNAVAILABLE"
|
||||
},
|
||||
{
|
||||
"timestamp": "2022-11-16T18:15:00.000Z",
|
||||
"status": "UNAVAILABLE"
|
||||
},
|
||||
{
|
||||
"timestamp": "2022-11-16T18:30:00.000Z",
|
||||
"status": "UNAVAILABLE"
|
||||
},
|
||||
{
|
||||
"timestamp": "2022-11-16T18:45:00.000Z",
|
||||
"status": "UNAVAILABLE"
|
||||
},
|
||||
{
|
||||
"timestamp": "2022-11-16T19:00:00.000Z",
|
||||
"status": "UNAVAILABLE"
|
||||
},
|
||||
{
|
||||
"timestamp": "2022-11-16T19:15:00.000Z",
|
||||
"status": "UNAVAILABLE"
|
||||
},
|
||||
{
|
||||
"timestamp": "2022-11-16T19:30:00.000Z",
|
||||
"status": "UNAVAILABLE"
|
||||
},
|
||||
{
|
||||
"timestamp": "2022-11-16T19:45:00.000Z",
|
||||
"status": "UNAVAILABLE"
|
||||
},
|
||||
{
|
||||
"timestamp": "2022-11-16T20:00:00.000Z",
|
||||
"status": "UNAVAILABLE"
|
||||
},
|
||||
{
|
||||
"timestamp": "2022-11-16T20:15:00.000Z",
|
||||
"status": "UNAVAILABLE"
|
||||
},
|
||||
{
|
||||
"timestamp": "2022-11-16T20:30:00.000Z",
|
||||
"status": "UNAVAILABLE"
|
||||
},
|
||||
{
|
||||
"timestamp": "2022-11-16T20:45:00.000Z",
|
||||
"status": "UNAVAILABLE"
|
||||
},
|
||||
{
|
||||
"timestamp": "2022-11-16T21:00:00.000Z",
|
||||
"status": "UNAVAILABLE"
|
||||
},
|
||||
{
|
||||
"timestamp": "2022-11-16T21:15:00.000Z",
|
||||
"status": "UNAVAILABLE"
|
||||
},
|
||||
{
|
||||
"timestamp": "2022-11-16T21:30:00.000Z",
|
||||
"status": "UNAVAILABLE"
|
||||
},
|
||||
{
|
||||
"timestamp": "2022-11-16T21:45:00.000Z",
|
||||
"status": "UNAVAILABLE"
|
||||
},
|
||||
{
|
||||
"timestamp": "2022-11-16T22:00:00.000Z",
|
||||
"status": "UNAVAILABLE"
|
||||
},
|
||||
{
|
||||
"timestamp": "2022-11-16T22:15:00.000Z",
|
||||
"status": "UNAVAILABLE"
|
||||
},
|
||||
{
|
||||
"timestamp": "2022-11-16T22:30:00.000Z",
|
||||
"status": "UNAVAILABLE"
|
||||
},
|
||||
{
|
||||
"timestamp": "2022-11-16T22:45:00.000Z",
|
||||
"status": "UNAVAILABLE"
|
||||
},
|
||||
{
|
||||
"timestamp": "2022-11-16T23:00:00.000Z",
|
||||
"status": "UNAVAILABLE"
|
||||
},
|
||||
{
|
||||
"timestamp": "2022-11-16T23:15:00.000Z",
|
||||
"status": "UNAVAILABLE"
|
||||
},
|
||||
{
|
||||
"timestamp": "2022-11-16T23:30:00.000Z",
|
||||
"status": "UNAVAILABLE"
|
||||
},
|
||||
{
|
||||
"timestamp": "2022-11-16T23:45:00.000Z",
|
||||
"status": "UNAVAILABLE"
|
||||
},
|
||||
{
|
||||
"timestamp": "2022-11-17T00:00:00.000Z",
|
||||
"status": "UNAVAILABLE"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"evseId": "DE*ION*E202102",
|
||||
"locationId": "DE-ION-03e2876e-0fd0-4e9d-abc1-d2caa1473947",
|
||||
"predictions": [
|
||||
{
|
||||
"timestamp": "2022-11-16T18:00:00.000Z",
|
||||
"status": "AVAILABLE"
|
||||
},
|
||||
{
|
||||
"timestamp": "2022-11-16T18:15:00.000Z",
|
||||
"status": "AVAILABLE"
|
||||
},
|
||||
{
|
||||
"timestamp": "2022-11-16T18:30:00.000Z",
|
||||
"status": "UNAVAILABLE"
|
||||
},
|
||||
{
|
||||
"timestamp": "2022-11-16T18:45:00.000Z",
|
||||
"status": "UNAVAILABLE"
|
||||
},
|
||||
{
|
||||
"timestamp": "2022-11-16T19:00:00.000Z",
|
||||
"status": "UNAVAILABLE"
|
||||
},
|
||||
{
|
||||
"timestamp": "2022-11-16T19:15:00.000Z",
|
||||
"status": "UNAVAILABLE"
|
||||
},
|
||||
{
|
||||
"timestamp": "2022-11-16T19:30:00.000Z",
|
||||
"status": "UNAVAILABLE"
|
||||
},
|
||||
{
|
||||
"timestamp": "2022-11-16T19:45:00.000Z",
|
||||
"status": "UNAVAILABLE"
|
||||
},
|
||||
{
|
||||
"timestamp": "2022-11-16T20:00:00.000Z",
|
||||
"status": "UNAVAILABLE"
|
||||
},
|
||||
{
|
||||
"timestamp": "2022-11-16T20:15:00.000Z",
|
||||
"status": "UNAVAILABLE"
|
||||
},
|
||||
{
|
||||
"timestamp": "2022-11-16T20:30:00.000Z",
|
||||
"status": "UNAVAILABLE"
|
||||
},
|
||||
{
|
||||
"timestamp": "2022-11-16T20:45:00.000Z",
|
||||
"status": "UNAVAILABLE"
|
||||
},
|
||||
{
|
||||
"timestamp": "2022-11-16T21:00:00.000Z",
|
||||
"status": "UNAVAILABLE"
|
||||
},
|
||||
{
|
||||
"timestamp": "2022-11-16T21:15:00.000Z",
|
||||
"status": "AVAILABLE"
|
||||
},
|
||||
{
|
||||
"timestamp": "2022-11-16T21:30:00.000Z",
|
||||
"status": "AVAILABLE"
|
||||
},
|
||||
{
|
||||
"timestamp": "2022-11-16T21:45:00.000Z",
|
||||
"status": "AVAILABLE"
|
||||
},
|
||||
{
|
||||
"timestamp": "2022-11-16T22:00:00.000Z",
|
||||
"status": "AVAILABLE"
|
||||
},
|
||||
{
|
||||
"timestamp": "2022-11-16T22:15:00.000Z",
|
||||
"status": "AVAILABLE"
|
||||
},
|
||||
{
|
||||
"timestamp": "2022-11-16T22:30:00.000Z",
|
||||
"status": "AVAILABLE"
|
||||
},
|
||||
{
|
||||
"timestamp": "2022-11-16T22:45:00.000Z",
|
||||
"status": "AVAILABLE"
|
||||
},
|
||||
{
|
||||
"timestamp": "2022-11-16T23:00:00.000Z",
|
||||
"status": "AVAILABLE"
|
||||
},
|
||||
{
|
||||
"timestamp": "2022-11-16T23:15:00.000Z",
|
||||
"status": "AVAILABLE"
|
||||
},
|
||||
{
|
||||
"timestamp": "2022-11-16T23:30:00.000Z",
|
||||
"status": "AVAILABLE"
|
||||
},
|
||||
{
|
||||
"timestamp": "2022-11-16T23:45:00.000Z",
|
||||
"status": "AVAILABLE"
|
||||
},
|
||||
{
|
||||
"timestamp": "2022-11-17T00:00:00.000Z",
|
||||
"status": "AVAILABLE"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
8
fastlane/metadata/android/de-DE/changelogs/146.txt
Normal file
8
fastlane/metadata/android/de-DE/changelogs/146.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
Verbesserungen:
|
||||
- Laden der Verfügbarkeitsprognosen beschleunigt
|
||||
- Android Auto: "Meine Tarife" im Preisvergleich hervorheben
|
||||
|
||||
Fehler behoben:
|
||||
- Android Automotive: Aktualisieren-Button fehlte
|
||||
- Darstellungsfehler nach dem Scrollen der Detailansicht behoben
|
||||
- Abstürze behoben
|
||||
9
fastlane/metadata/android/de-DE/changelogs/148.txt
Normal file
9
fastlane/metadata/android/de-DE/changelogs/148.txt
Normal file
@@ -0,0 +1,9 @@
|
||||
Verbesserungen:
|
||||
- Laden der Verfügbarkeitsprognosen beschleunigt
|
||||
- Android Auto: "Meine Tarife" im Preisvergleich hervorheben
|
||||
|
||||
Fehler behoben:
|
||||
- Android Automotive: Aktualisieren-Button fehlte
|
||||
- Android Auto: Klick auf Suchergebnis funktioniert manchmal nicht
|
||||
- Darstellungsfehler nach dem Scrollen der Detailansicht behoben
|
||||
- Abstürze behoben
|
||||
8
fastlane/metadata/android/en-US/changelogs/146.txt
Normal file
8
fastlane/metadata/android/en-US/changelogs/146.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
Improvements:
|
||||
- Faster loading of availability prediction
|
||||
- Android Auto: highlight "my plans" in price comparison
|
||||
|
||||
Bugfixes:
|
||||
- Android Automotive: refresh button was missing
|
||||
- fixed visual bug after scrolling detail view
|
||||
- fixed crashes
|
||||
9
fastlane/metadata/android/en-US/changelogs/148.txt
Normal file
9
fastlane/metadata/android/en-US/changelogs/148.txt
Normal file
@@ -0,0 +1,9 @@
|
||||
Improvements:
|
||||
- Faster loading of availability prediction
|
||||
- Android Auto: highlight "my plans" in price comparison
|
||||
|
||||
Bugfixes:
|
||||
- Android Automotive: refresh button was missing
|
||||
- Android Auto: Clicking search result sometimes not working
|
||||
- fixed visual bug after scrolling detail view
|
||||
- fixed crashes
|
||||
Reference in New Issue
Block a user