Compare commits

...

20 Commits
1.9.3 ... 1.9.6

Author SHA1 Message Date
Johan von Forstner
398f159e27 Release 1.9.6 2024-07-31 16:37:23 +02:00
Johan von Forstner
6ab3ba2ed2 Mapbox Autocomplete: catch HttpExceptions 2024-07-31 16:10:36 +02:00
Johan von Forstner
f59fd9b3aa OpenChargeMap API: fix nullability 2024-07-31 16:04:14 +02:00
Johan von Forstner
9e18c62d9d remove unused method 2024-07-31 16:03:07 +02:00
Johan von Forstner
3626c9a72f update spatia-room 2024-07-31 15:56:48 +02:00
Johan von Forstner
36805d8224 AAOS: fallback to regular navigation intent if car app intent fails 2024-07-30 21:21:41 +02:00
johan12345
b1dee90068 docs improvements 2024-07-13 14:11:51 +02:00
johan12345
dfc7de75ad Release 1.9.5 2024-06-30 17:37:53 +02:00
johan12345
32c7774a3a update MapLibre
may help with #351
2024-06-30 16:34:03 +02:00
johan12345
02ef25b961 rework navigation handling to avoid changing start destination
fixes the following issue:
- start app for the first time, go through onboarding
- go to settings, change to dark mode
- try to go back to the map using the drawer
-> stuck, only back button helps
2024-06-30 16:22:35 +02:00
johan12345
e535e77b7a update build tools 2024-06-30 15:08:30 +02:00
johan12345
5b0b4e4337 remove "noinspection JCenterRepositoryObsolete" 2024-06-30 15:07:50 +02:00
johan12345
a6bbf635c5 Update AnyMaps & Google Maps
uses new Google Maps dark mode
2024-06-30 14:08:24 +02:00
johan12345
591f99dea4 update to released locale-config-x version 2024-06-22 11:53:52 +02:00
johan12345
0c5bd69205 Release 1.9.4 2024-06-21 00:25:13 +02:00
johan12345
72e98cf611 fix NoSuchElementExceptions in intent handling 2024-06-21 00:23:37 +02:00
johan12345
0fefffda2f update MapLibre
may help with #351
2024-06-21 00:19:38 +02:00
johan12345
49e555ef04 switch to fork of locale-config-x
fixes crash #352 until https://github.com/erfansn/locale-config-x/pull/2 is merged
2024-06-21 00:00:42 +02:00
johan12345
d6d1e915ee updated TeslaGuestApi 2024-06-19 00:19:36 +02:00
johan12345
546d7a11ce maybe fix rare NPE in GoingElectricAPI 2024-06-16 17:54:59 +02:00
20 changed files with 247 additions and 216 deletions

View File

