diff --git a/app/build.gradle b/app/build.gradle
index f45bc9dd0..08d97d015 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -123,6 +123,7 @@ dependencies {
kapt 'androidx.room:room-compiler:2.3.0'
implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0'
+ implementation 'com.squareup.okhttp3:okhttp:3.12.12'
implementation 'com.github.omadahealth:swipy:1.2.3'
implementation 'de.cketti.library.changelog:ckchangelog:1.2.2'
implementation 'com.google.android:flexbox:0.3.2'
@@ -132,9 +133,9 @@ dependencies {
implementation "com.mikepenz:fastadapter-commons:$fastadapterVersion"
implementation "com.mikepenz:fastadapter-extensions-expandable:$fastadapterVersion"
implementation 'uk.co.samuelwall:material-tap-target-prompt:2.14.0'
- implementation 'com.mapbox.mapboxsdk:mapbox-android-sdk:5.5.0'
- implementation 'com.mapbox.mapboxsdk:mapbox-android-plugin-locationlayer:0.4.0'
- implementation 'com.mapzen.android:lost:3.0.4'
+ implementation('org.maplibre.gl:android-sdk:9.5.2') {
+ exclude group: 'com.google.android.gms' // no proprietary Google libraries
+ }
// only added because of lint bug Timber 4.6.0
implementation 'com.jakewharton.timber:timber:4.7.0'
diff --git a/app/src/main/java/de/grobox/transportr/AppModule.java b/app/src/main/java/de/grobox/transportr/AppModule.java
index 766ad9010..4e862d716 100644
--- a/app/src/main/java/de/grobox/transportr/AppModule.java
+++ b/app/src/main/java/de/grobox/transportr/AppModule.java
@@ -27,7 +27,7 @@
import de.grobox.transportr.data.locations.LocationRepository;
import de.grobox.transportr.data.searches.SearchesDao;
import de.grobox.transportr.data.searches.SearchesRepository;
-import de.grobox.transportr.map.GpsController;
+import de.grobox.transportr.map.PositionController;
import de.grobox.transportr.networks.TransportNetworkManager;
import de.grobox.transportr.settings.SettingsManager;
@@ -70,8 +70,8 @@ SearchesRepository searchesRepository(SearchesDao searchesDao, LocationDao locat
}
@Provides
- GpsController gpsController() {
- return new GpsController(application.getApplicationContext());
+ PositionController gpsController() {
+ return new PositionController(application.getApplicationContext());
}
}
diff --git a/app/src/main/java/de/grobox/transportr/TransportrApplication.kt b/app/src/main/java/de/grobox/transportr/TransportrApplication.kt
index 5e69126ca..539ceea37 100644
--- a/app/src/main/java/de/grobox/transportr/TransportrApplication.kt
+++ b/app/src/main/java/de/grobox/transportr/TransportrApplication.kt
@@ -20,7 +20,7 @@ package de.grobox.transportr
import android.app.Application
import com.mapbox.mapboxsdk.Mapbox
-import com.mapbox.services.android.telemetry.MapboxTelemetry
+import com.mapbox.mapboxsdk.WellKnownTileServer
open class TransportrApplication : Application() {
lateinit var component: AppComponent
@@ -29,11 +29,7 @@ open class TransportrApplication : Application() {
override fun onCreate() {
super.onCreate()
- Mapbox.getInstance(
- applicationContext,
- "pk.eyJ1IjoidG92b2s3IiwiYSI6ImNpeTA1OG82YjAwN3YycXA5cWJ6NThmcWIifQ.QpURhF9y7XBMLmWhELsOnw"
- )
- MapboxTelemetry.getInstance().isTelemetryEnabled = false
+ Mapbox.getInstance(applicationContext)
component = createComponent()
}
@@ -43,4 +39,4 @@ open class TransportrApplication : Application() {
.appModule(AppModule(this))
.build()
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/de/grobox/transportr/locations/LocationLiveData.kt b/app/src/main/java/de/grobox/transportr/locations/LocationLiveData.kt
deleted file mode 100644
index ec6df47fc..000000000
--- a/app/src/main/java/de/grobox/transportr/locations/LocationLiveData.kt
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Transportr
- *
- * Copyright (c) 2013 - 2021 Torsten Grote
- *
- * This program is Free Software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package de.grobox.transportr.locations
-
-import android.Manifest.permission.ACCESS_FINE_LOCATION
-import android.annotation.SuppressLint
-import android.content.Context
-import android.location.Location
-import androidx.annotation.RequiresPermission
-import androidx.annotation.WorkerThread
-import androidx.lifecycle.LifecycleOwner
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.Observer
-import com.mapbox.services.android.telemetry.location.LocationEngineListener
-import com.mapbox.services.android.telemetry.location.LocationEnginePriority.BALANCED_POWER_ACCURACY
-import com.mapbox.services.android.telemetry.location.LostLocationEngine
-import de.grobox.transportr.locations.ReverseGeocoder.ReverseGeocoderCallback
-import de.grobox.transportr.map.hasLocationProviders
-
-
-class LocationLiveData(private val context: Context) : LiveData(), LocationEngineListener, ReverseGeocoderCallback {
-
- private val locationEngine: LostLocationEngine = LostLocationEngine(context)
-
- @RequiresPermission(ACCESS_FINE_LOCATION)
- override fun observe(owner: LifecycleOwner, observer: Observer) {
- super.observe(owner, observer)
- }
-
- @SuppressLint("MissingPermission")
- override fun onActive() {
- super.onActive()
-
- if (hasLocationProviders(context)) {
- locationEngine.priority = BALANCED_POWER_ACCURACY
- locationEngine.interval = 5000
- locationEngine.activate()
- locationEngine.addLocationEngineListener(this)
- // work-around for https://github.com/mapbox/mapbox-plugins-android/issues/371
- locationEngine.requestLocationUpdates()
- } else {
- value = null
- }
- }
-
- override fun onInactive() {
- super.onInactive()
- locationEngine.removeLocationUpdates()
- locationEngine.removeLocationEngineListener(this)
- locationEngine.deactivate()
- }
-
- @SuppressLint("MissingPermission")
- override fun onConnected() {
- locationEngine.requestLocationUpdates()
- }
-
- override fun onLocationChanged(location: Location) {
- locationEngine.removeLocationUpdates()
- Thread {
- val geoCoder = ReverseGeocoder(context, this)
- geoCoder.findLocation(location)
- }.start()
- }
-
- @WorkerThread
- override fun onLocationRetrieved(location: WrapLocation) {
- postValue(location)
- }
-
-}
diff --git a/app/src/main/java/de/grobox/transportr/map/BaseMapFragment.kt b/app/src/main/java/de/grobox/transportr/map/BaseMapFragment.kt
index 0c2c2fd23..4838aa2e8 100644
--- a/app/src/main/java/de/grobox/transportr/map/BaseMapFragment.kt
+++ b/app/src/main/java/de/grobox/transportr/map/BaseMapFragment.kt
@@ -20,14 +20,15 @@
package de.grobox.transportr.map
import android.os.Bundle
-import android.text.Html
import android.text.method.LinkMovementMethod
-import androidx.annotation.CallSuper
-import androidx.annotation.LayoutRes
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
+import androidx.annotation.CallSuper
+import androidx.annotation.LayoutRes
+import androidx.core.text.HtmlCompat
+import androidx.core.text.HtmlCompat.FROM_HTML_MODE_LEGACY
import com.mapbox.mapboxsdk.camera.CameraUpdateFactory
import com.mapbox.mapboxsdk.geometry.LatLng
import com.mapbox.mapboxsdk.geometry.LatLngBounds
@@ -43,10 +44,16 @@ abstract class BaseMapFragment : TransportrFragment(), OnMapReadyCallback {
private lateinit var attribution: TextView
protected var map: MapboxMap? = null
protected var mapPadding: Int = 0
+ protected var mapInset: MapPadding = MapPadding()
@get:LayoutRes
protected abstract val layout: Int
+ // Returns the Jawg url depending on the style given (jawg-streets by default)
+ // taken from https://www.jawg.io/docs/integration/maplibre-gl-android/simple-map/
+ private fun makeStyleUrl(style: String = "jawg-streets") =
+ "${getString(R.string.jawg_styles_url) + style}.json?access-token=${getString(R.string.jawg_access_token)}"
+
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
super.onCreateView(inflater, container, savedInstanceState)
@@ -64,7 +71,7 @@ abstract class BaseMapFragment : TransportrFragment(), OnMapReadyCallback {
mapView.onCreate(savedInstanceState)
mapView.getMapAsync(this)
attribution.movementMethod = LinkMovementMethod.getInstance()
- attribution.text = Html.fromHtml(getString(R.string.map_attribution, getString(R.string.map_attribution_improve)))
+ attribution.text = HtmlCompat.fromHtml(getString(R.string.map_attribution, getString(R.string.map_attribution_improve)), FROM_HTML_MODE_LEGACY)
}
override fun onStart() {
@@ -78,9 +85,9 @@ abstract class BaseMapFragment : TransportrFragment(), OnMapReadyCallback {
activity?.run {
// work-around to force update map style after theme switching
obtainStyledAttributes(intArrayOf(R.attr.mapStyle)).apply {
- val mapStyle = getString(0)
- if (mapStyle != null && mapboxMap.styleUrl != mapStyle) {
- mapboxMap.setStyleUrl(mapStyle)
+ val mapStyle = getString(0)?.let { makeStyleUrl(it) }
+ if (mapStyle != null && mapboxMap.style?.uri != mapStyle) {
+ mapboxMap.setStyle(mapStyle)
}
recycle()
}
@@ -120,6 +127,8 @@ abstract class BaseMapFragment : TransportrFragment(), OnMapReadyCallback {
protected open fun animateTo(latLng: LatLng?, zoom: Int) {
if (latLng == null) return
map?.let { map ->
+ val padding = mapInset + mapPadding
+ map.moveCamera(CameraUpdateFactory.paddingTo(padding.left.toDouble(), padding.top.toDouble(), padding.right.toDouble(), padding.bottom.toDouble()))
val update = if (map.cameraPosition.zoom < zoom) CameraUpdateFactory.newLatLngZoom(
latLng,
zoom.toDouble()
@@ -130,7 +139,8 @@ abstract class BaseMapFragment : TransportrFragment(), OnMapReadyCallback {
protected open fun zoomToBounds(latLngBounds: LatLngBounds?, animate: Boolean) {
if (latLngBounds == null) return
- val update = CameraUpdateFactory.newLatLngBounds(latLngBounds, mapPadding)
+ val padding = mapInset + mapPadding
+ val update = CameraUpdateFactory.newLatLngBounds(latLngBounds, padding.left, padding.top, padding.right, padding.bottom)
map?.let { map ->
if (animate) {
map.easeCamera(update)
@@ -148,4 +158,23 @@ abstract class BaseMapFragment : TransportrFragment(), OnMapReadyCallback {
zoomToBounds(latLngBounds, true)
}
+ protected fun setPadding(left: Int = 0, top: Int = 0, right: Int = 0, bottom: Int = 0) {
+ // store map padding to be retained even after CameraBoundsUpdates
+ // and update directly for subsequent camera updates in MapDrawer
+ mapInset = MapPadding(left, top, right, bottom)
+ map?.moveCamera(CameraUpdateFactory.paddingTo(left.toDouble(), top.toDouble(), right.toDouble(), bottom.toDouble()))
+ }
+
+ data class MapPadding(
+ val left: Int = 0,
+ val top: Int = 0,
+ val right: Int = 0,
+ val bottom: Int = 0
+ ) {
+ constructor(padding: DoubleArray) : this(padding[0].toInt(), padding[1].toInt(), padding[2].toInt(), padding[3].toInt())
+
+ operator fun plus(other: Int) =
+ MapPadding(left + other, top + other, right + other, bottom + other)
+ }
+
}
diff --git a/app/src/main/java/de/grobox/transportr/map/GpsController.kt b/app/src/main/java/de/grobox/transportr/map/GpsController.kt
deleted file mode 100644
index 6e9bf8ee8..000000000
--- a/app/src/main/java/de/grobox/transportr/map/GpsController.kt
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Transportr
- *
- * Copyright (c) 2013 - 2021 Torsten Grote
- *
- * This program is Free Software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package de.grobox.transportr.map
-
-
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.MutableLiveData
-import android.content.Context
-import android.location.Location
-import android.location.LocationManager
-import androidx.annotation.WorkerThread
-import de.grobox.transportr.AbstractManager
-import de.grobox.transportr.locations.ReverseGeocoder
-import de.grobox.transportr.locations.ReverseGeocoder.ReverseGeocoderCallback
-import de.grobox.transportr.locations.WrapLocation
-import de.grobox.transportr.map.GpsController.Companion.GPS_FIX_EXPIRY
-import de.schildbach.pte.dto.Location.coord
-import java.util.*
-import java.util.concurrent.TimeUnit
-
-internal data class GpsState(var hasFix: Boolean, var isOld: Boolean, var isTracking: Boolean)
-
-internal class GpsController(val context: Context) : AbstractManager(), ReverseGeocoderCallback {
-
- companion object {
- internal val GPS_FIX_EXPIRY = TimeUnit.SECONDS.toMillis(3)
- }
-
- private val geoCoder = ReverseGeocoder(context, this)
-
- private val gpsState = MutableLiveData()
-
- private var location: Location? = null
- private var lastLocation: Location? = null
- private var wrapLocation: WrapLocation? = null
-
- init {
- gpsState.value = GpsState(false, false, false)
- }
-
- @WorkerThread
- override fun onLocationRetrieved(location: WrapLocation) {
- runOnUiThread { wrapLocation = location }
- }
-
- fun setLocation(newLocation: Location?, useGeoCoder: Boolean) {
- if (newLocation == null) return
-
- if (location == null || location!!.distanceTo(newLocation) > 50) {
- // store location and last location
- lastLocation = location
- location = newLocation
-
- if (useGeoCoder) {
- // check if we need to use the reverse geo coder
- val isNew = wrapLocation.let { it == null || !it.isSamePlace(newLocation.latitude, newLocation.longitude) }
- if (isNew) geoCoder.findLocation(newLocation)
- }
- }
- // set new FAB state if location is recent
- if (!newLocation.isOld()) {
- updateGpsState(hasFix = true, isOld = false)
- }
- }
-
- internal fun updateGpsState(hasFix: Boolean? = null, isOld: Boolean? = null, isTracking: Boolean? = null) {
- val newState = GpsState(
- hasFix ?: gpsState.value!!.hasFix,
- isOld ?: gpsState.value!!.isOld,
- isTracking ?: gpsState.value!!.isTracking
- )
- // only set a new value if it is different from the old
- if (newState != gpsState.value) gpsState.value = newState
- }
-
- fun getGpsState(): LiveData = gpsState
-
- fun getWrapLocation(): WrapLocation? {
- if (wrapLocation == null) {
- location?.let { return it.toWrapLocation() }
- }
- wrapLocation?.let { return it }
- return null
- }
-
-}
-
-fun Location.toWrapLocation(): WrapLocation? {
- if (latitude == 0.0 && longitude == 0.0) return null
- val loc = coord((latitude * 1E6).toInt(), (longitude * 1E6).toInt())
- return WrapLocation(loc)
-}
-
-fun Location.isOld(): Boolean {
- return Date().time > time + GPS_FIX_EXPIRY
-}
-
-fun hasLocationProviders(context: Context): Boolean {
- val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
- val activeProviders = locationManager.getProviders(true)
-
- return activeProviders.size > 1 || (activeProviders.size == 1 && activeProviders[0] != "passive")
-}
diff --git a/app/src/main/java/de/grobox/transportr/map/GpsMapFragment.kt b/app/src/main/java/de/grobox/transportr/map/GpsMapFragment.kt
index 002081046..6a009c181 100644
--- a/app/src/main/java/de/grobox/transportr/map/GpsMapFragment.kt
+++ b/app/src/main/java/de/grobox/transportr/map/GpsMapFragment.kt
@@ -21,193 +21,133 @@ package de.grobox.transportr.map
import android.Manifest.permission.ACCESS_FINE_LOCATION
import android.annotation.SuppressLint
-import androidx.lifecycle.Observer
import android.content.pm.PackageManager.PERMISSION_GRANTED
import android.content.res.ColorStateList
-import android.graphics.BlendMode
-import android.graphics.BlendModeColorFilter
import android.graphics.PorterDuff
-import android.location.Location
-import android.os.Build
import android.os.Bundle
-import android.os.CountDownTimer
-import androidx.annotation.CallSuper
-import androidx.annotation.RequiresPermission
-import com.google.android.material.floatingactionbutton.FloatingActionButton
-import androidx.core.content.ContextCompat
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
-import com.mapbox.mapboxsdk.camera.CameraUpdateFactory
-import com.mapbox.mapboxsdk.camera.CameraUpdateFactory.newLatLng
-import com.mapbox.mapboxsdk.camera.CameraUpdateFactory.newLatLngZoom
-import com.mapbox.mapboxsdk.geometry.LatLng
-import com.mapbox.mapboxsdk.geometry.LatLngBounds
+import androidx.annotation.CallSuper
+import androidx.core.content.ContextCompat
+import com.google.android.material.floatingactionbutton.FloatingActionButton
+import com.mapbox.mapboxsdk.location.LocationComponent
+import com.mapbox.mapboxsdk.location.LocationComponentActivationOptions
+import com.mapbox.mapboxsdk.location.LocationComponentOptions
+import com.mapbox.mapboxsdk.location.OnCameraTrackingChangedListener
+import com.mapbox.mapboxsdk.location.modes.CameraMode
+import com.mapbox.mapboxsdk.location.modes.RenderMode
import com.mapbox.mapboxsdk.maps.MapboxMap
-import com.mapbox.mapboxsdk.plugins.locationlayer.LocationLayerMode
-import com.mapbox.mapboxsdk.plugins.locationlayer.LocationLayerPlugin
-import com.mapbox.services.android.telemetry.location.LocationEngineListener
-import com.mapbox.services.android.telemetry.location.LocationEnginePriority
-import com.mapbox.services.android.telemetry.location.LostLocationEngine
import de.grobox.transportr.R
-import de.grobox.transportr.map.GpsController.Companion.GPS_FIX_EXPIRY
+import de.grobox.transportr.map.GpsMapViewModel.GpsFabState
+import de.grobox.transportr.map.PositionController.Companion.FIX_EXPIRY
+import de.grobox.transportr.map.PositionController.PositionState
import de.grobox.transportr.utils.Constants.REQUEST_LOCATION_PERMISSION
-import java.util.concurrent.TimeUnit.SECONDS
-abstract class GpsMapFragment : BaseMapFragment(), LocationEngineListener {
+internal abstract class GpsMapFragment : BaseMapFragment() {
- internal lateinit var gpsController: GpsController
+ internal abstract val viewModel: ViewModel
- private var locationPlugin: LocationLayerPlugin? = null
- private var locationEngine: LostLocationEngine? = null
+ private var locationComponent: LocationComponent? = null
private lateinit var gpsFab: FloatingActionButton
- protected open var useGeoCoder = false
-
companion object {
const val LOCATION_ZOOM = 14
}
- private val timer = object : CountDownTimer(Long.MAX_VALUE, GPS_FIX_EXPIRY) {
- override fun onTick(millisUntilFinished: Long) {
- val location = locationPlugin?.lastKnownLocation
- if (location == null) gpsController.updateGpsState(hasFix = false)
- else if (location.isOld()) {
- gpsController.updateGpsState(isOld = true)
- }
- }
-
- override fun onFinish() {}
- }
-
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val v = super.onCreateView(inflater, container, savedInstanceState) as View
gpsFab = v.findViewById(R.id.gpsFab)
gpsFab.setOnClickListener { onGpsFabClick() }
+ viewModel.gpsFabState.observe(viewLifecycleOwner) { state ->
+ // Floating GPS Action Button Style
+ val (iconColor, backgroundColor) = when (state) {
+ GpsFabState.TRACKING -> Pair(
+ ContextCompat.getColor(context, R.color.fabForegroundFollow),
+ ColorStateList.valueOf(ContextCompat.getColor(context, R.color.fabBackground))
+ )
+ GpsFabState.ENABLED -> Pair(
+ ContextCompat.getColor(context, R.color.fabForegroundMoved),
+ ColorStateList.valueOf(ContextCompat.getColor(context, R.color.fabBackgroundMoved))
+ )
+ else -> Pair(
+ ContextCompat.getColor(context, R.color.fabForegroundInitial),
+ ColorStateList.valueOf(ContextCompat.getColor(context, R.color.fabBackground))
+ )
+ }
+ gpsFab.drawable.setColorFilter(iconColor, PorterDuff.Mode.SRC_IN)
+ gpsFab.backgroundTintList = backgroundColor
+ }
+
return v
}
@CallSuper
- @SuppressLint("MissingPermission")
- override fun onStart() {
- super.onStart()
- locationPlugin?.onStart()
- locationEngine?.let {
- it.addLocationEngineListener(this)
- // work-around for https://github.com/mapbox/mapbox-plugins-android/issues/371
- it.requestLocationUpdates()
- }
- timer.start()
+ override fun onMapReady(mapboxMap: MapboxMap) {
+ super.onMapReady(mapboxMap)
+ activateLocationComponent()
}
- @CallSuper
- override fun onStop() {
- locationPlugin?.onStop()
- locationEngine?.let {
- it.removeLocationEngineListener(this)
- it.removeLocationUpdates()
- }
- super.onStop()
- timer.cancel()
- }
+ private fun activateLocationComponent() {
+ // Check if permissions are enabled and if not request
+ if (ContextCompat.checkSelfPermission(context, ACCESS_FINE_LOCATION) == PERMISSION_GRANTED) {
- override fun onDestroyView() {
- super.onDestroyView()
- locationEngine?.deactivate()
- }
+ locationComponent = map?.locationComponent
+ map?.getStyle { style ->
+ locationComponent?.apply {
- @CallSuper
- override fun onMapReady(mapboxMap: MapboxMap) {
- super.onMapReady(mapboxMap)
- enableLocationPlugin()
+ activateLocationComponent(
+ LocationComponentActivationOptions.builder(context, style)
+ .locationComponentOptions(LocationComponentOptions.builder(context).staleStateTimeout(FIX_EXPIRY).build())
+ .useDefaultLocationEngine(false)
+ .build()
+ )
- locationEngine?.let {
- if (ContextCompat.checkSelfPermission(context, ACCESS_FINE_LOCATION) == PERMISSION_GRANTED) {
- gpsController.setLocation(it.lastLocation, useGeoCoder)
- }
- }
+ addOnCameraTrackingChangedListener(object : OnCameraTrackingChangedListener {
+ override fun onCameraTrackingDismissed() {}
+
+ override fun onCameraTrackingChanged(currentMode: Int) {
+ viewModel.isCameraTracking.value = currentMode == CameraMode.TRACKING
+ }
+ })
- gpsController.getGpsState().observe(this, Observer { onNewGpsState(it!!) })
- map?.addOnScrollListener {
- gpsController.getGpsState().value?.let {
- if (it.isTracking) {
- gpsController.updateGpsState(isTracking = false)
+ addOnLocationStaleListener {
+ viewModel.isPositionStale.value = it
+ }
+
+ cameraMode = CameraMode.NONE
+ renderMode = RenderMode.COMPASS
}
- }
- }
- }
- private fun enableLocationPlugin() {
- // Check if permissions are enabled and if not request
- if (ContextCompat.checkSelfPermission(context, ACCESS_FINE_LOCATION) == PERMISSION_GRANTED) {
- // Create an instance of LOST location engine
- initializeLocationEngine()
+ viewModel.positionController.position.observe(viewLifecycleOwner) {
+ locationComponent?.forceLocationUpdate(it)
+ }
- if (locationPlugin == null && map != null) {
- locationPlugin = LocationLayerPlugin(mapView, map!!, locationEngine, R.style.LocationLayer)
- locationPlugin!!.setLocationLayerEnabled(LocationLayerMode.COMPASS)
+ viewModel.positionController.positionState.observe(viewLifecycleOwner) {
+ locationComponent?.isLocationComponentEnabled = when (it) {
+ PositionState.ENABLED -> true
+ else -> false
+ }
+ }
}
} else {
requestPermission()
}
}
- @RequiresPermission(ACCESS_FINE_LOCATION)
- private fun initializeLocationEngine() {
- locationEngine = LostLocationEngine(context)
- locationEngine?.let {
- it.priority = LocationEnginePriority.HIGH_ACCURACY
- it.interval = SECONDS.toMillis(1).toInt()
- it.smallestDisplacement = 0f
- it.activate()
- it.addLocationEngineListener(this)
- }
- }
-
- @SuppressLint("MissingPermission")
- override fun onConnected() {
- locationEngine?.requestLocationUpdates()
- }
-
+ @SuppressLint("MissingPermission") //todo: remove
private fun onGpsFabClick() {
- if (ContextCompat.checkSelfPermission(context, ACCESS_FINE_LOCATION) != PERMISSION_GRANTED) {
- Toast.makeText(context, R.string.permission_denied_gps, Toast.LENGTH_SHORT).show()
- return
- }
- if (!hasLocationProviders(context)) {
- Toast.makeText(context, R.string.warning_gps_off, Toast.LENGTH_SHORT).show()
- return
- }
- val location = locationPlugin?.lastKnownLocation
- if (location == null) {
- Toast.makeText(context, R.string.warning_no_gps_fix, Toast.LENGTH_SHORT).show()
- return
- }
- map?.let { map ->
- val latLng = LatLng(location.latitude, location.longitude)
- val update = if (map.cameraPosition.zoom < LOCATION_ZOOM) newLatLngZoom(latLng, LOCATION_ZOOM.toDouble()) else newLatLng(latLng)
- map.easeCamera(update, 750)
- gpsController.updateGpsState(isTracking = true)
- }
- }
-
- private fun onNewGpsState(gpsState: GpsState) {
- // Floating GPS Action Button Style
- var iconColor = ContextCompat.getColor(context, R.color.fabForegroundInitial)
- var backgroundColor = ColorStateList.valueOf(ContextCompat.getColor(context, R.color.fabBackground))
- if (gpsState.hasFix && !gpsState.isOld && !gpsState.isTracking) {
- iconColor = ContextCompat.getColor(context, R.color.fabForegroundMoved)
- backgroundColor = ColorStateList.valueOf(ContextCompat.getColor(context, R.color.fabBackgroundMoved))
- } else if (gpsState.hasFix && !gpsState.isOld && gpsState.isTracking) {
- iconColor = ContextCompat.getColor(context, R.color.fabForegroundFollow)
+ when (viewModel.positionController.positionState.value) {
+ PositionState.DENIED -> Toast.makeText(context, R.string.permission_denied_gps, Toast.LENGTH_SHORT).show()
+ PositionState.DISABLED -> Toast.makeText(context, R.string.warning_gps_off, Toast.LENGTH_SHORT).show()
+ PositionState.ENABLED -> {
+ locationComponent?.lastKnownLocation?.let {
+ map?.zoomToMyLocation()
+ } ?: Toast.makeText(context, R.string.warning_no_gps_fix, Toast.LENGTH_SHORT).show()
+ }
}
- gpsFab.drawable.setColorFilter(iconColor, PorterDuff.Mode.SRC_IN)
- gpsFab.backgroundTintList = backgroundColor
-
- // Location Marker Icon Style
- locationPlugin?.applyStyle(if (gpsState.isOld) R.style.LocationLayerOld else R.style.LocationLayer)
}
private fun requestPermission() {
@@ -219,34 +159,19 @@ abstract class GpsMapFragment : BaseMapFragment(), LocationEngineListener {
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) {
if (requestCode != REQUEST_LOCATION_PERMISSION) return
if (grantResults.isNotEmpty() && grantResults[0] == PERMISSION_GRANTED) {
- enableLocationPlugin()
+ viewModel.positionController.permissionGranted()
+ activateLocationComponent()
}
}
- override fun onLocationChanged(location: Location) {
- if (gpsController.getGpsState().value!!.isTracking) {
- map?.animateCamera(newLatLng(LatLng(location.latitude, location.longitude)))
- }
- gpsController.setLocation(location, useGeoCoder)
- }
-
- fun zoomToMyLocation() {
- val location = getLastKnownLocation() ?: return
- val latLng = LatLng(location.latitude, location.longitude)
- val update = CameraUpdateFactory.newLatLngZoom(latLng, LOCATION_ZOOM.toDouble())
- map?.moveCamera(update)
- }
-
- override fun animateTo(latLng: LatLng?, zoom: Int) {
- gpsController.updateGpsState(isTracking = false)
- super.animateTo(latLng, zoom)
- }
-
- override fun zoomToBounds(latLngBounds: LatLngBounds?, animate: Boolean) {
- gpsController.updateGpsState(isTracking = false)
- super.zoomToBounds(latLngBounds, animate)
+ protected fun MapboxMap.zoomToMyLocation() {
+ locationComponent.setCameraMode(
+ CameraMode.TRACKING, 750,
+ if (cameraPosition.zoom < LOCATION_ZOOM) LOCATION_ZOOM.toDouble() else null,
+ null, null, null
+ )
}
- protected fun getLastKnownLocation() = locationPlugin?.lastKnownLocation
+ protected fun getLastKnownLocation() = viewModel.positionController.position.value
}
diff --git a/app/src/main/java/de/grobox/transportr/map/GpsMapViewModel.kt b/app/src/main/java/de/grobox/transportr/map/GpsMapViewModel.kt
new file mode 100644
index 000000000..fa5a754df
--- /dev/null
+++ b/app/src/main/java/de/grobox/transportr/map/GpsMapViewModel.kt
@@ -0,0 +1,72 @@
+/*
+ * Transportr
+ *
+ * Copyright (c) 2013 - 2021 Torsten Grote
+ *
+ * This program is Free Software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package de.grobox.transportr.map
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MediatorLiveData
+import androidx.lifecycle.MutableLiveData
+import de.grobox.transportr.map.GpsMapViewModel.GpsFabState
+import de.grobox.transportr.map.PositionController.PositionState
+
+internal interface GpsMapViewModel {
+
+ val positionController: PositionController
+
+ //todo: change to LiveData
+ val isCameraTracking: MutableLiveData
+ val isPositionStale: MutableLiveData
+ val gpsFabState: LiveData
+
+ enum class GpsFabState {
+ DISABLED,
+ ENABLED,
+ TRACKING
+ }
+}
+
+internal class GpsMapViewModelImpl(override val positionController: PositionController) : GpsMapViewModel {
+ override val isCameraTracking = MutableLiveData()
+ override val isPositionStale = MutableLiveData()
+ override val gpsFabState = MediatorLiveData().apply {
+ var state = PositionState.DISABLED
+ var isTracking = false
+ var isStale = false
+ fun update() {
+ value = when {
+ state == PositionState.DENIED || state == PositionState.DISABLED || isStale -> GpsFabState.DISABLED
+ state == PositionState.ENABLED && !isTracking -> GpsFabState.ENABLED
+ state == PositionState.ENABLED && isTracking -> GpsFabState.TRACKING
+ else -> value
+ }
+ }
+ addSource(positionController.positionState) {
+ state = it
+ update()
+ }
+ addSource(isCameraTracking) {
+ isTracking = it
+ update()
+ }
+ addSource(isPositionStale) {
+ isStale = it
+ update()
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/de/grobox/transportr/map/MapActivity.java b/app/src/main/java/de/grobox/transportr/map/MapActivity.java
index f8fd7c38d..6713762c1 100644
--- a/app/src/main/java/de/grobox/transportr/map/MapActivity.java
+++ b/app/src/main/java/de/grobox/transportr/map/MapActivity.java
@@ -53,6 +53,7 @@
import static com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_COLLAPSED;
import static com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED;
import static com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_HIDDEN;
+import static de.grobox.transportr.locations.WrapLocation.WrapType.GPS;
import static de.grobox.transportr.trips.search.DirectionsActivity.ACTION_SEARCH;
import static de.grobox.transportr.utils.Constants.WRAP_LOCATION;
import static de.grobox.transportr.utils.IntentUtils.findDirections;
@@ -65,7 +66,7 @@ public class MapActivity extends DrawerActivity implements LocationViewListener
@Inject ViewModelProvider.Factory viewModelFactory;
private MapViewModel viewModel;
- private GpsController gpsController;
+ private PositionController positionController;
private LocationView search;
private BottomSheetBehavior bottomSheetBehavior;
@@ -105,7 +106,7 @@ public void onSlide(@NonNull View bottomSheet, float slideOffset) {
// get view model and observe data
viewModel = new ViewModelProvider(this, viewModelFactory).get(MapViewModel.class);
- gpsController = viewModel.getGpsController();
+ positionController = viewModel.getPositionController();
viewModel.getTransportNetwork().observe(this, this::onTransportNetworkChanged);
viewModel.getHome().observe(this, homeLocation -> search.setHomeLocation(homeLocation));
viewModel.getWork().observe(this, workLocation -> search.setWorkLocation(workLocation));
@@ -120,7 +121,7 @@ public void onSlide(@NonNull View bottomSheet, float slideOffset) {
FloatingActionButton directionsFab = findViewById(R.id.directionsFab);
directionsFab.setOnClickListener(view -> {
- WrapLocation from = gpsController.getWrapLocation();
+ WrapLocation from = new WrapLocation(GPS); //locationController.getWrapLocation(); //TODO!!!
WrapLocation to = null;
if (locationFragment != null && locationFragmentVisible()) {
to = locationFragment.getLocation();
diff --git a/app/src/main/java/de/grobox/transportr/map/MapDrawer.kt b/app/src/main/java/de/grobox/transportr/map/MapDrawer.kt
index d78f034f8..2df7ef747 100644
--- a/app/src/main/java/de/grobox/transportr/map/MapDrawer.kt
+++ b/app/src/main/java/de/grobox/transportr/map/MapDrawer.kt
@@ -53,8 +53,8 @@ internal abstract class MapDrawer(protected val context: Context) {
protected fun zoomToBounds(map: MapboxMap, builder: LatLngBounds.Builder, animate: Boolean) {
try {
val latLngBounds = builder.build()
- val padding = context.resources.getDimensionPixelSize(R.dimen.mapPadding)
- val cameraUpdate = CameraUpdateFactory.newLatLngBounds(latLngBounds, padding)
+ val padding = BaseMapFragment.MapPadding(map.cameraPosition.padding) + context.resources.getDimensionPixelSize(R.dimen.mapPadding)
+ val cameraUpdate = CameraUpdateFactory.newLatLngBounds(latLngBounds, padding.left, padding.top, padding.right, padding.bottom)
if (animate) {
map.easeCamera(cameraUpdate, 750)
} else {
@@ -72,4 +72,4 @@ internal abstract class MapDrawer(protected val context: Context) {
return iconFactory.fromBitmap(bitmap)
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/de/grobox/transportr/map/MapFragment.kt b/app/src/main/java/de/grobox/transportr/map/MapFragment.kt
index 2fb1c8e7b..271046a14 100644
--- a/app/src/main/java/de/grobox/transportr/map/MapFragment.kt
+++ b/app/src/main/java/de/grobox/transportr/map/MapFragment.kt
@@ -46,30 +46,26 @@ import de.schildbach.pte.dto.NearbyLocationsResult
import de.schildbach.pte.dto.NearbyLocationsResult.Status.OK
import javax.inject.Inject
-class MapFragment : GpsMapFragment(), LoaderCallbacks, OnMarkerClickListener {
+internal class MapFragment : GpsMapFragment(), LoaderCallbacks, OnMarkerClickListener {
@Inject
internal lateinit var viewModelFactory: ViewModelProvider.Factory
- private lateinit var viewModel: MapViewModel
+ override lateinit var viewModel: MapViewModel
private lateinit var nearbyStationsDrawer: NearbyStationsDrawer
private var selectedLocationMarker: Marker? = null
- override var useGeoCoder: Boolean = true
-
override val layout: Int
@LayoutRes
get() = R.layout.fragment_map
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
- val v = super.onCreateView(inflater, container, savedInstanceState)
-
component.inject(this)
-
viewModel = ViewModelProvider(activity!!, viewModelFactory).get(MapViewModel::class.java)
- viewModel.transportNetwork.observe(viewLifecycleOwner, Observer { onTransportNetworkChanged(it) })
- gpsController = viewModel.gpsController
+
+ val v = super.onCreateView(inflater, container, savedInstanceState)
+ viewModel.transportNetwork.observe(viewLifecycleOwner) { onTransportNetworkChanged(it) }
nearbyStationsDrawer = NearbyStationsDrawer(context)
@@ -83,8 +79,8 @@ class MapFragment : GpsMapFragment(), LoaderCallbacks, On
val args = NearbyLocationsLoader.getBundle(location, 0)
LoaderManager.getInstance(this).initLoader(LOADER_NEARBY_STATIONS, args, this)
- mapboxMap.addOnMapClickListener { viewModel.mapClicked.call() }
- mapboxMap.addOnMapLongClickListener { point -> viewModel.selectLocation(WrapLocation(point)) }
+ mapboxMap.addOnMapClickListener { viewModel.mapClicked.call(); false }
+ mapboxMap.addOnMapLongClickListener { point -> viewModel.selectLocation(WrapLocation(point)); false }
mapboxMap.setOnMarkerClickListener(this)
if (viewModel.transportNetworkWasChanged || mapboxMap.isInitialPosition()) {
@@ -105,14 +101,14 @@ class MapFragment : GpsMapFragment(), LoaderCallbacks, On
private fun zoomInOnFreshStart() {
// zoom to favorite locations or only current location, if no favorites exist
- viewModel.liveBounds.observe(this, Observer { bounds ->
+ viewModel.liveBounds.observe(this) { bounds ->
if (bounds != null) {
zoomToBounds(bounds)
} else if (getLastKnownLocation() != null) {
- zoomToMyLocation()
+ map?.zoomToMyLocation()
}
viewModel.liveBounds.removeObservers(this)
- })
+ }
}
override fun onMarkerClick(marker: Marker): Boolean {
diff --git a/app/src/main/java/de/grobox/transportr/map/MapViewModel.kt b/app/src/main/java/de/grobox/transportr/map/MapViewModel.kt
index 5ccb8f9a9..6bafb04f4 100644
--- a/app/src/main/java/de/grobox/transportr/map/MapViewModel.kt
+++ b/app/src/main/java/de/grobox/transportr/map/MapViewModel.kt
@@ -48,7 +48,8 @@ internal class MapViewModel @Inject internal constructor(
transportNetworkManager: TransportNetworkManager,
locationRepository: LocationRepository,
searchesRepository: SearchesRepository,
- val gpsController: GpsController) : SavedSearchesViewModel(application, transportNetworkManager, locationRepository, searchesRepository) {
+ override val positionController: PositionController
+ ) : SavedSearchesViewModel(application, transportNetworkManager, locationRepository, searchesRepository), GpsMapViewModel by GpsMapViewModelImpl(positionController) {
private val peekHeight = MutableLiveData()
private val selectedLocationClicked = MutableLiveData()
@@ -84,7 +85,6 @@ internal class MapViewModel @Inject internal constructor(
selectedLocation.value = location
// do not reset the selected location right away, will break incoming geo intent
// the observing fragment will call clearSelectedLocation() instead when it is done
- gpsController.updateGpsState(isTracking = false)
}
fun clearSelectedLocation() {
@@ -131,7 +131,7 @@ internal class MapViewModel @Inject internal constructor(
.toMutableSet()
home.value?.let { if (it.hasLocation()) points.add(it.latLng) }
work.value?.let { if (it.hasLocation()) points.add(it.latLng) }
- gpsController.getWrapLocation()?.let { if (it.hasLocation()) points.add(it.latLng) }
+ //locationController.getWrapLocation()?.let { if (it.hasLocation()) points.add(it.latLng) } //TODO!!!!
if (points.size < 2) {
updatedLiveBounds.setValue(null)
} else {
diff --git a/app/src/main/java/de/grobox/transportr/map/PositionController.kt b/app/src/main/java/de/grobox/transportr/map/PositionController.kt
new file mode 100644
index 000000000..fa6da8eda
--- /dev/null
+++ b/app/src/main/java/de/grobox/transportr/map/PositionController.kt
@@ -0,0 +1,178 @@
+/*
+ * Transportr
+ *
+ * Copyright (c) 2013 - 2021 Torsten Grote
+ *
+ * This program is Free Software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package de.grobox.transportr.map
+
+
+import android.Manifest.permission.ACCESS_FINE_LOCATION
+import android.content.Context
+import android.content.pm.PackageManager
+import android.location.Location
+import android.location.LocationListener
+import android.location.LocationManager
+import android.location.LocationManager.GPS_PROVIDER
+import android.location.LocationManager.NETWORK_PROVIDER
+import android.os.Bundle
+import android.os.Looper
+import androidx.annotation.RequiresPermission
+import androidx.annotation.WorkerThread
+import androidx.core.content.ContextCompat
+import androidx.core.location.LocationManagerCompat
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MediatorLiveData
+import androidx.lifecycle.MutableLiveData
+import de.grobox.transportr.AbstractManager
+import de.grobox.transportr.locations.ReverseGeocoder
+import de.grobox.transportr.locations.WrapLocation
+import de.grobox.transportr.utils.NotifyingLiveData
+import java.util.concurrent.TimeUnit
+
+//todo: maybe move listeners to internal private class instead
+internal class PositionController(val context: Context)
+ : AbstractManager(), NotifyingLiveData.OnActivationCallback, ReverseGeocoder.ReverseGeocoderCallback, LocationListener {
+
+ private val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
+ private val geoCoder = ReverseGeocoder(context, this)
+
+ private val _position = NotifyingLiveData(this)
+ private val _positionState = MutableLiveData()
+ private val _positionName = MediatorLiveData().apply {
+ addSource(_position) {
+ geoCoder.findLocation(it)
+ }
+ }
+
+ val position: LiveData = _position
+ val positionState: LiveData = _positionState
+ val positionName: LiveData = _positionName
+
+ init {
+ _positionState.value = when {
+ ContextCompat.checkSelfPermission(context, ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED ->
+ PositionState.DENIED
+ LocationManagerCompat.isLocationEnabled(locationManager) ->
+ PositionState.ENABLED
+ else -> PositionState.DISABLED
+ }
+ }
+
+ fun permissionGranted() {
+ _positionState.value = PositionState.DISABLED
+ }
+
+ @RequiresPermission(ACCESS_FINE_LOCATION)
+ override fun onActive() {
+ //todo: check this without having the permission!
+ for (provider in LOCATION_PROVIDERS) {
+ locationManager.requestLocationUpdates(provider, MIN_UPDATE_INTERVAL, MIN_UPDATE_DISTANCE, this, Looper.getMainLooper())
+ }
+ }
+
+ override fun onInactive() {
+ for (provider in LOCATION_PROVIDERS) {
+ locationManager.removeUpdates(this)
+ }
+ }
+
+ override fun onLocationChanged(location: Location) {
+ if (isBetterPosition(location, _position.value)) {
+ _position.value = location
+ }
+ }
+
+ @Deprecated("Deprecated in Java")
+ override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {}
+
+ override fun onProviderEnabled(provider: String) {
+ if (provider == GPS_PROVIDER && LocationManagerCompat.isLocationEnabled(locationManager)) {
+ _positionState.value = PositionState.ENABLED
+ }
+ }
+
+ override fun onProviderDisabled(provider: String) {
+ if (provider == GPS_PROVIDER && !LocationManagerCompat.isLocationEnabled(locationManager)) {
+ _positionState.value = PositionState.DISABLED
+ }
+ }
+
+ @WorkerThread
+ override fun onLocationRetrieved(location: WrapLocation) {
+ _positionName.postValue(location)
+ }
+
+ companion object {
+ val LOCATION_PROVIDERS = arrayOf(GPS_PROVIDER, NETWORK_PROVIDER)
+ val FIX_EXPIRY = TimeUnit.SECONDS.toMillis(5)
+ val MIN_UPDATE_INTERVAL = TimeUnit.SECONDS.toMillis(1)
+ const val MIN_UPDATE_DISTANCE = 0.0f
+ const val ACCURACY_THRESHOLD_METERS = 200
+
+ /**
+ * Determines whether one position reading is better than the current position fix
+ *
+ *
+ * (c) https://developer.android.com/guide/topics/location/strategies
+ *
+ * @param position The new Location that you want to evaluate
+ * @param currentBestPosition The current Location fix, to which you want to compare the new one
+ */
+ fun isBetterPosition(position: Location?, currentBestPosition: Location?): Boolean {
+ if (position == null) {
+ return false
+ }
+ if (currentBestPosition == null) {
+ // A new location is always better than no location
+ return true
+ }
+
+ // Check whether the new location fix is newer or older
+ val timeDelta = position.time - currentBestPosition.time
+ val isSignificantlyNewer = timeDelta > TimeUnit.MINUTES.toMillis(2)
+ val isSignificantlyOlder = timeDelta < -TimeUnit.MINUTES.toMillis(2)
+ val isNewer = timeDelta > 0
+
+ // Check whether the new location fix is more or less accurate
+ val accuracyDelta = (position.accuracy - currentBestPosition.accuracy).toInt()
+ val isLessAccurate = accuracyDelta > 0
+ val isMoreAccurate = accuracyDelta < 0
+ val isSignificantlyLessAccurate = accuracyDelta > ACCURACY_THRESHOLD_METERS
+
+ // Check if the old and new location are from the same provider
+ val isFromSameProvider = position.provider.equals(currentBestPosition.provider)
+
+ // Determine location quality using a combination of timeliness and accuracy
+ return when {
+ // the user has likely moved
+ isSignificantlyNewer -> true
+ // If the new location is more than two minutes older, it must be worse
+ isSignificantlyOlder -> return false
+ isMoreAccurate -> true
+ isNewer && !isLessAccurate -> true
+ isNewer && !isSignificantlyLessAccurate && isFromSameProvider -> true
+ else -> false
+ }
+ }
+ }
+
+ enum class PositionState {
+ DENIED,
+ DISABLED,
+ ENABLED,
+ }
+}
diff --git a/app/src/main/java/de/grobox/transportr/map/SavedSearchesFragment.kt b/app/src/main/java/de/grobox/transportr/map/SavedSearchesFragment.kt
index bd2ea49ed..f62380fae 100644
--- a/app/src/main/java/de/grobox/transportr/map/SavedSearchesFragment.kt
+++ b/app/src/main/java/de/grobox/transportr/map/SavedSearchesFragment.kt
@@ -49,7 +49,7 @@ internal class SavedSearchesFragment : FavoriteTripsFragment() {
}
override fun onSpecialLocationClicked(location: WrapLocation) {
- val from = viewModel.gpsController.getWrapLocation() ?: WrapLocation(GPS)
+ val from = /* viewModel.locationController.getWrapLocation() ?:*/ WrapLocation(GPS) //TODO
findDirections(context, from, null, location, true, true)
}
diff --git a/app/src/main/java/de/grobox/transportr/trips/detail/TripDetailViewModel.kt b/app/src/main/java/de/grobox/transportr/trips/detail/TripDetailViewModel.kt
index b351ddf0e..ce9a28600 100644
--- a/app/src/main/java/de/grobox/transportr/trips/detail/TripDetailViewModel.kt
+++ b/app/src/main/java/de/grobox/transportr/trips/detail/TripDetailViewModel.kt
@@ -27,7 +27,9 @@ import com.mapbox.mapboxsdk.geometry.LatLngBounds
import de.grobox.transportr.R
import de.grobox.transportr.TransportrApplication
import de.grobox.transportr.locations.WrapLocation
-import de.grobox.transportr.map.GpsController
+import de.grobox.transportr.map.GpsMapViewModel
+import de.grobox.transportr.map.GpsMapViewModelImpl
+import de.grobox.transportr.map.PositionController
import de.grobox.transportr.networks.TransportNetworkManager
import de.grobox.transportr.networks.TransportNetworkViewModel
import de.grobox.transportr.settings.SettingsManager
@@ -40,11 +42,13 @@ import de.schildbach.pte.dto.Trip
import de.schildbach.pte.dto.Trip.Leg
import javax.inject.Inject
-class TripDetailViewModel @Inject internal constructor(
- application: TransportrApplication,
- transportNetworkManager: TransportNetworkManager,
- val gpsController: GpsController,
- private val settingsManager: SettingsManager) : TransportNetworkViewModel(application, transportNetworkManager), LegClickListener {
+internal class TripDetailViewModel
+@Inject internal constructor(
+ application: TransportrApplication,
+ transportNetworkManager: TransportNetworkManager,
+ override val positionController: PositionController,
+ private val settingsManager: SettingsManager
+) : TransportNetworkViewModel(application, transportNetworkManager), LegClickListener, GpsMapViewModel by GpsMapViewModelImpl(positionController) {
enum class SheetState {
BOTTOM, MIDDLE, EXPANDED
@@ -112,5 +116,4 @@ class TripDetailViewModel @Inject internal constructor(
TripReloader(network.networkProvider, settingsManager, query, trip, errorString, tripReloadError)
.reload()
}
-
}
diff --git a/app/src/main/java/de/grobox/transportr/trips/detail/TripMapFragment.kt b/app/src/main/java/de/grobox/transportr/trips/detail/TripMapFragment.kt
index 563b0d8e7..df31093f7 100644
--- a/app/src/main/java/de/grobox/transportr/trips/detail/TripMapFragment.kt
+++ b/app/src/main/java/de/grobox/transportr/trips/detail/TripMapFragment.kt
@@ -34,7 +34,7 @@ import de.grobox.transportr.map.GpsMapFragment
import de.schildbach.pte.dto.Trip
import javax.inject.Inject
-class TripMapFragment : GpsMapFragment() {
+internal class TripMapFragment : GpsMapFragment() {
companion object {
@JvmField
@@ -48,31 +48,24 @@ class TripMapFragment : GpsMapFragment() {
@Inject
internal lateinit var viewModelFactory: ViewModelProvider.Factory
- private lateinit var viewModel: TripDetailViewModel
+ override lateinit var viewModel: TripDetailViewModel
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
- val v = super.onCreateView(inflater, container, savedInstanceState)
-
component.inject(this)
viewModel = ViewModelProvider(activity!!, viewModelFactory).get(TripDetailViewModel::class.java)
- gpsController = viewModel.gpsController
- return v
+ return super.onCreateView(inflater, container, savedInstanceState)
}
override fun onMapReady(mapboxMap: MapboxMap) {
super.onMapReady(mapboxMap)
- // set padding, so everything gets centered in top half of screen
- val metrics = DisplayMetrics()
- activity!!.windowManager.defaultDisplay.getMetrics(metrics)
- val topPadding = mapPadding / 2
- val bottomPadding = mapView.height / 4
- map!!.setPadding(0, topPadding, 0, bottomPadding)
+ // set bottom padding, so everything gets centered in top half of screen
+ setPadding(bottom = mapView.height / 2)
- viewModel.getTrip().observe(this, Observer { onTripChanged(it) })
- viewModel.getZoomLocation().observe(this, Observer { this.animateTo(it) })
- viewModel.getZoomLeg().observe(this, Observer { this.animateToBounds(it) })
+ viewModel.getTrip().observe(this) { onTripChanged(it) }
+ viewModel.getZoomLocation().observe(this) { this.animateTo(it) }
+ viewModel.getZoomLeg().observe(this) { this.animateToBounds(it) }
}
private fun onTripChanged(trip: Trip?) {
diff --git a/app/src/main/java/de/grobox/transportr/trips/search/DirectionsViewModel.kt b/app/src/main/java/de/grobox/transportr/trips/search/DirectionsViewModel.kt
index 9273ccfe2..cd396b4c9 100644
--- a/app/src/main/java/de/grobox/transportr/trips/search/DirectionsViewModel.kt
+++ b/app/src/main/java/de/grobox/transportr/trips/search/DirectionsViewModel.kt
@@ -26,9 +26,9 @@ import de.grobox.transportr.data.locations.FavoriteLocation.FavLocationType
import de.grobox.transportr.data.locations.LocationRepository
import de.grobox.transportr.data.searches.SearchesRepository
import de.grobox.transportr.favorites.trips.SavedSearchesViewModel
-import de.grobox.transportr.locations.LocationLiveData
import de.grobox.transportr.locations.LocationView.LocationViewListener
import de.grobox.transportr.locations.WrapLocation
+import de.grobox.transportr.map.PositionController
import de.grobox.transportr.networks.TransportNetworkManager
import de.grobox.transportr.networks.getTransportNetwork
import de.grobox.transportr.settings.SettingsManager
@@ -47,7 +47,7 @@ import javax.inject.Inject
class DirectionsViewModel @Inject internal constructor(
application: TransportrApplication, transportNetworkManager: TransportNetworkManager, settingsManager: SettingsManager,
- locationRepository: LocationRepository, searchesRepository: SearchesRepository
+ locationRepository: LocationRepository, searchesRepository: SearchesRepository, private val positionController: PositionController
) : SavedSearchesViewModel(application, transportNetworkManager, locationRepository, searchesRepository), TimeDateListener, LocationViewListener {
private val _tripsRepository: TripsRepository
@@ -55,7 +55,7 @@ class DirectionsViewModel @Inject internal constructor(
private val _viaLocation = MutableLiveData()
val viaSupported: LiveData
private val _toLocation = MutableLiveData()
- val locationLiveData = LocationLiveData(application.applicationContext)
+ val locationLiveData = positionController.positionName
val findGpsLocation = MutableLiveData()
val timeUpdate = LiveTrigger()
private val _now = MutableLiveData(true)
diff --git a/app/src/main/java/de/grobox/transportr/utils/NotifyingLiveData.kt b/app/src/main/java/de/grobox/transportr/utils/NotifyingLiveData.kt
new file mode 100644
index 000000000..96101f7b2
--- /dev/null
+++ b/app/src/main/java/de/grobox/transportr/utils/NotifyingLiveData.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * 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
+ *
+ * http://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.
+ */
+package de.grobox.transportr.utils
+
+import androidx.annotation.MainThread
+import javax.annotation.ParametersAreNonnullByDefault
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.Observer
+import de.grobox.transportr.utils.NotifyingLiveData
+import java.util.concurrent.atomic.AtomicBoolean
+
+//todo: change description!
+/**
+ * A lifecycle-aware observable that sends only new updates after subscription, used for events like
+ * navigation and Snackbar messages.
+ *
+ *
+ * This avoids a common problem with events: on configuration change (like rotation) an update
+ * can be emitted if the observer is active. This LiveData only calls the observable if there's an
+ * explicit call to setValue() or call().
+ *
+ *
+ * Note that only one observer is going to be notified of changes.
+ */
+@ParametersAreNonnullByDefault
+class NotifyingLiveData(private val callback: OnActivationCallback) : MutableLiveData() {
+
+ @MainThread
+ override fun onActive() {
+ super.onActive()
+ callback.onActive()
+ }
+
+ override fun onInactive() {
+ super.onInactive()
+ callback.onInactive()
+ }
+
+ interface OnActivationCallback {
+ fun onActive()
+ fun onInactive()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_map.xml b/app/src/main/res/layout/fragment_map.xml
index c1e9e90c5..9d491aff2 100644
--- a/app/src/main/res/layout/fragment_map.xml
+++ b/app/src/main/res/layout/fragment_map.xml
@@ -8,24 +8,17 @@
-
-
+ tools:text="© JawgMaps | © OSM Contributors"/>
-
\ No newline at end of file
+
diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml
index 13cf411b2..a5d229a76 100644
--- a/app/src/main/res/values-night/themes.xml
+++ b/app/src/main/res/values-night/themes.xml
@@ -8,7 +8,7 @@
- @color/cardview_dark_background
- #ff424242
- - @string/mapbox_style_dark
+ - @string/jawg_style_dark
- #99000000
- @color/cardview_dark_background
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index b4eb6955a..7873d90ab 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -79,7 +79,12 @@ and always knows where you are to not miss where to get off the bus.
This app is Free Software, so you can freely use, study, share and improve it. Contributions are encouraged and appreciated. If you would like to contribute, please visit the homepage for more information.
Visit Website
- © <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> © <a href="https://www.mapbox.com/about/maps/">Mapbox</a> <b><a href="https://www.mapbox.com/map-feedback/">%1$s</a></b>
+ https://api.jawg.io/styles/
+ KxO8lF4U3kiO63m0c7lzqDCDrMUVg1OA2JVzRXxxmYSyjugr1xpe4W4Db5rFNvbQ
+ jawg-light
+ jawg-dark
+
+ <a href="http://jawg.io" title="Tiles Courtesy of Jawg Maps">© <b>Jawg</b>Maps</a>, <a href="https://www.openstreetmap.org/copyright" title="OpenStreetMap is open data licensed under ODbL">© OSM contributors</a> | <b><a href="https://www.openstreetmap.org/fixthemap">%1$s</a></b>
Improve this map
Open Navigation Drawer
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 0b206c6b9..4504122fa 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -53,21 +53,10 @@
-
-
-
-
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
index d21281ec3..95cdcc27d 100644
--- a/app/src/main/res/values/themes.xml
+++ b/app/src/main/res/values/themes.xml
@@ -19,7 +19,7 @@