Compare commits

...

37 Commits
1.3.5 ... 1.3.7

Author SHA1 Message Date
johan12345
4a82250a3d Release 1.3.7 2022-08-15 21:37:43 +02:00
johan12345
a8f23e9fb6 Android Auto/Automotive: Refresh data after relaunching app from background
fixes #207
2022-08-15 21:25:20 +02:00
johan12345
7da64fd566 after first start, remove onboarding from back stack
fixes #202
2022-08-15 20:56:28 +02:00
johan12345
09b5d536cb keep ChargerDetails in saved state
fixes #205
2022-08-15 20:53:22 +02:00
johan12345
5e01200d96 Chargeprice: remove errorneous tint from provider logo in dark mode
fixes #206
2022-08-15 18:54:05 +02:00
johan12345
c8d2e73218 BindingAdapters.colorToTransparent(): fix when values become negative
fixes #206
2022-08-15 18:52:13 +02:00
johan12345
d7b377ea56 disable mini markers completely when filtered by HPCs 2022-08-15 18:38:18 +02:00
johan12345
edd35fba1b suppress lint again 2022-08-13 16:29:18 +02:00
johan12345
1f23080141 suppress lint 2022-08-13 16:17:29 +02:00
johan12345
a3d9ecf49e fix another lint warning 2022-08-13 15:01:39 +02:00
johan12345
6681d3cc17 Android Auto: don't exit app when canceling vehicle data permission 2022-08-13 14:59:21 +02:00
johan12345
a184b817bc Android Auto: fix alignment of +/- buttons for Chargeprice range selection 2022-08-13 14:43:21 +02:00
johan12345
b658e0183c fix lint error 2022-08-13 14:38:11 +02:00
johan12345
6a0234ac2f use proper app icon for persistent Android Auto notification
fixes #201
2022-08-13 14:15:19 +02:00
johan12345
d5ac35100b use new Activity Result API for requesting permissions 2022-08-13 13:08:55 +02:00
johan12345
d3b4cb6a90 update AnyMaps 2022-08-13 13:08:55 +02:00
johan12345
5d70d8c09a replace deprecated override in NavHostFragment 2022-08-13 13:08:55 +02:00
johan12345
9642a58206 implement new MenuProvider API
to avoid deprecated functions
2022-08-13 13:08:55 +02:00
johan12345
0e3280a119 upgrade Kotlin to 1.7.10 2022-08-13 13:08:55 +02:00
johan12345
c60043f925 disable AndroidX Jetifier
which is now not needed anymore
2022-08-13 13:08:55 +02:00
johan12345
b445be99bb remove dependency on unmaintained Lost library
use Android's own Location APIs instead
2022-08-13 13:08:55 +02:00
johan12345
02395dda7f upgrade libraries 2022-08-13 13:08:55 +02:00
johan12345
c33c69db0b update Android Gradle Plugin 2022-08-13 13:08:55 +02:00
Johan von Forstner
77fdfc7ccb Update comparison of product flavors in README 2022-08-10 18:20:45 +02:00
johan12345
bbb5c93132 Release 1.3.6 2022-08-10 17:51:33 +02:00
johan12345
2e8cdb01fd Revert upgrade of Car App library 2022-08-10 17:35:48 +02:00
johan12345
6b6c7da081 Revert "Android Auto: move search button from filter screen back to map"
This reverts commit 9f0c5caf31.
2022-08-10 17:34:42 +02:00
johan12345
720d52285d Android Auto: use locationManager.getBestProvider 2022-08-07 22:07:59 +02:00
johan12345
e7efda2e90 Android Auto: request fine location permission 2022-08-07 21:29:24 +02:00
johan12345
ed80d7b968 Replace mapbox-events-android, removing compile-time GMS dependency
patched version at https://github.com/johan12345/mapbox-events-android
2022-08-07 19:52:45 +02:00
johan12345
8b1b971fad use PASSIVE_PROVIDER to get last known location faster 2022-08-07 13:51:18 +02:00
johan12345
cf20ab8d82 use LocationListenerCompat to fix crash on Android API < 30 2022-08-07 13:31:41 +02:00
johan12345
581d0c07ec increase version code 2022-08-06 16:47:19 +02:00
johan12345
0b17821611 AA: avoid crash when place search cannot load results 2022-08-06 16:46:33 +02:00
johan12345
2493328715 update AnyMaps
fixes crash due to deprecated setRetainInstance(true)
2022-08-06 16:35:54 +02:00
johan12345
f8abeed96c let Android Studio update gradle-wrapper.properties 2022-08-06 15:34:12 +02:00
johan12345
d9ca21c31e update gradle wrapper JAR 2022-08-06 10:17:55 +02:00
42 changed files with 491 additions and 359 deletions

View File