@@ -46,8 +46,7 @@ features and how they can be obtained in our [documentation page](doc/api_keys.m
There are four different build flavors, `googleNormal`, `fossNormal`, `googleAutomotive`, and
`fossAutomotive`.
- The `foss` variants only use OSM data and should run on most Android devices, even without
Google Play Services.
- The `foss` variants only use OSM data for the base map and place search. They should run on most Android devices, even those without Google Play Services.
- `fossNormal` is intended to run on smartphones and tablets, and also includes the Android
Auto app for use on the car display (however Android Auto may not work if the app is not
installed from Google Play, see https://github.com/ev-map/EVMap/issues/319).

View File

@@ -20,8 +20,8 @@ android {
minSdk = 21
targetSdk = 34
// NOTE: always increase versionCode by 2 since automotive flavor uses versionCode + 1
versionCode = 224
versionName = "1.9.3"
versionCode = 230
versionName = "1.9.6"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
@@ -306,7 +306,7 @@ dependencies {
implementation("com.google.guava:guava:29.0-android")
implementation("com.github.pengrad:mapscaleview:1.6.0")
implementation("com.github.romandanylyk:PageIndicatorView:b1bad589b5")
implementation("com.github.erfansn:locale-config-x:1.0.0")
implementation("com.github.erfansn:locale-config-x:1.0.1")
// Android Auto
val carAppVersion = "1.4.0"
@@ -315,14 +315,17 @@ dependencies {
automotiveImplementation("androidx.car.app:app-automotive:$carAppVersion")
// AnyMaps
val anyMapsVersion = "1e00650bc7"
val anyMapsVersion = "3e6c71410f"
implementation("com.github.ev-map.AnyMaps:anymaps-base:$anyMapsVersion")
googleImplementation("com.github.ev-map.AnyMaps:anymaps-google:$anyMapsVersion")
googleImplementation("com.google.android.gms:play-services-maps:18.2.0")
googleImplementation("com.google.android.gms:play-services-maps:19.0.0")
implementation("com.github.ev-map.AnyMaps:anymaps-maplibre:$anyMapsVersion") {
// duplicates classes from mapbox-sdk-services
exclude("org.maplibre.gl", "android-sdk-geojson")
}
implementation("org.maplibre.gl:android-sdk:10.3.2-pre3") {
exclude("org.maplibre.gl", "android-sdk-geojson")
}
// Google Places
googleImplementation("com.google.android.libraries.places:places:3.5.0")
@@ -345,10 +348,7 @@ dependencies {
implementation("androidx.room:room-runtime:$room_version")
kapt("androidx.room:room-compiler:$room_version")
implementation("androidx.room:room-ktx:$room_version")
implementation("com.github.anboralabs:spatia-room:0.2.9") {
exclude(group = "com.github.dalgarins", module = "android-spatialite")
}
implementation("com.github.EV-map:android-spatialite:e5495c83ad") // version with minSdk increased to 21 & FORTIFY_SOURCE enabled
implementation("com.github.anboralabs:spatia-room:0.3.0")
// billing library
val billing_version = "7.0.0"

View File

@@ -60,6 +60,7 @@ class MapsActivity : AppCompatActivity(),
setContentView(R.layout.activity_maps)
val drawerLayout = findViewById<DrawerLayout>(R.id.drawer_layout)
appBarConfiguration = AppBarConfiguration(
setOf(
R.id.map,
@@ -67,7 +68,7 @@ class MapsActivity : AppCompatActivity(),
R.id.about,
R.id.settings
),
findViewById<DrawerLayout>(R.id.drawer_layout)
drawerLayout
)
val navHostFragment =
supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
@@ -87,6 +88,17 @@ class MapsActivity : AppCompatActivity(),
checkPlayServices(this)
navController.setGraph(navGraph, MapFragmentArgs(appStart = true).toBundle())
var deepLink: PendingIntent? = null
navController.addOnDestinationChangedListener { _, destination, _ ->
if (destination.id == R.id.onboarding) {
drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
} else {
drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)
}
}
if (!prefs.welcomeDialogShown || !prefs.dataSourceSet) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
// wait for splash screen animation to finish on first start
@@ -104,134 +116,125 @@ class MapsActivity : AppCompatActivity(),
}
})
}
navGraph.setStartDestination(R.id.onboarding)
navController.graph = navGraph
return
} else if (!prefs.privacyAccepted) {
navGraph.setStartDestination(R.id.onboarding)
navController.graph = navGraph
return
} else {
navGraph.setStartDestination(R.id.map)
navController.setGraph(navGraph, MapFragmentArgs(appStart = true).toBundle())
var deepLink: PendingIntent? = null
} else if (intent?.scheme == "geo") {
val query = intent.data?.query?.split("=")?.get(1)
val coords = getLocationFromIntent(intent)
if (intent?.scheme == "geo") {
val query = intent.data?.query?.split("=")?.get(1)
val coords = getLocationFromIntent(intent)
if (coords != null) {
val lat = coords[0]
val lon = coords[1]
deepLink = navController.createDeepLink()
.setGraph(R.navigation.nav_graph)
.setDestination(R.id.map)
.setArguments(MapFragmentArgs(latLng = LatLng(lat, lon)).toBundle())
.createPendingIntent()
} else if (!query.isNullOrEmpty()) {
deepLink = navController.createDeepLink()
.setGraph(R.navigation.nav_graph)
.setDestination(R.id.map)
.setArguments(MapFragmentArgs(locationName = query).toBundle())
.createPendingIntent()
}
} else if (intent?.scheme == "https" && intent?.data?.host == "www.goingelectric.de") {
val id = intent.data?.pathSegments?.last()?.toLongOrNull()
if (id != null) {
if (prefs.dataSource != "goingelectric") {
prefs.dataSource = "goingelectric"
Toast.makeText(
this,
getString(
R.string.data_source_switched_to,
getString(R.string.data_source_goingelectric)
),
Toast.LENGTH_LONG
).show()
}
deepLink = navController.createDeepLink()
.setGraph(R.navigation.nav_graph)
.setDestination(R.id.map)
.setArguments(MapFragmentArgs(chargerId = id).toBundle())
.createPendingIntent()
}
} else if (intent?.scheme == "https" && intent?.data?.host in listOf("openchargemap.org", "map.openchargemap.io")) {
val id = when (intent.data?.host) {
"openchargemap.org" -> intent.data?.pathSegments?.last()?.toLongOrNull()
"map.openchargemap.io" -> intent.data?.getQueryParameter("id")?.toLongOrNull()
else -> null
}
if (id != null) {
if (prefs.dataSource != "openchargemap") {
prefs.dataSource = "openchargemap"
Toast.makeText(
this,
getString(
R.string.data_source_switched_to,
getString(R.string.data_source_openchargemap)
),
Toast.LENGTH_LONG
).show()
}
deepLink = navController.createDeepLink()
.setGraph(R.navigation.nav_graph)
.setDestination(R.id.map)
.setArguments(MapFragmentArgs(chargerId = id).toBundle())
.createPendingIntent()
}
} else if (intent.scheme == "net.vonforst.evmap") {
intent.data?.let {
if (it.host == "find_charger") {
val lat = it.getQueryParameter("latitude")?.toDouble()
val lon = it.getQueryParameter("longitude")?.toDouble()
val name = it.getQueryParameter("name")
if (lat != null && lon != null) {
deepLink = navController.createDeepLink()
.setGraph(R.navigation.nav_graph)
.setDestination(R.id.map)
.setArguments(
MapFragmentArgs(
latLng = LatLng(lat, lon),
locationName = name
).toBundle()
)
.createPendingIntent()
} else if (name != null) {
deepLink = navController.createDeepLink()
.setGraph(R.navigation.nav_graph)
.setDestination(R.id.map)
.setArguments(MapFragmentArgs(locationName = name).toBundle())
.createPendingIntent()
}
}
}
} else if (intent.hasExtra(EXTRA_CHARGER_ID)) {
if (coords != null) {
val lat = coords[0]
val lon = coords[1]
deepLink = navController.createDeepLink()
.setGraph(R.navigation.nav_graph)
.setDestination(R.id.map)
.setArguments(
MapFragmentArgs(
chargerId = intent.getLongExtra(EXTRA_CHARGER_ID, 0),
latLng = LatLng(
intent.getDoubleExtra(EXTRA_LAT, 0.0),
intent.getDoubleExtra(EXTRA_LON, 0.0)
)
).toBundle()
)
.setArguments(MapFragmentArgs(latLng = LatLng(lat, lon)).toBundle())
.createPendingIntent()
} else if (intent.hasExtra(EXTRA_FAVORITES)) {
} else if (!query.isNullOrEmpty()) {
deepLink = navController.createDeepLink()
.setGraph(navGraph)
.setDestination(R.id.favs)
.createPendingIntent()
} else if (intent.hasExtra(EXTRA_DONATE)) {
deepLink = navController.createDeepLink()
.setGraph(navGraph)
.setDestination(R.id.donate)
.setGraph(R.navigation.nav_graph)
.setDestination(R.id.map)
.setArguments(MapFragmentArgs(locationName = query).toBundle())
.createPendingIntent()
}
deepLink?.send()
} else if (intent?.scheme == "https" && intent?.data?.host == "www.goingelectric.de") {
val id = intent.data?.pathSegments?.lastOrNull()?.toLongOrNull()
if (id != null) {
if (prefs.dataSource != "goingelectric") {
prefs.dataSource = "goingelectric"
Toast.makeText(
this,
getString(
R.string.data_source_switched_to,
getString(R.string.data_source_goingelectric)
),
Toast.LENGTH_LONG
).show()
}
deepLink = navController.createDeepLink()
.setGraph(R.navigation.nav_graph)
.setDestination(R.id.map)
.setArguments(MapFragmentArgs(chargerId = id).toBundle())
.createPendingIntent()
}
} else if (intent?.scheme == "https" && intent?.data?.host in listOf(
"openchargemap.org",
"map.openchargemap.io"
)
) {
val id = when (intent.data?.host) {
"openchargemap.org" -> intent.data?.pathSegments?.lastOrNull()?.toLongOrNull()
"map.openchargemap.io" -> intent.data?.getQueryParameter("id")?.toLongOrNull()
else -> null
}
if (id != null) {
if (prefs.dataSource != "openchargemap") {
prefs.dataSource = "openchargemap"
Toast.makeText(
this,
getString(
R.string.data_source_switched_to,
getString(R.string.data_source_openchargemap)
),
Toast.LENGTH_LONG
).show()
}
deepLink = navController.createDeepLink()
.setGraph(R.navigation.nav_graph)
.setDestination(R.id.map)
.setArguments(MapFragmentArgs(chargerId = id).toBundle())
.createPendingIntent()
}
} else if (intent.scheme == "net.vonforst.evmap") {
intent.data?.let {
if (it.host == "find_charger") {
val lat = it.getQueryParameter("latitude")?.toDouble()
val lon = it.getQueryParameter("longitude")?.toDouble()
val name = it.getQueryParameter("name")
if (lat != null && lon != null) {
deepLink = navController.createDeepLink()
.setGraph(R.navigation.nav_graph)
.setDestination(R.id.map)
.setArguments(
MapFragmentArgs(
latLng = LatLng(lat, lon),
locationName = name
).toBundle()
)
.createPendingIntent()
} else if (name != null) {
deepLink = navController.createDeepLink()
.setGraph(R.navigation.nav_graph)
.setDestination(R.id.map)
.setArguments(MapFragmentArgs(locationName = name).toBundle())
.createPendingIntent()
}
}
}
} else if (intent.hasExtra(EXTRA_CHARGER_ID)) {
deepLink = navController.createDeepLink()
.setDestination(R.id.map)
.setArguments(
MapFragmentArgs(
chargerId = intent.getLongExtra(EXTRA_CHARGER_ID, 0),
latLng = LatLng(
intent.getDoubleExtra(EXTRA_LAT, 0.0),
intent.getDoubleExtra(EXTRA_LON, 0.0)
)
).toBundle()
)
.createPendingIntent()
} else if (intent.hasExtra(EXTRA_FAVORITES)) {
deepLink = navController.createDeepLink()
.setGraph(navGraph)
.setDestination(R.id.favs)
.createPendingIntent()
} else if (intent.hasExtra(EXTRA_DONATE)) {
deepLink = navController.createDeepLink()
.setGraph(navGraph)
.setDestination(R.id.donate)
.createPendingIntent()
}
deepLink?.send()
}
fun navigateTo(charger: ChargeLocation) {

View File

@@ -7,8 +7,6 @@ import kotlinx.coroutines.withContext
import kotlinx.parcelize.Parcelize
import net.vonforst.evmap.addDebugInterceptors
import net.vonforst.evmap.api.RateLimitInterceptor
import net.vonforst.evmap.api.await
import net.vonforst.evmap.api.chargeprice.ChargepriceApi
import net.vonforst.evmap.api.equivalentPlugTypes
import net.vonforst.evmap.cartesianProduct
import net.vonforst.evmap.model.ChargeLocation
@@ -18,7 +16,6 @@ import net.vonforst.evmap.viewmodel.Resource
import okhttp3.Cache
import okhttp3.JavaNetCookieJar
import okhttp3.OkHttpClient
import okhttp3.Request
import retrofit2.HttpException
import java.io.IOException
import java.net.CookieManager
@@ -41,16 +38,6 @@ interface AvailabilityDetector {
abstract class BaseAvailabilityDetector(private val client: OkHttpClient) : AvailabilityDetector {
protected val radius = 150 // max radius in meters
protected suspend fun httpGet(url: String): String {
val request = Request.Builder().url(url).build()
val response = client.newCall(request).await()
if (!response.isSuccessful) throw IOException(response.message)
val str = response.body!!.string()
return str
}
protected fun getCorrespondingChargepoint(
cps: Iterable<Chargepoint>, type: String, power: Double
): Chargepoint? {

View File

@@ -52,32 +52,31 @@ class TeslaGuestAvailabilityDetector(
val (detailsA, guestPricing) = coroutineScope {
val details = async {
api.getChargingSiteDetails(
TeslaChargingGuestGraphQlApi.GetChargingSiteDetailsRequest(
TeslaChargingGuestGraphQlApi.GetChargingSiteInformationVariables(
api.getSiteDetails(
TeslaChargingGuestGraphQlApi.GetSiteDetailsRequest(
TeslaChargingGuestGraphQlApi.GetSiteDetailsVariables(
TeslaChargingGuestGraphQlApi.Identifier(
TeslaChargingGuestGraphQlApi.ChargingSiteIdentifier(
trtId
trtId, TeslaChargingGuestGraphQlApi.Experience.ADHOC
)
),
TeslaChargingGuestGraphQlApi.Experience.ADHOC
)
)
).data.site ?: throw AvailabilityDetectorException("no candidates found.")
).data.chargingNetwork?.site
?: throw AvailabilityDetectorException("no candidates found.")
}
val guestPricing = async {
api.getChargingSiteDetails(
TeslaChargingGuestGraphQlApi.GetChargingSiteDetailsRequest(
TeslaChargingGuestGraphQlApi.GetChargingSiteInformationVariables(
api.getSiteDetails(
TeslaChargingGuestGraphQlApi.GetSiteDetailsRequest(
TeslaChargingGuestGraphQlApi.GetSiteDetailsVariables(
TeslaChargingGuestGraphQlApi.Identifier(
TeslaChargingGuestGraphQlApi.ChargingSiteIdentifier(
trtId
trtId, TeslaChargingGuestGraphQlApi.Experience.GUEST
)
),
TeslaChargingGuestGraphQlApi.Experience.GUEST
)
)
).data.site?.pricing
).data.chargingNetwork?.site?.pricing
}
details to guestPricing
}
@@ -103,12 +102,9 @@ class TeslaGuestAvailabilityDetector(
"charger has unknown connectors"
)
val chargerDetails = details.chargersAvailable.chargerDetails
val chargers = details.chargers.associateBy { it.id }
var detailsSorted = chargerDetails
.sortedBy { chargers[it.id]?.labelLetter }
.sortedBy { chargers[it.id]?.labelNumber }
var detailsSorted = details.chargerList
.sortedBy { c -> c.labelLetter }
.sortedBy { c -> c.labelNumber }
if (detailsSorted.size != scV2Connectors.sumOf { it.count } + scV3Connectors.sumOf { it.count }) {
// apparently some connectors are missing in Tesla data
@@ -120,7 +116,7 @@ class TeslaGuestAvailabilityDetector(
detailsSorted + List(numMissing) {
TeslaChargingGuestGraphQlApi.ChargerDetail(
ChargerAvailability.UNKNOWN,
""
"", ""
)
}
} else {
@@ -151,7 +147,7 @@ class TeslaGuestAvailabilityDetector(
}
val statusMap = detailsMap.mapValues { it.value.map { it.availability.toStatus() } }
val labelsMap = detailsMap.mapValues { it.value.map { chargers[it.id]?.label } }
val labelsMap = detailsMap.mapValues { it.value.map { it.label } }
val pricing = details.pricing.copy(memberRates = guestPricing.await()?.userRates)

View File

@@ -1,12 +1,8 @@
package net.vonforst.evmap.api.availability.tesla
import com.squareup.moshi.Json
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.JsonClass
import com.squareup.moshi.JsonReader
import com.squareup.moshi.JsonWriter
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import okhttp3.CacheControl
import okhttp3.OkHttpClient
import retrofit2.Retrofit
@@ -15,7 +11,6 @@ import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.POST
import retrofit2.http.Query
import java.lang.reflect.Type
import java.util.concurrent.TimeUnit
interface TeslaCuaApi {
@@ -71,24 +66,22 @@ interface TeslaCuaApi {
interface TeslaChargingGuestGraphQlApi {
@POST("graphql")
suspend fun getChargingSiteDetails(
@Body request: GetChargingSiteDetailsRequest,
@Query("operationName") operationName: String = "getGuestChargingSiteDetails"
suspend fun getSiteDetails(
@Body request: GetSiteDetailsRequest,
@Query("operationName") operationName: String = "GetSiteDetails"
): GetChargingSiteDetailsResponse
@JsonClass(generateAdapter = true)
data class GetChargingSiteDetailsRequest(
override val variables: GetChargingSiteInformationVariables,
override val operationName: String = "getGuestChargingSiteDetails",
data class GetSiteDetailsRequest(
override val variables: GetSiteDetailsVariables,
override val operationName: String = "GetSiteDetails",
override val query: String =
"\n query getGuestChargingSiteDetails(\$identifier: ChargingSiteIdentifierInput!, \$deviceLocale: String!, \$experience: ChargingExperienceEnum!) {\n site(\n identifier: \$identifier\n deviceLocale: \$deviceLocale\n experience: \$experience\n ) {\n activeOutages\n address {\n countryCode\n }\n chargers {\n id\n label\n }\n chargersAvailable {\n chargerDetails {\n id\n availability\n }\n }\n holdAmount {\n holdAmount\n currencyCode\n }\n maxPowerKw\n name\n programType\n publicStallCount\n id\n pricing(experience: \$experience) {\n userRates {\n activePricebook {\n charging {\n uom\n rates\n buckets {\n start\n end\n }\n bucketUom\n currencyCode\n programType\n vehicleMakeType\n touRates {\n enabled\n activeRatesByTime {\n startTime\n endTime\n rates\n }\n }\n }\n parking {\n uom\n rates\n buckets {\n start\n end\n }\n bucketUom\n currencyCode\n programType\n vehicleMakeType\n touRates {\n enabled\n activeRatesByTime {\n startTime\n endTime\n rates\n }\n }\n }\n congestion {\n uom\n rates\n buckets {\n start\n end\n }\n bucketUom\n currencyCode\n programType\n vehicleMakeType\n touRates {\n enabled\n activeRatesByTime {\n startTime\n endTime\n rates\n }\n }\n }\n }\n }\n }\n }\n}\n "
"\n query GetSiteDetails(\$siteId: SiteIdInput!) {\n chargingNetwork {\n site(siteId: \$siteId) {\n address {\n countryCode\n }\n chargerList {\n id\n label\n availability\n }\n holdAmount {\n amount\n currencyCode\n }\n maxPowerKw\n name\n programType\n publicStallCount\n trtId\n pricing {\n userRates {\n activePricebook {\n charging {\n ...ChargingRate\n }\n parking {\n ...ChargingRate\n }\n congestion {\n ...ChargingRate\n }\n }\n }\n }\n }\n }\n}\n \n fragment ChargingRate on ChargingUserRate {\n uom\n rates\n buckets {\n start\n end\n }\n bucketUom\n currencyCode\n programType\n vehicleMakeType\n touRates {\n enabled\n activeRatesByTime {\n startTime\n endTime\n rates\n }\n }\n}\n "
) : GraphQlRequest()
@JsonClass(generateAdapter = true)
data class GetChargingSiteInformationVariables(
val identifier: Identifier,
val experience: Experience,
val deviceLocale: String = "de-DE",
data class GetSiteDetailsVariables(
val siteId: Identifier,
)
enum class Experience {
@@ -97,22 +90,22 @@ interface TeslaChargingGuestGraphQlApi {
@JsonClass(generateAdapter = true)
data class Identifier(
val siteId: ChargingSiteIdentifier
val byTrtId: ChargingSiteIdentifier
)
@JsonClass(generateAdapter = true)
data class ChargingSiteIdentifier(
val id: Long,
val siteType: SiteType = SiteType.SUPERCHARGER
val trtId: Long,
val chargingExperience: Experience,
val programType: String = "PTSCH",
val locale: String = "de-DE",
)
enum class SiteType {
@Json(name = "SITE_TYPE_SUPERCHARGER")
SUPERCHARGER
}
@JsonClass(generateAdapter = true)
data class GetChargingSiteDetailsResponse(val data: GetChargingSiteDetailsResponseDataNetwork)
@JsonClass(generateAdapter = true)
data class GetChargingSiteDetailsResponse(val data: GetChargingSiteDetailsResponseData)
data class GetChargingSiteDetailsResponseDataNetwork(val chargingNetwork: GetChargingSiteDetailsResponseData?)
@JsonClass(generateAdapter = true)
data class GetChargingSiteDetailsResponseData(val site: ChargingSiteInformation?)
@@ -120,9 +113,8 @@ interface TeslaChargingGuestGraphQlApi {
@JsonClass(generateAdapter = true)
data class ChargingSiteInformation(
val activeOutages: List<Outage>?,
val chargers: List<ChargerId>,
val chargersAvailable: ChargersAvailable,
val id: Long,
val chargerList: List<ChargerDetail>,
val trtId: Long,
val maxPowerKw: Int,
val name: String,
val pricing: Pricing,
@@ -130,9 +122,10 @@ interface TeslaChargingGuestGraphQlApi {
)
@JsonClass(generateAdapter = true)
data class ChargerId(
val id: String,
data class ChargerDetail(
val availability: ChargerAvailability,
val label: String?,
val id: String
) {
val labelNumber
get() = label?.replace(Regex("""\D"""), "")?.toInt()
@@ -140,15 +133,6 @@ interface TeslaChargingGuestGraphQlApi {
get() = label?.replace(Regex("""\d"""), "")
}
@JsonClass(generateAdapter = true)
data class ChargersAvailable(val chargerDetails: List<ChargerDetail>)
@JsonClass(generateAdapter = true)
data class ChargerDetail(
val availability: ChargerAvailability,
val id: String
)
companion object {
fun create(
client: OkHttpClient,

View File

@@ -452,7 +452,10 @@ class GoingElectricApiWrapper(
if (responses.map { it.isSuccessful }.all { it }
&& plugsResponse.body()!!.status == STATUS_OK
&& chargeCardsResponse.body()!!.status == STATUS_OK
&& networksResponse.body()!!.status == STATUS_OK) {
&& networksResponse.body()!!.status == STATUS_OK
&& plugsResponse.body()!!.result != null
&& chargeCardsResponse.body()!!.result != null
&& networksResponse.body()!!.result != null) {
Resource.success(
GEReferenceData(
plugsResponse.body()!!.result!!,

View File

@@ -254,7 +254,7 @@ data class OCMUserComment(
@Json(name = "ID") val id: Long,
@Json(name = "CommentTypeID") val commentTypeId: Long,
@Json(name = "Comment") val comment: String?,
@Json(name = "UserName") val userName: String,
@Json(name = "UserName") val userName: String?,
@Json(name = "DateCreated") val dateCreated: ZonedDateTime
)

View File

@@ -11,6 +11,7 @@ import android.net.Uri
import android.text.SpannableString
import android.text.SpannableStringBuilder
import android.text.Spanned
import android.util.Log
import androidx.car.app.CarContext
import androidx.car.app.CarToast
import androidx.car.app.HostException
@@ -57,6 +58,7 @@ import net.vonforst.evmap.api.iconForPlugType
import net.vonforst.evmap.api.nameForPlugType
import net.vonforst.evmap.api.stringProvider
import net.vonforst.evmap.model.ChargeLocation
import net.vonforst.evmap.model.Coordinate
import net.vonforst.evmap.model.Cost
import net.vonforst.evmap.model.FaultReport
import net.vonforst.evmap.model.Favorite
@@ -75,6 +77,7 @@ import java.time.format.FormatStyle
import kotlin.math.floor
import kotlin.math.roundToInt
private const val TAG = "ChargerDetailScreen"
class ChargerDetailScreen(ctx: CarContext, val chargerSparse: ChargeLocation) : Screen(ctx) {
var charger: ChargeLocation? = null
@@ -544,6 +547,14 @@ class ChargerDetailScreen(ctx: CarContext, val chargerSparse: ChargeLocation) :
}
private fun navigateToCharger(charger: ChargeLocation) {
val success = navigateCarApp(charger)
if (!success && BuildConfig.FLAVOR_automotive == "automotive") {
// on AAOS, some OEMs' navigation apps might not support
navigateRegularApp(charger)
}
}
private fun navigateCarApp(charger: ChargeLocation): Boolean {
val coord = charger.coordinates
val intent =
Intent(
@@ -552,11 +563,35 @@ class ChargerDetailScreen(ctx: CarContext, val chargerSparse: ChargeLocation) :
)
try {
carContext.startCarApp(intent)
return true
} catch (e: HostException) {
CarToast.makeText(carContext, R.string.no_maps_app_found, CarToast.LENGTH_SHORT).show()
} catch (ignored: SecurityException) {
Log.w(TAG, "Could not start navigation using car app intent")
Log.w(TAG, intent.toString())
e.printStackTrace()
} catch (e: SecurityException) {
Log.w(TAG, "Could not start navigation using car app intent")
Log.w(TAG, intent.toString())
e.printStackTrace()
}
return false
}
private fun navigateRegularApp(charger: ChargeLocation): Boolean {
val coord = charger.coordinates
val intent = Intent(Intent.ACTION_VIEW)
intent.data = Uri.parse(
"geo:${coord.lat},${coord.lng}?q=${coord.lat},${coord.lng}(${
Uri.encode(charger.name)
})"
)
if (intent.resolveActivity(carContext.packageManager) != null) {
carContext.startActivity(intent)
return true
} else {
Log.w(TAG, "Could not start navigation using regular intent")
Log.w(TAG, intent.toString())
}
return false
}
private fun loadCharger() {

View File

@@ -16,6 +16,7 @@ import com.mapbox.api.geocoding.v5.models.CarmenFeature
import com.mapbox.geojson.BoundingBox
import com.mapbox.geojson.Point
import net.vonforst.evmap.R
import retrofit2.HttpException
import java.io.IOException
class MapboxAutocompleteProvider(val context: Context) : AutocompleteProvider {
@@ -25,7 +26,7 @@ class MapboxAutocompleteProvider(val context: Context) : AutocompleteProvider {
override val id = "mapbox"
override fun autocomplete(query: String, location: LatLng?): List<AutocompletePlace> {
val result = MapboxGeocoding.builder().apply {
val request = MapboxGeocoding.builder().apply {
location?.let {
proximity(Point.fromLngLat(location.longitude, location.latitude))
}
@@ -33,7 +34,12 @@ class MapboxAutocompleteProvider(val context: Context) : AutocompleteProvider {
accessToken(context.getString(R.string.mapbox_key))
autocomplete(true)
this.query(query)
}.build().executeCall()
}
val result = try {
request.build().executeCall()
} catch (e: HttpException) {
throw IOException(e)
}
if (!result.isSuccessful) {
throw IOException(result.message())
}

View File

@@ -297,6 +297,10 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
get() = resources.getBoolean(R.bool.bottom_sheet_collapsible)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
if (!prefs.welcomeDialogShown || !prefs.dataSourceSet || !prefs.privacyAccepted) {
findNavController().navigate(R.id.onboarding)
}
requireActivity().addMenuProvider(this, viewLifecycleOwner, Lifecycle.State.RESUMED)
mapFragment!!.getMapAsync(this)
@@ -1051,6 +1055,8 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
this.map = map
vm.mapProjection = map.projection
val context = this.context ?: return
view ?: return
chargerIconGenerator = ChargerIconGenerator(context, map.bitmapDescriptorFactory)
vm.mapTrafficSupported.value =

View File

@@ -2,6 +2,7 @@
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
app:startDestination="@id/map"
android:id="@+id/nav_graph">
<navigation

View File

@@ -10,7 +10,7 @@ buildscript {
gradlePluginPortal()
}
dependencies {
classpath("com.android.tools.build:gradle:8.4.1")
classpath("com.android.tools.build:gradle:8.4.2")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion")
classpath("com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin:$aboutLibsVersion")
classpath("androidx.navigation:navigation-safe-args-gradle-plugin:$navVersion")
@@ -24,7 +24,6 @@ allprojects {
repositories {
google()
mavenCentral()
//noinspection JcenterRepositoryObsolete
maven { setUrl("https://jitpack.io") }
}
}

View File

@@ -101,9 +101,9 @@ to switch between the two, while the `foss` flavor only includes OSM.
### ArcGIS
[World Imagery basemap](https://www.arcgis.com/home/item.html?id=10df2279f9684e4a9f6a7f08febac2a9)
*We use this for the satellite map, as [Jawg's](https://blog.jawg.io/satellite-imaging/) satellite
style does not have global coverage.*
[World Imagery basemap](https://www.arcgis.com/home/item.html?id=10df2279f9684e4a9f6a7f08febac2a9)\
*We use this for the satellite map, as [Jawg's satellite
style](https://blog.jawg.io/satellite-imaging/) does not have global coverage.*
<details>
<summary>How to obtain an API key</summary>
@@ -116,7 +116,7 @@ style does not have global coverage.*
### Mapbox
[Geocoding API](https://docs.mapbox.com/api/search/geocoding/)
[Geocoding API](https://docs.mapbox.com/api/search/geocoding/)\
*previously we also used Mapbox's Maps SDK, but this has now been switched to Jawg Maps.*
<details>

View File

@@ -0,0 +1,2 @@
Fehler behoben:
- Abstürze behoben

View File

@@ -0,0 +1,2 @@
Fehler behoben:
- Abstürze behoben

View File

@@ -0,0 +1,2 @@
Fehler behoben:
- Abstürze behoben

View File

@@ -0,0 +1,2 @@
Bugfixes:
- Fixed crashes

View File

@@ -0,0 +1,2 @@
Bugfixes:
- Fixed crashes

View File

@@ -0,0 +1,2 @@
Bugfixes:
- Fixed crashes