Compare commits

..

24 Commits
1.3.6 ... 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
29 changed files with 222 additions and 162 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 96
versionName "1.3.6"
versionCode 98
versionName "1.3.7"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
@@ -173,7 +173,6 @@ 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'
@@ -185,7 +184,7 @@ dependencies {
googleAutomotiveImplementation "androidx.car.app:app-automotive:$carAppVersion"
// AnyMaps
def anyMapsVersion = 'c401a4256a'
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'
@@ -200,7 +199,7 @@ dependencies {
// 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'
@@ -232,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'
@@ -243,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

@@ -71,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())

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
}
@@ -192,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
@@ -280,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()
}
@@ -376,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() {
@@ -396,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

@@ -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

@@ -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

@@ -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

@@ -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,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,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