@@ -20,7 +20,7 @@ Features
- Advanced filtering options, including saved filter profiles
- Favorites list, also with availability information
- Integrated price comparison using [Chargeprice.app](https://chargeprice.app) (only in Europe)
- Android Auto integration
- Android Auto & Android Automotive OS integration
- No ads, fully open source
- Compatible with Android 5.0 and above
- Can use Google Maps or Mapbox (OpenStreetMap) as map backends - the version available on F-Droid only uses Mapbox.
@@ -41,9 +41,13 @@ EVMap uses and put them into the app in the form of a resource file called `apik
`app/src/main/res/values`. You can find more information on which API keys are necessary for which
features and how they can be obtained in our [documentation page](doc/api_keys.md).
There are two different build flavors, `google` and `foss`, where only the `google` variant uses
Google Maps data and provides the Android Auto integration. The `foss` variant only uses Mapbox data
and should run on devices without Google Play Services.
There are three different build flavors, `googleNormal`, `fossNormal` and `googleAutomotive`.
- The `foss` variant only uses Mapbox data and should run on most Android devices, even without Google Play Services.
- The `google` variants also include access to Google Maps data.
- `googleNormal` is intended to run on smartphones and tablets, and also includes the Android Auto app for use
on the car display.
- `googleAutomotive` variant is intended to be installed directly on car infotainment systems using the
Google-flavored Android Automotive OS. It does not provide the usual smartphone UI.
We also have a special [documentation page](doc/android_auto.md) on how to test the Android Auto
app.

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?><!-- Generator: Adobe Illustrator 24.3.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"
viewBox="0 0 120 120" style="enable-background:new 0 0 120 120;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
</style>
<g>
<g>
<path class="st0"
d="M27.1,88.3l-2.2-19.2l-3.3,0.3l2.2,19.2L27.1,88.3z M39,86.9l-2.2-19.2l-3.3,0.3l2.2,19.2L39,86.9z" />
<path class="st0" d="M45.2,113c-1,1.3-1.8,2.1-2,2.2c-3,2.4-5.4,3.1-7.4,2.2c-3.5-1.7-3.2-8.2-3.1-8.9l2.4,0.1
c-0.1,1.8,0.2,5.8,1.8,6.6c0.9,0.5,2.5-0.1,4.6-1.8l0,0c0,0,6.7-6.7,5.3-12c-1.6-6.4,5.8-15.5,8.2-18.6l0.3-0.3l2,1.5l-0.3,0.5
c-7.5,9.2-8.3,14-7.7,16.4C50.5,105.4,47.4,110.4,45.2,113z" />
<path class="st0" d="M19.7,88.1l0.9,7.9l7.3,4.9l9.8-1l6-6.4l-0.9-7.9L19.7,88.1z" />
<g>
<path class="st0"
d="M37.6,99.7l-9.8,1l2.1,8.7l7.7-0.9V99.7L37.6,99.7z M44.6,79l0.8,7.2l-28.2,3.2l-0.8-7.2L44.6,79z" />
</g>
</g>
<path class="st0" d="M66.7,0C46.5,0,30.1,16.4,30.1,36.6c0,27.6,30.8,42,34.5,81.4c0.1,1.2,1,2,2.2,2c1.2,0,2.1-0.8,2.2-2
c3.7-39.4,34.5-53.8,34.5-81.4C103.3,16.2,86.9,0,66.7,0z M78.4,34.7L64.3,59V40.8h-6V18.7c0,0,20.2,0,20.1-0.1l-8.1,16.2H78.4z" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -19,8 +19,8 @@ android {
minSdkVersion 21
targetSdkVersion 31
// NOTE: always increase versionCode by 2 since automotive flavor uses versionCode + 1
versionCode 92
versionName "1.3.5"
versionCode 98
versionName "1.3.7"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
@@ -173,19 +173,18 @@ dependencies {
implementation "com.mikepenz:aboutlibraries:$about_libs_version"
implementation 'com.airbnb.android:lottie:4.1.0'
implementation 'io.michaelrocks.bimap:bimap:1.1.0'
implementation 'com.mapzen.android:lost:3.0.2'
implementation 'com.google.guava:guava:29.0-android'
implementation 'com.github.pengrad:mapscaleview:1.6.0'
implementation 'com.github.romandanylyk:PageIndicatorView:b1bad589b5'
// Android Auto
def carAppVersion = '1.3.0-alpha01'
def carAppVersion = '1.2.0-rc01'
googleImplementation "androidx.car.app:app:$carAppVersion"
googleNormalImplementation "androidx.car.app:app-projected:$carAppVersion"
googleAutomotiveImplementation "androidx.car.app:app-automotive:$carAppVersion"
// AnyMaps
def anyMapsVersion = '3c67d7a1dc'
def anyMapsVersion = 'f36bb3c126'
implementation "com.github.johan12345.AnyMaps:anymaps-base:$anyMapsVersion"
googleImplementation "com.github.johan12345.AnyMaps:anymaps-google:$anyMapsVersion"
googleImplementation 'com.google.android.gms:play-services-maps:18.1.0'
@@ -193,11 +192,14 @@ dependencies {
exclude group: 'com.mapbox.mapboxsdk', module: 'mapbox-android-accounts'
exclude group: 'com.mapbox.mapboxsdk', module: 'mapbox-android-telemetry'
exclude group: 'com.google.android.gms', module: 'play-services-location'
exclude group: 'com.mapbox.mapboxsdk', module: 'mapbox-android-core'
}
// patched version of mapbox-android-core that removes build-time dependency on GMS
implementation 'com.github.johan12345:mapbox-events-android:a21c324501'
// Google Places
googleImplementation 'com.google.android.libraries.places:places:2.6.0'
googleImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.4.1'
googleImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.6.1'
// Mapbox Geocoding
implementation 'com.mapbox.mapboxsdk:mapbox-sdk-services:5.5.0'
@@ -229,8 +231,8 @@ dependencies {
implementation("ch.acra:acra-limiter:$acraVersion")
// debug tools
implementation 'com.facebook.stetho:stetho:1.5.1'
implementation 'com.facebook.stetho:stetho-okhttp3:1.5.1'
implementation 'com.facebook.stetho:stetho:1.6.0'
implementation 'com.facebook.stetho:stetho-okhttp3:1.6.0'
// testing
testImplementation 'junit:junit:4.13.2'
@@ -240,7 +242,7 @@ dependencies {
// testing for car app
testGoogleImplementation "androidx.car.app:app-testing:$carAppVersion"
testGoogleImplementation 'org.robolectric:robolectric:4.7.3'
testGoogleImplementation 'org.robolectric:robolectric:4.8.1'
testGoogleImplementation 'androidx.test:core:1.4.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'

View File

@@ -8,6 +8,7 @@ import android.app.NotificationManager
import android.content.Context
import android.content.Intent
import android.content.pm.ApplicationInfo
import android.location.Criteria
import android.location.Location
import android.location.LocationManager
import android.os.Build
@@ -26,10 +27,11 @@ import androidx.car.app.validation.HostValidator
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat
import androidx.core.location.LocationListenerCompat
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import net.vonforst.evmap.R
import net.vonforst.evmap.utils.checkAnyLocationPermission
import net.vonforst.evmap.utils.checkFineLocationPermission
interface LocationAwareScreen {
@@ -69,7 +71,8 @@ class CarAppService : androidx.car.app.CarAppService() {
.setContentTitle(getString(R.string.app_name))
.setOngoing(true)
.setPriority(NotificationManagerCompat.IMPORTANCE_HIGH)
.setSmallIcon(R.mipmap.ic_launcher)
.setSmallIcon(R.drawable.ic_appicon_notification)
.setColor(ContextCompat.getColor(this, R.color.colorPrimary))
.setTicker(getString(R.string.auto_location_service))
.setWhen(System.currentTimeMillis())
@@ -131,7 +134,7 @@ class EVMapSession(val cas: CarAppService) : Session(), DefaultLifecycleObserver
return mapScreen
}
fun locationPermissionGranted() = carContext.checkAnyLocationPermission()
private fun locationPermissionGranted() = carContext.checkFineLocationPermission()
private fun updateLocation(location: Location?) {
Log.d(TAG, "Received location: $location")
@@ -158,6 +161,7 @@ class EVMapSession(val cas: CarAppService) : Session(), DefaultLifecycleObserver
requestPhoneLocationUpdates()
}
@RequiresPermission(anyOf = [Manifest.permission.ACCESS_FINE_LOCATION])
private fun requestCarHardwareLocationUpdates() {
if (supportsCarApiLevel3(carContext)) {
val exec = ContextCompat.getMainExecutor(carContext)
@@ -169,15 +173,23 @@ class EVMapSession(val cas: CarAppService) : Session(), DefaultLifecycleObserver
}
}
private val phoneLocationListener = LocationListenerCompat {
this.updateLocation(it)
}
@RequiresPermission(anyOf = [Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION])
private fun requestPhoneLocationUpdates() {
val location = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER)
val provider = locationManager.getBestProvider(Criteria().apply {
accuracy = Criteria.ACCURACY_FINE
}, true) ?: return
val location = locationManager.getLastKnownLocation(provider)
updateLocation(location)
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER,
provider,
1000,
1f,
this::updateLocation
phoneLocationListener
)
}
@@ -197,7 +209,7 @@ class EVMapSession(val cas: CarAppService) : Session(), DefaultLifecycleObserver
@RequiresPermission(anyOf = [Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION])
private fun removePhoneLocationUpdates() {
locationManager.removeUpdates(this::updateLocation)
locationManager.removeUpdates(phoneLocationListener)
}
@SuppressLint("MissingPermission")

View File

@@ -47,6 +47,30 @@ class FilterScreen(ctx: CarContext, val session: EVMapSession) : Screen(ctx) {
setHeaderAction(Action.BACK)
setActionStrip(
ActionStrip.Builder().apply {
addAction(Action.Builder().apply {
setIcon(
CarIcon.Builder(
IconCompat.createWithResource(
carContext,
if (prefs.placeSearchResultAndroidAuto != null) {
R.drawable.ic_search_off
} else {
R.drawable.ic_search
}
)
).build()
)
setOnClickListener(ParkedOnlyOnClickListener.create {
if (prefs.placeSearchResultAndroidAuto != null) {
prefs.placeSearchResultAndroidAutoName = null
prefs.placeSearchResultAndroidAuto = null
screenManager.pop()
} else {
screenManager.push(PlaceSearchScreen(carContext, session))
}
})
}.build())
addAction(Action.Builder().apply {
setIcon(
CarIcon.Builder(

View File

@@ -102,9 +102,7 @@ class MapScreen(ctx: CarContext, val session: EVMapSession) :
private var searchLocation: LatLng? = null
init {
filtersWithValue.observe(this) {
loadChargers()
}
lifecycle.addObserver(this)
marker = MARKER
}
@@ -178,42 +176,6 @@ class MapScreen(ctx: CarContext, val session: EVMapSession) :
session.mapScreen = null
}
.build())
.addAction(Action.Builder().apply {
setIcon(
CarIcon.Builder(
IconCompat.createWithResource(
carContext,
if (prefs.placeSearchResultAndroidAuto != null) {
R.drawable.ic_search_off
} else {
R.drawable.ic_search
}
)
).build()
)
setOnClickListener(ParkedOnlyOnClickListener.create {
if (prefs.placeSearchResultAndroidAuto != null) {
prefs.placeSearchResultAndroidAutoName = null
prefs.placeSearchResultAndroidAuto = null
screenManager.pushForResult(DummyReturnScreen(carContext)) {
chargers = null
loadChargers()
}
} else {
screenManager.pushForResult(
PlaceSearchScreen(
carContext,
session
)
) {
chargers = null
loadChargers()
}
session.mapScreen = null
}
})
}.build())
.addAction(
Action.Builder()
.setIcon(
@@ -228,7 +190,6 @@ class MapScreen(ctx: CarContext, val session: EVMapSession) :
)
.setOnClickListener {
screenManager.pushForResult(FilterScreen(carContext, session)) {
chargers = null
filterStatus.value = prefs.filterStatus
}
session.mapScreen = null
@@ -316,13 +277,8 @@ class MapScreen(ctx: CarContext, val session: EVMapSession) :
)
setOnClickListener {
screenManager.pushForResult(ChargerDetailScreen(carContext, charger)) {
if (filterStatus.value == FILTERS_FAVORITES) {
// favorites list may have been updated
chargers = null
loadChargers()
}
}
screenManager.push(ChargerDetailScreen(carContext, charger))
session.mapScreen = null
}
}.build()
}
@@ -412,8 +368,17 @@ class MapScreen(ctx: CarContext, val session: EVMapSession) :
if (isUpdate) invalidate()
}
override fun onResume(owner: LifecycleOwner) {
override fun onStart(owner: LifecycleOwner) {
setupListeners()
// Reloading chargers in onStart does not seem to count towards content limit.
// So let's do this so the user gets fresh chargers when re-entering the app.
chargers = null
availabilities.clear()
invalidate()
filtersWithValue.observe(this) {
loadChargers()
}
}
private fun setupListeners() {
@@ -432,7 +397,7 @@ class MapScreen(ctx: CarContext, val session: EVMapSession) :
}
}
override fun onPause(owner: LifecycleOwner) {
override fun onStop(owner: LifecycleOwner) {
removeListeners()
}

View File

@@ -12,7 +12,8 @@ import net.vonforst.evmap.R
class PermissionScreen(
ctx: CarContext,
@StringRes val message: Int,
val permissions: List<String>
val permissions: List<String>,
val finishApp: Boolean = true
) : Screen(ctx) {
override fun onGetTemplate(): Template {
return MessageTemplate.Builder(carContext.getString(message))
@@ -31,7 +32,13 @@ class PermissionScreen(
Action.Builder()
.setTitle(carContext.getString(R.string.cancel))
.setOnClickListener {
carContext.finishCarApp()
if (finishApp) {
carContext.finishCarApp()
} else {
// pop twice to get away from the screen that requires the permission
screenManager.pop()
screenManager.pop()
}
}
.build(),
)

View File

@@ -5,6 +5,7 @@ import android.location.Location
import android.text.SpannableStringBuilder
import android.text.Spanned
import androidx.car.app.CarContext
import androidx.car.app.CarToast
import androidx.car.app.Screen
import androidx.car.app.annotations.ExperimentalCarApi
import androidx.car.app.constraints.ConstraintManager
@@ -26,6 +27,7 @@ import net.vonforst.evmap.autocomplete.*
import net.vonforst.evmap.storage.AppDatabase
import net.vonforst.evmap.storage.PreferenceDataSource
import net.vonforst.evmap.storage.RecentAutocompletePlace
import java.io.IOException
import java.time.Instant
@ExperimentalCarApi
@@ -133,7 +135,15 @@ class PlaceSearchScreen(ctx: CarContext, val session: EVMapSession) : Screen(ctx
if (prefs.searchProvider == "mapbox" && !isShortQuery(searchText)) {
delay(500L)
}
loadNewList(searchText)
try {
loadNewList(searchText)
} catch (e: IOException) {
CarToast.makeText(
carContext,
R.string.autocomplete_connection_error,
CarToast.LENGTH_SHORT
).show()
}
}
}

View File

@@ -442,6 +442,7 @@ class SelectChargingRangeScreen(ctx: CarContext) : Screen(ctx) {
val nSpacers = when {
maxItems % 3 == 0 -> 1
maxItems == 100 -> 0 // AA has increased the limit to 100 and changed the way items are laid out
maxItems % 4 == 0 -> 2
else -> 0
}

View File

@@ -3,13 +3,13 @@ package net.vonforst.evmap.auto
import android.content.Context
import android.graphics.Bitmap
import androidx.car.app.CarContext
import androidx.car.app.Screen
import androidx.car.app.constraints.ConstraintManager
import androidx.car.app.hardware.common.CarUnit
import androidx.car.app.model.*
import androidx.car.app.model.CarColor
import androidx.car.app.model.CarIcon
import androidx.car.app.model.Distance
import androidx.car.app.versioning.CarAppApiLevels
import androidx.core.graphics.drawable.IconCompat
import net.vonforst.evmap.R
import net.vonforst.evmap.api.availability.ChargepointStatus
import java.util.*
import kotlin.math.roundToInt
@@ -152,17 +152,4 @@ fun supportsCarApiLevel3(ctx: CarContext): Boolean {
}
}
return true
}
class DummyReturnScreen(ctx: CarContext) : Screen(ctx) {
/*
Dummy screen to get around template refresh limitations.
It immediately pops back to the previous screen.
*/
override fun onGetTemplate(): Template {
screenManager.pop()
return MessageTemplate.Builder(carContext.getString(R.string.loading)).setLoading(true)
.build()
}
}

View File

@@ -58,7 +58,8 @@ class VehicleDataScreen(ctx: CarContext) : Screen(ctx), DefaultLifecycleObserver
PermissionScreen(
carContext,
R.string.auto_vehicle_data_permission_needed,
permissions
permissions,
finishApp = false
)
) {
setupListeners()

View File

@@ -40,5 +40,4 @@
<string name="data_sources_hint">In den Einstellungen kannst du auch zwischen Google Maps und OpenStreetMap (Mapbox) für die Kartendaten wechseln.</string>
<string name="selecting_all">alle Einträge ausgewählt</string>
<string name="selecting_none">alle Einträge abgewählt</string>
<string name="loading">Lade…</string>
</resources>

View File

@@ -50,5 +50,4 @@
<string name="data_sources_hint">In the settings you can also switch between Google Maps and OpenStreetMap (Mapbox) for the map data.</string>
<string name="selecting_all">selected all items</string>
<string name="selecting_none">deselected all items</string>
<string name="loading">Loading…</string>
</resources>

View File

@@ -261,17 +261,6 @@
android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" />
</activity>
<!-- Override services of the com.mapzen.android.lost library with exported:false
until https://github.com/lostzen/lost/pull/270 is merged -->
<service
android:name="com.mapzen.android.lost.internal.GeofencingIntentService"
android:exported="false">
<intent-filter>
<action android:name="com.mapzen.lost.action.ACTION_GEOFENCING_SERVICE" />
</intent-filter>
</service>
</application>
</manifest>

View File

@@ -36,7 +36,6 @@ import net.vonforst.evmap.utils.LocaleContextWrapper
import net.vonforst.evmap.utils.getLocationFromIntent
const val REQUEST_LOCATION_PERMISSION = 1
const val EXTRA_CHARGER_ID = "chargerId"
const val EXTRA_LAT = "lat"
const val EXTRA_LON = "lon"

View File

@@ -62,6 +62,8 @@ class FiltersAdapter : DataBindingAdapter<FilterWithValue<FilterValue>>() {
)
}
}
is BooleanFilterValue -> {
}
}
}

View File

@@ -1,6 +1,9 @@
package net.vonforst.evmap.fragment
import android.content.Context
import android.graphics.Canvas
import android.location.Criteria
import android.location.LocationManager
import android.os.Bundle
import android.view.Gravity
import android.view.LayoutInflater
@@ -20,8 +23,6 @@ import com.car2go.maps.model.LatLng
import com.google.android.material.color.MaterialColors
import com.google.android.material.snackbar.Snackbar
import com.google.android.material.transition.MaterialFadeThrough
import com.mapzen.android.lost.api.LocationServices
import com.mapzen.android.lost.api.LostApiClient
import net.vonforst.evmap.MapsActivity
import net.vonforst.evmap.R
import net.vonforst.evmap.adapter.DataBindingAdapter
@@ -34,9 +35,9 @@ import net.vonforst.evmap.utils.checkAnyLocationPermission
import net.vonforst.evmap.viewmodel.FavoritesViewModel
import net.vonforst.evmap.viewmodel.viewModelFactory
class FavoritesFragment : Fragment(), LostApiClient.ConnectionCallbacks {
class FavoritesFragment : Fragment() {
private lateinit var binding: FragmentFavoritesBinding
private var locationClient: LostApiClient? = null
private lateinit var locationManager: LocationManager
private var toDelete: Favorite? = null
private var deleteSnackbar: Snackbar? = null
private lateinit var adapter: FavoritesAdapter
@@ -52,8 +53,9 @@ class FavoritesFragment : Fragment(), LostApiClient.ConnectionCallbacks {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
locationClient = LostApiClient.Builder(requireContext())
.addConnectionCallbacks(this).build()
locationManager =
requireContext().getSystemService(Context.LOCATION_SERVICE) as LocationManager
enterTransition = MaterialFadeThrough()
exitTransition = MaterialFadeThrough()
@@ -109,8 +111,6 @@ class FavoritesFragment : Fragment(), LostApiClient.ConnectionCallbacks {
}
createTouchHelper().attachToRecyclerView(binding.favsList)
locationClient!!.connect()
binding.swipeRefresh.setOnRefreshListener {
vm.reloadAvailability() {
binding.swipeRefresh.isRefreshing = false
@@ -118,27 +118,21 @@ class FavoritesFragment : Fragment(), LostApiClient.ConnectionCallbacks {
}
}
override fun onConnected() {
val context = this.context ?: return
if (context.checkAnyLocationPermission()) {
val location = LocationServices.FusedLocationApi.getLastLocation(locationClient!!)
if (location != null) {
vm.location.value = LatLng(location.latitude, location.longitude)
override fun onStart() {
super.onStart()
if (requireContext().checkAnyLocationPermission()) {
val provider = locationManager.getBestProvider(Criteria().apply {
accuracy = Criteria.ACCURACY_FINE
}, true) ?: return
val location = locationManager.getLastKnownLocation(provider)
location?.let {
vm.location.value = LatLng(it.latitude, it.longitude)
}
}
}
override fun onConnectionSuspended() {
}
override fun onDestroy() {
super.onDestroy()
locationClient?.let {
if (it.isConnected) it.disconnect()
}
}
fun delete(fav: FavoriteWithDetail) {
val position =
vm.listData.value?.indexOfFirst { it.fav.favorite.favoriteId == fav.favorite.favoriteId }

View File

@@ -3,9 +3,11 @@ package net.vonforst.evmap.fragment
import android.os.Bundle
import android.view.*
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.MenuProvider
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import androidx.navigation.ui.setupWithNavController
@@ -22,7 +24,7 @@ import net.vonforst.evmap.ui.showEditTextDialog
import net.vonforst.evmap.viewmodel.FilterViewModel
class FilterFragment : Fragment() {
class FilterFragment : Fragment(), MenuProvider {
private lateinit var binding: FragmentFilterBinding
private val vm: FilterViewModel by viewModels()
@@ -40,9 +42,6 @@ class FilterFragment : Fragment() {
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_filter, container, false)
binding.lifecycleOwner = this
binding.vm = vm
setHasOptionsMenu(true)
vm.filterProfile.observe(viewLifecycleOwner) {}
return binding.root
@@ -50,6 +49,7 @@ class FilterFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
(requireActivity() as AppCompatActivity).setSupportActionBar(binding.toolbar)
requireActivity().addMenuProvider(this, viewLifecycleOwner, Lifecycle.State.RESUMED)
binding.toolbar.setupWithNavController(
findNavController(),
@@ -81,12 +81,11 @@ class FilterFragment : Fragment() {
view.setBackgroundColor(MaterialColors.getColor(view, android.R.attr.windowBackground))
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
override fun onCreateMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.filter, menu)
super.onCreateOptionsMenu(menu, inflater)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
override fun onMenuItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.menu_apply -> {
lifecycleScope.launch {
@@ -99,7 +98,7 @@ class FilterFragment : Fragment() {
saveProfile()
true
}
else -> super.onOptionsItemSelected(item)
else -> false
}
}

View File

@@ -4,11 +4,11 @@ import android.Manifest.permission.ACCESS_COARSE_LOCATION
import android.Manifest.permission.ACCESS_FINE_LOCATION
import android.annotation.SuppressLint
import android.content.Context
import android.content.pm.PackageManager
import android.content.res.Configuration
import android.graphics.Color
import android.location.Criteria
import android.location.Geocoder
import android.location.Location
import android.location.LocationManager
import android.os.Bundle
import android.text.method.KeyListener
import android.view.*
@@ -18,11 +18,12 @@ import android.widget.ImageView
import android.widget.TextView
import android.widget.Toast
import androidx.activity.OnBackPressedCallback
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.RequiresPermission
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.PopupMenu
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.core.location.LocationListenerCompat
import androidx.core.view.*
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
@@ -60,17 +61,15 @@ import com.mahc.custombottomsheetbehavior.BottomSheetBehaviorGoogleMapsLike
import com.mahc.custombottomsheetbehavior.BottomSheetBehaviorGoogleMapsLike.STATE_COLLAPSED
import com.mahc.custombottomsheetbehavior.BottomSheetBehaviorGoogleMapsLike.STATE_HIDDEN
import com.mahc.custombottomsheetbehavior.MergedAppBarLayoutBehavior
import com.mapzen.android.lost.api.LocationListener
import com.mapzen.android.lost.api.LocationRequest
import com.mapzen.android.lost.api.LocationServices
import com.mapzen.android.lost.api.LostApiClient
import com.stfalcon.imageviewer.StfalconImageViewer
import io.michaelrocks.bimap.HashBiMap
import io.michaelrocks.bimap.MutableBiMap
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import net.vonforst.evmap.*
import net.vonforst.evmap.BuildConfig
import net.vonforst.evmap.MapsActivity
import net.vonforst.evmap.R
import net.vonforst.evmap.adapter.ConnectorAdapter
import net.vonforst.evmap.adapter.DetailsAdapter
import net.vonforst.evmap.adapter.GalleryAdapter
@@ -79,6 +78,7 @@ import net.vonforst.evmap.api.goingelectric.GoingElectricApiWrapper
import net.vonforst.evmap.api.openchargemap.OpenChargeMapApiWrapper
import net.vonforst.evmap.autocomplete.ApiUnavailableException
import net.vonforst.evmap.autocomplete.PlaceWithBounds
import net.vonforst.evmap.bold
import net.vonforst.evmap.databinding.FragmentMapBinding
import net.vonforst.evmap.model.*
import net.vonforst.evmap.storage.PreferenceDataSource
@@ -95,14 +95,13 @@ import kotlin.collections.contains
import kotlin.collections.set
class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallback,
LostApiClient.ConnectionCallbacks, LocationListener {
class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallback, MenuProvider {
private lateinit var binding: FragmentMapBinding
private val vm: MapViewModel by viewModels()
private val galleryVm: GalleryViewModel by activityViewModels()
private var mapFragment: MapFragment? = null
private var map: AnyMap? = null
private lateinit var locationClient: LostApiClient
private lateinit var locationManager: LocationManager
private var requestingLocationUpdates = false
private lateinit var bottomSheetBehavior: BottomSheetBehaviorGoogleMapsLike<View>
private lateinit var detailAppBarBehavior: MergedAppBarLayoutBehavior
@@ -147,10 +146,8 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
prefs = PreferenceDataSource(requireContext())
locationClient = LostApiClient.Builder(requireContext())
.addConnectionCallbacks(this)
.build()
locationClient.connect()
locationManager =
requireContext().getSystemService(Context.LOCATION_SERVICE) as LocationManager
clusterIconGenerator = ClusterIconGenerator(requireContext())
enterTransition = MaterialFadeThrough()
@@ -197,8 +194,6 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
searchResultIcon = null
}
setHasOptionsMenu(true)
ViewCompat.setOnApplyWindowInsetsListener(
binding.root
) { v, insets ->
@@ -245,6 +240,8 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
requireActivity().addMenuProvider(this, viewLifecycleOwner, Lifecycle.State.RESUMED)
mapFragment!!.getMapAsync(this)
bottomSheetBehavior = BottomSheetBehaviorGoogleMapsLike.from(binding.bottomSheet)
detailAppBarBehavior = MergedAppBarLayoutBehavior.from(binding.detailAppBar)
@@ -319,19 +316,25 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
vm.reloadPrefs()
if (requestingLocationUpdates && requireContext().checkAnyLocationPermission()
&& locationClient.isConnected
) {
requestLocationUpdates()
}
}
@SuppressLint("MissingPermission")
private val requestPermissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) {
val context = context ?: return@registerForActivityResult
if (context.checkAnyLocationPermission()) {
enableLocation(moveTo = true, animate = true)
}
}
private fun setupClickListeners() {
binding.fabLocate.setOnClickListener {
if (!requireContext().checkFineLocationPermission()) {
ActivityCompat.requestPermissions(
requireActivity(),
arrayOf(ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION),
REQUEST_LOCATION_PERMISSION
requestPermissionLauncher.launch(
arrayOf(ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION)
)
}
if (requireContext().checkAnyLocationPermission()) {
@@ -1016,16 +1019,16 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
map.uiSettings.setMyLocationButtonEnabled(false)
if (moveTo) {
vm.myLocationEnabled.value = true
if (locationClient.isConnected) {
moveToLastLocation(map, animate)
requestLocationUpdates()
}
moveToLastLocation(map, animate)
requestLocationUpdates()
}
}
@RequiresPermission(anyOf = [ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION])
private fun moveToLastLocation(map: AnyMap, animate: Boolean) {
val location = LocationServices.FusedLocationApi.getLastLocation(locationClient)
val provider = getLocationProvider() ?: return
val location = locationManager.getLastKnownLocation(provider)
if (location != null) {
val latLng = LatLng(location.latitude, location.longitude)
vm.location.value = latLng
@@ -1038,6 +1041,10 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
}
}
private fun getLocationProvider() = locationManager.getBestProvider(Criteria().apply {
accuracy = Criteria.ACCURACY_FINE
}, true)
@Synchronized
private fun updateMap(chargepoints: List<ChargepointListItem>) {
val map = this.map ?: return
@@ -1142,23 +1149,7 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
}
}
@SuppressLint("MissingPermission")
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
when (requestCode) {
REQUEST_LOCATION_PERMISSION -> {
if ((grantResults.isNotEmpty() && grantResults.any { it == PackageManager.PERMISSION_GRANTED })) {
enableLocation(moveTo = true, animate = true)
}
}
else -> super.onRequestPermissionsResult(requestCode, permissions, grantResults)
}
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
override fun onCreateMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.map, menu)
val filterItem = menu.findItem(R.id.menu_filter)
@@ -1296,42 +1287,38 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
}
}
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
return false
}
override fun getRootView(): View {
return binding.root
}
override fun onConnected() {
val map = this.map ?: return
val context = this.context ?: return
if (vm.myLocationEnabled.value == true) {
if (context.checkAnyLocationPermission()) {
moveToLastLocation(map, false)
requestLocationUpdates()
}
}
}
@RequiresPermission(ACCESS_FINE_LOCATION)
@RequiresPermission(anyOf = [ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION])
private fun requestLocationUpdates() {
val request: LocationRequest = LocationRequest.create()
.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY)
.setInterval(5000)
LocationServices.FusedLocationApi.requestLocationUpdates(locationClient, request, this)
val provider = getLocationProvider() ?: return
locationManager.requestLocationUpdates(
provider,
5000,
1f,
locationListener
)
requestingLocationUpdates = true
}
@SuppressLint("MissingPermission")
private fun removeLocationUpdates() {
if (locationClient.isConnected) {
LocationServices.FusedLocationApi.removeLocationUpdates(locationClient, this)
if (context?.checkAnyLocationPermission() == true) {
locationManager.removeUpdates(locationListener)
}
}
override fun onConnectionSuspended() {
}
override fun onLocationChanged(location: Location?) {
val map = this.map ?: return
if (location == null || vm.myLocationEnabled.value == false) return
private val locationListener = LocationListenerCompat { location ->
val map = this.map ?: return@LocationListenerCompat
if (vm.myLocationEnabled.value == false) return@LocationListenerCompat
val latLng = LatLng(location.latitude, location.longitude)
val oldLoc = vm.location.value
@@ -1356,8 +1343,5 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
override fun onDestroy() {
super.onDestroy()
if (locationClient.isConnected) {
locationClient.disconnect()
}
}
}

View File

@@ -1,12 +1,12 @@
package net.vonforst.evmap.navigation
import androidx.navigation.NavController
import androidx.navigation.NavHostController
import androidx.navigation.fragment.NavHostFragment
class NavHostFragment : NavHostFragment() {
override fun onCreateNavController(navController: NavController) {
super.onCreateNavController(navController)
navController.navigatorProvider.addNavigator(
override fun onCreateNavHostController(navHostController: NavHostController) {
super.onCreateNavHostController(navHostController)
navHostController.navigatorProvider.addNavigator(
CustomNavigator(
requireContext()
)

View File

@@ -389,9 +389,10 @@ private fun colorToTransparent(color: Int, targetAlpha: Float = 31f / 255): Int
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()
val newRed = kotlin.math.max(((red - (1 - targetAlpha) * 255) / targetAlpha).roundToInt(), 0)
val newGreen =
kotlin.math.max(((green - (1 - targetAlpha) * 255) / targetAlpha).roundToInt(), 0)
val newBlue = kotlin.math.max(((blue - (1 - targetAlpha) * 255) / targetAlpha).roundToInt(), 0)
return Color.argb((targetAlpha * 255).roundToInt(), newRed, newGreen, newBlue)
}

View File

@@ -141,17 +141,24 @@ class MapViewModel(application: Application, private val state: SavedStateHandle
}
val chargerDetails: MediatorLiveData<Resource<ChargeLocation>> by lazy {
MediatorLiveData<Resource<ChargeLocation>>().apply {
value = state["chargerDetails"]
listOf(chargerSparse, referenceData).forEach {
addSource(it) { _ ->
val charger = chargerSparse.value
val refData = referenceData.value
if (charger != null && refData != null) {
loadChargerDetails(charger, refData)
if (charger.id != value?.data?.id) {
loadChargerDetails(charger, refData)
}
} else {
value = null
}
}
}
observeForever {
// persist data in case fragment gets recreated
state["chargerDetails"] = it
}
}
}
val charger: MediatorLiveData<Resource<ChargeLocation>> by lazy {
@@ -311,7 +318,8 @@ class MapViewModel(application: Application, private val state: SavedStateHandle
}
minPower >= 100 -> {
// when only showing high-power chargers we can use large markers
zoom < clusterThreshold
// because the density is much lower
false
}
else -> {
zoom < miniMarkerThreshold

View File

@@ -1,5 +1,6 @@
package net.vonforst.evmap.viewmodel
import android.os.Parcelable
import androidx.annotation.MainThread
import androidx.annotation.Nullable
import androidx.lifecycle.*
@@ -7,6 +8,8 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.parcelize.Parcelize
import kotlinx.parcelize.RawValue
import java.util.concurrent.atomic.AtomicBoolean
@@ -24,9 +27,13 @@ enum class Status {
/**
* A generic class that holds a value with its loading status.
* @param <T>
</T> */
data class Resource<out T>(val status: Status, val data: T?, val message: String?) {
*
* Note that this class implements Parcelable for convenience, but will give a runtime error when
* trying to write it to a Parcel if the type parameter does not implement Parcelable.
*/
@Parcelize
data class Resource<out T>(val status: Status, val data: @RawValue T?, val message: String?) :
Parcelable {
companion object {
fun <T> success(data: T?): Resource<T> {
return Resource(Status.SUCCESS, data, null)

View File

@@ -0,0 +1,28 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#FFFFFF">
<group
android:scaleX="0.184"
android:scaleY="0.184"
android:translateX="0.96"
android:translateY="0.96">
<path
android:pathData="M27.1,88.3l-2.2,-19.2l-3.3,0.3l2.2,19.2L27.1,88.3zM39,86.9l-2.2,-19.2l-3.3,0.3l2.2,19.2L39,86.9z"
android:fillColor="#FFFFFF" />
<path
android:pathData="M45.2,113c-1,1.3 -1.8,2.1 -2,2.2c-3,2.4 -5.4,3.1 -7.4,2.2c-3.5,-1.7 -3.2,-8.2 -3.1,-8.9l2.4,0.1c-0.1,1.8 0.2,5.8 1.8,6.6c0.9,0.5 2.5,-0.1 4.6,-1.8l0,0c0,0 6.7,-6.7 5.3,-12c-1.6,-6.4 5.8,-15.5 8.2,-18.6l0.3,-0.3l2,1.5l-0.3,0.5c-7.5,9.2 -8.3,14 -7.7,16.4C50.5,105.4 47.4,110.4 45.2,113z"
android:fillColor="#FFFFFF" />
<path
android:pathData="M19.7,88.1l0.9,7.9l7.3,4.9l9.8,-1l6,-6.4l-0.9,-7.9L19.7,88.1z"
android:fillColor="#FFFFFF" />
<path
android:pathData="M37.6,99.7l-9.8,1l2.1,8.7l7.7,-0.9V99.7L37.6,99.7zM44.6,79l0.8,7.2l-28.2,3.2l-0.8,-7.2L44.6,79z"
android:fillColor="#FFFFFF" />
<path
android:pathData="M66.7,0C46.5,0 30.1,16.4 30.1,36.6c0,27.6 30.8,42 34.5,81.4c0.1,1.2 1,2 2.2,2c1.2,0 2.1,-0.8 2.2,-2c3.7,-39.4 34.5,-53.8 34.5,-81.4C103.3,16.2 86.9,0 66.7,0zM78.4,34.7L64.3,59V40.8h-6V18.7c0,0 20.2,0 20.1,-0.1l-8.1,16.2H78.4z"
android:fillColor="#FFFFFF" />
</group>
</vector>

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 583 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 393 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 778 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -167,7 +167,6 @@
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>

View File

@@ -163,6 +163,8 @@
android:label="OnboardingFragment">
<action
android:id="@+id/action_onboarding_to_map"
app:destination="@id/map" />
app:destination="@id/map"
app:popUpTo="@id/onboarding"
app:popUpToInclusive="true" />
</fragment>
</navigation>

View File

@@ -261,4 +261,5 @@
<string name="pref_map_rotate_gestures_on">Karte kann mit Zweifingergeste rotiert werden</string>
<string name="pref_map_rotate_gestures_off">Karte bleibt fest nach Norden ausgerichtet</string>
<string name="refresh_live_data">Echtzeitstatus aktualisieren</string>
<string name="autocomplete_connection_error">Vorschläge konnten nicht geladen werden</string>
</resources>

View File

@@ -246,4 +246,5 @@
<string name="pref_map_rotate_gestures_on">Map can be rotated with two-finger gesture</string>
<string name="pref_map_rotate_gestures_off">Map will be fixed to north-up</string>
<string name="refresh_live_data">refresh real-time status</string>
<string name="autocomplete_connection_error">Suggestions could not be loaded</string>
</resources>

View File

@@ -1,7 +1,7 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = '1.6.21'
ext.kotlin_version = '1.7.10'
ext.about_libs_version = '8.9.4'
ext.nav_version = '2.5.1'
repositories {
@@ -10,7 +10,7 @@ buildscript {
gradlePluginPortal()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.2.1'
classpath 'com.android.tools.build:gradle:7.2.2'
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"

View File

@@ -0,0 +1,3 @@
Fehler behoben:
- Mögliche Behebung von Abstürzen unter Android Auto
- Filterbutton war unter Android Automotive verschwunden

View File

@@ -0,0 +1,6 @@
Fehler behoben:
- Darstellungsprobleme mit einigen hervorgehobenen Tarifen im Preisvergleich behoben
- Verbesserungen für weitere kleine Darstellungsfehler
- Android Auto: Richtiges Icon für dauerhafte Benachrichtigung zum Standortzugriff verwenden
- Android Auto: Ausrichtung von +/- Buttons korrigiert
- Android Auto: Liste der Ladestationen nach Neustart der App aktualisieren

View File

@@ -0,0 +1,3 @@
Bugfixes:
- Possible fix of crashes under Android Auto
- Filter button disappeared under Android Automotive

View File

@@ -0,0 +1,6 @@
Bugfixes:
- Fixed visual problems with some highlighted providers in price comparison
- Improvements for some other minor visual issues
- Android Auto: Use proper icon for persistent notification about location access
- Android Auto: Fix alignment of +/- buttons
- Android Auto: Refresh chargers after going back to app

View File

@@ -14,4 +14,3 @@
kotlin.code.style=official
org.gradle.jvmargs=-Xmx2048M -Dkotlin.daemon.jvm.options\="-Xmx2048M"
android.useAndroidX=true
android.enableJetifier=true

View File

Binary file not shown.

View File

@@ -1,6 +1,6 @@
#Wed Dec 23 14:54:49 CET 2020
#Sat Aug 06 15:33:46 CEST 2022
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME

286
gradlew vendored
View File

@@ -1,78 +1,129 @@
#!/usr/bin/env sh
#!/bin/sh
#
# Copyright <20> 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
##
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions <20>$var<61>, <20>${var}<7D>, <20>${var:-default}<7D>, <20>${var+SET}<7D>,
# <20>${var#prefix}<7D>, <20>${var%suffix}<7D>, and <20>$( cmd )<29>;
# * compound commands having a testable exit status, especially <20>case<73>;
# * various built-in commands including <20>command<6E>, <20>set<65>, and <20>ulimit<69>.
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
MAX_FD=maximum
warn () {
echo "$*"
}
} >&2
die () {
echo
echo "$*"
echo
exit 1
}
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD="$JAVA_HOME/bin/java"
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
@@ -81,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
@@ -89,84 +140,95 @@ location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

43
gradlew.bat vendored
View File

@@ -1,3 +1,19 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@@ -13,15 +29,18 @@ if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@@ -35,7 +54,7 @@ goto fail
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
@@ -45,28 +64,14 @@ echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell