diff --git a/app/build.gradle b/app/build.gradle
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -140,7 +140,7 @@
implementation 'com.readystatesoftware.sqliteasset:sqliteassethelper:2.0.1'
implementation 'com.android.volley:volley:1.2.1'
//maplibre
- implementation 'org.maplibre.gl:android-sdk:12.0.1'
+ implementation 'org.maplibre.gl:android-sdk:13.1.0'
implementation 'org.maplibre.gl:android-sdk-turf:6.0.1'
implementation 'org.maplibre.gl:android-plugin-annotation-v9:3.0.2'
diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/AlertsDialogFragment.kt b/app/src/main/java/it/reyboz/bustorino/fragments/AlertsDialogFragment.kt
--- a/app/src/main/java/it/reyboz/bustorino/fragments/AlertsDialogFragment.kt
+++ b/app/src/main/java/it/reyboz/bustorino/fragments/AlertsDialogFragment.kt
@@ -1,3 +1,20 @@
+/*
+ BusTO - Fragments components
+ Copyright (C) 2026 Fabio Mazza
+
+ 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 it.reyboz.bustorino.fragments
import android.os.Bundle
diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/AlertsFragment.kt b/app/src/main/java/it/reyboz/bustorino/fragments/AlertsFragment.kt
--- a/app/src/main/java/it/reyboz/bustorino/fragments/AlertsFragment.kt
+++ b/app/src/main/java/it/reyboz/bustorino/fragments/AlertsFragment.kt
@@ -1,3 +1,20 @@
+/*
+ BusTO - Fragments components
+ Copyright (C) 2026 Fabio Mazza
+
+ 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 it.reyboz.bustorino.fragments
import android.os.Bundle
diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/BackupImportFragment.kt b/app/src/main/java/it/reyboz/bustorino/fragments/BackupImportFragment.kt
--- a/app/src/main/java/it/reyboz/bustorino/fragments/BackupImportFragment.kt
+++ b/app/src/main/java/it/reyboz/bustorino/fragments/BackupImportFragment.kt
@@ -1,3 +1,20 @@
+/*
+ BusTO - Fragments components
+ Copyright (C) 2024 Fabio Mazza
+
+ 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 it.reyboz.bustorino.fragments
import android.app.Activity
diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/GeneralMapLibreFragment.kt b/app/src/main/java/it/reyboz/bustorino/fragments/GeneralMapLibreFragment.kt
--- a/app/src/main/java/it/reyboz/bustorino/fragments/GeneralMapLibreFragment.kt
+++ b/app/src/main/java/it/reyboz/bustorino/fragments/GeneralMapLibreFragment.kt
@@ -1,3 +1,20 @@
+/*
+ BusTO - Fragments components
+ Copyright (C) 2025 Fabio Mazza
+
+ 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 it.reyboz.bustorino.fragments
import android.animation.ValueAnimator
@@ -28,6 +45,7 @@
import androidx.lifecycle.lifecycleScope
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.gson.JsonObject
+import it.reyboz.bustorino.BuildConfig
import it.reyboz.bustorino.R
import it.reyboz.bustorino.backend.FiveTNormalizer
import it.reyboz.bustorino.backend.LivePositionTripPattern
@@ -48,7 +66,13 @@
import org.maplibre.android.camera.CameraPosition
import org.maplibre.android.geometry.LatLng
import org.maplibre.android.location.LocationComponent
+import org.maplibre.android.location.LocationComponentActivationOptions
import org.maplibre.android.location.LocationComponentOptions
+import org.maplibre.android.location.engine.LocationEngineCallback
+import org.maplibre.android.location.engine.LocationEngineResult
+import org.maplibre.android.location.engine.MapLibreFusedLocationEngineImpl
+import org.maplibre.android.location.modes.CameraMode
+import org.maplibre.android.location.modes.RenderMode
import org.maplibre.android.maps.MapLibreMap
import org.maplibre.android.maps.MapView
import org.maplibre.android.maps.OnMapReadyCallback
@@ -87,6 +111,8 @@
protected lateinit var sharedPreferences: SharedPreferences
protected lateinit var bottomSheetBehavior: BottomSheetBehavior
+ protected lateinit var locationEngine: MapLibreFusedLocationEngineImpl
+
private val preferenceChangeListener = SharedPreferences.OnSharedPreferenceChangeListener(){ pref, key ->
/*when(key){
@@ -134,7 +160,8 @@
//private lateinit var symbolManager: SymbolManager
protected val mapStateViewModel: MapStateViewModel by viewModels()
-
+ protected var locationInitialized = false
+ protected var mapInitialized = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -181,6 +208,8 @@
}
}
+
+
@Deprecated("Deprecated in Java")
override fun onLowMemory() {
mapView.onLowMemory()
@@ -398,6 +427,28 @@
}
}
+ abstract fun onMapLocationComponentInitialized()
+
+ @SuppressLint("MissingPermission")
+ protected fun initializeMapLocationComponent(map: MapLibreMap, context: Context, style: Style?){
+ val mStyle = style ?: map.style
+ mStyle?.let{ style ->
+ locationComponent = map.locationComponent
+ val options = LocationComponentActivationOptions.builder(context, style)
+ .useDefaultLocationEngine(true)
+ .build()
+ locationComponent.activateLocationComponent(options)
+ locationComponent.isLocationComponentEnabled = true
+ //locationComponent.cameraMode = CameraMode.TRACKING
+ //locationComponent.renderMode = RenderMode.COMPASS
+ locationInitialized = true
+ //UI Elements
+ // setLocationIconEnabled(true)
+ //setFollowingUser(true)
+ onMapLocationComponentInitialized()
+ }
+ }
+
/**
* Update function for the bus positions
diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/IntroFragment.kt b/app/src/main/java/it/reyboz/bustorino/fragments/IntroFragment.kt
--- a/app/src/main/java/it/reyboz/bustorino/fragments/IntroFragment.kt
+++ b/app/src/main/java/it/reyboz/bustorino/fragments/IntroFragment.kt
@@ -1,3 +1,20 @@
+/*
+ BusTO - Fragments components
+ Copyright (C) 2023 Fabio Mazza
+
+ 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 it.reyboz.bustorino.fragments
import android.content.Context
diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/LinesDetailFragment.kt b/app/src/main/java/it/reyboz/bustorino/fragments/LinesDetailFragment.kt
--- a/app/src/main/java/it/reyboz/bustorino/fragments/LinesDetailFragment.kt
+++ b/app/src/main/java/it/reyboz/bustorino/fragments/LinesDetailFragment.kt
@@ -89,7 +89,7 @@
private var shouldMapLocationBeReactivated = true
private var toRunWhenMapReady : Runnable? = null
- private var mapInitialized = AtomicBoolean(false)
+ //private var mapInitialized = AtomicBoolean(false)
//private var patternsSpinnerState: Parcelable? = null
@@ -232,7 +232,7 @@
//isBottomSheetShowing = false
//stopsLayerStarted = false
lastStopsSizeShown = 0
- mapInitialized.set(false)
+ mapInitialized = false
val rootView = inflater.inflate(R.layout.fragment_lines_detail, container, false)
//lineID = requireArguments().getString(LINEID_KEY, "")
@@ -528,6 +528,10 @@
}
+ override fun onMapLocationComponentInitialized() {
+ //THIS IS USEFUL ONLY IN MapLibreFragment, the new initialization routine has not been implemented yet TODO
+ }
+
/**
* Switch position icon from activ
*/
@@ -569,7 +573,7 @@
initSymbolManager(mapReady, style)
toRunWhenMapReady?.run()
toRunWhenMapReady = null
- mapInitialized.set(true)
+ mapInitialized = true
if(patternShown!=null){
viewModel.stopsForPatternLiveData.value?.let {
@@ -922,7 +926,7 @@
}
private fun displayPatternWithStopsOnMap(patternWs: MatoPatternWithStops, stopsToSort: List, zoomToPattern: Boolean){
- if(!mapInitialized.get()){
+ if(!mapInitialized){
//set the runnable and do nothing else
Log.d(DEBUG_TAG, "Delaying pattern display to when map is Ready: ${patternWs.pattern.code}")
toRunWhenMapReady = Runnable {
diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/LivePositionsDialogFragment.kt b/app/src/main/java/it/reyboz/bustorino/fragments/LivePositionsDialogFragment.kt
--- a/app/src/main/java/it/reyboz/bustorino/fragments/LivePositionsDialogFragment.kt
+++ b/app/src/main/java/it/reyboz/bustorino/fragments/LivePositionsDialogFragment.kt
@@ -1,3 +1,20 @@
+/*
+ BusTO - Fragments components
+ Copyright (C) 2025 Fabio Mazza
+
+ 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 it.reyboz.bustorino.fragments
import it.reyboz.bustorino.R
diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/MainScreenFragment.java b/app/src/main/java/it/reyboz/bustorino/fragments/MainScreenFragment.java
--- a/app/src/main/java/it/reyboz/bustorino/fragments/MainScreenFragment.java
+++ b/app/src/main/java/it/reyboz/bustorino/fragments/MainScreenFragment.java
@@ -1,4 +1,20 @@
-
+/*
+ BusTO - Fragments components
+ Copyright (C) 2021 Fabio Mazza
+
+ 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 it.reyboz.bustorino.fragments;
import android.Manifest;
diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/MapLibreFragment.kt b/app/src/main/java/it/reyboz/bustorino/fragments/MapLibreFragment.kt
--- a/app/src/main/java/it/reyboz/bustorino/fragments/MapLibreFragment.kt
+++ b/app/src/main/java/it/reyboz/bustorino/fragments/MapLibreFragment.kt
@@ -1,12 +1,27 @@
+/*
+ BusTO - Fragments components
+ Copyright (C) 2025 Fabio Mazza
+
+ 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 it.reyboz.bustorino.fragments
import android.Manifest
import android.annotation.SuppressLint
import android.content.Context
-import android.content.res.ColorStateList
import android.location.Location
-import android.location.LocationListener
import android.location.LocationManager
import android.os.Bundle
import android.util.Log
@@ -16,18 +31,15 @@
import android.widget.ImageButton
import android.widget.RelativeLayout
import android.widget.Toast
-import it.reyboz.bustorino.backend.FiveTNormalizer
-import it.reyboz.bustorino.backend.gtfs.GtfsUtils
import androidx.activity.result.ActivityResultCallback
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.ContextCompat
-import androidx.core.content.res.ResourcesCompat
-import androidx.core.view.ViewCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.preference.PreferenceManager
import androidx.room.concurrent.AtomicBoolean
import com.google.android.material.bottomsheet.BottomSheetBehavior
+import it.reyboz.bustorino.BuildConfig
import it.reyboz.bustorino.R
import it.reyboz.bustorino.backend.Stop
import it.reyboz.bustorino.backend.gtfs.LivePositionUpdate
@@ -41,18 +53,18 @@
import org.maplibre.android.camera.CameraUpdateFactory
import org.maplibre.android.geometry.LatLng
import org.maplibre.android.geometry.LatLngBounds
+import org.maplibre.android.location.LocationComponentActivationOptions
+import org.maplibre.android.location.engine.LocationEngineCallback
+import org.maplibre.android.location.engine.LocationEngineRequest
+import org.maplibre.android.location.engine.LocationEngineResult
import org.maplibre.android.location.modes.CameraMode
+import org.maplibre.android.location.modes.RenderMode
import org.maplibre.android.maps.MapLibreMap
import org.maplibre.android.maps.Style
import org.maplibre.android.plugins.annotation.Symbol
import org.maplibre.geojson.Feature
import org.maplibre.geojson.FeatureCollection
-
-// TODO: Rename parameter arguments, choose names that match
-// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
-private const val STOP_TO_SHOW = "stoptoshow"
-
/**
* A simple [Fragment] subclass.
* Use the [MapLibreFragment.newInstance] factory method to
@@ -68,7 +80,6 @@
private var isUserMovingCamera = false
private var lastStopsSizeShown = 0
private var lastBBox = LatLngBounds.from(2.0, 2.0, 1.0,1.0)
- private var mapInitCompleted =false
private var stopsRedrawnTimes = 0
//bottom Sheet behavior in GeneralMapLibreFragment
@@ -76,72 +87,65 @@
// Location stuff
private lateinit var locationManager: LocationManager
- private lateinit var showUserPositionButton: ImageButton
+ private lateinit var userLocationButton: ImageButton
private lateinit var centerUserButton: ImageButton
private lateinit var followUserButton: ImageButton
private var followingUserLocation = false
- private var pendingLocationActivation = false
private var ignoreCameraMovementForFollowing = true
- private var enablingPositionFromClick = false
private var restoredMapCamera = AtomicBoolean()
- private var permissionsGranted = false
+ private var zoomedToFirstLocation = false
- //TODO: Rewrite this mess using LocationEngineProvider in MapLibre
- private val positionRequestLauncher = registerForActivityResult, Map>(
- ActivityResultContracts.RequestMultiplePermissions(), ActivityResultCallback { result ->
- if (result == null) {
- Log.w(DEBUG_TAG, "Got asked permission but request is null, doing nothing?")
- }else if(!pendingLocationActivation){
- /// SHOULD DO NOTHING HERE
- Log.d(DEBUG_TAG, "Requested location but now there is no pendingLocationActivation")
- } else if (java.lang.Boolean.TRUE == result[Manifest.permission.ACCESS_COARSE_LOCATION]
- && java.lang.Boolean.TRUE == result[Manifest.permission.ACCESS_FINE_LOCATION]) {
- // We can use the position, restart location overlay
- permissionsGranted = true
- if (context == null || requireContext().getSystemService(Context.LOCATION_SERVICE) == null)
- return@ActivityResultCallback
- val locationManager = requireContext().getSystemService(Context.LOCATION_SERVICE) as LocationManager
- var lastLoc = stopsViewModel.lastUserLocation
- @SuppressLint("MissingPermission")
- if(lastLoc==null) lastLoc = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER)
- else Log.d(DEBUG_TAG, "Got last location from cache")
-
- //FIRST CASE: I have no GPS
- if( !locationManager.allProviders.contains(LocationManager.GPS_PROVIDER) ){
- setMapLocationEnabled(false, false,false)
+ //TODO: Rewrite this mess using LocationEngineProvider in MapLibre
+ private val positionRequestResponder = registerForActivityResult(
+ ActivityResultContracts.RequestMultiplePermissions(), ActivityResultCallback{ res ->
+ if(!(res.containsKey(PERM_LOC_COARSE)&&res.containsKey(PERM_LOC_FINE))){
+ Log.e(DEBUG_TAG, "Location request does not have the correct keys")
+ } else if(res[PERM_LOC_COARSE]!! && res[PERM_LOC_FINE]!!){
+ //permission OK, init map location
+ val mMap = map
+ if(mMap == null){
+ Log.w(DEBUG_TAG, "Location request completed, but map is null!")
+ }else{
+ initializeMapLocationComponent(mMap,requireContext(), null)
+ }
+ } else{
+ // PERMISSION DENIED
+ // TODO find better way to show the necessity of the permission
+ if(shouldShowRequestPermissionRationale(PERM_LOC_FINE))
+ Toast.makeText(requireContext(),
+ R.string.enable_position_message_map, Toast.LENGTH_SHORT).show()
+ }
+ }
+ )
+
+ protected val mapLibreLocationCallback = object : LocationEngineCallback {
+ override fun onSuccess(result: LocationEngineResult) {
+ val location: Location? = result.lastLocation
+ location?.let {
+ if(BuildConfig.DEBUG)
+ Log.d(DEBUG_TAG, "Lat: ${it.latitude}, Lon: ${it.longitude}")
+
+ if(mapInitialized && !zoomedToFirstLocation) {
+ map?.apply{
+ animateCamera(CameraUpdateFactory.newCameraPosition(
+ CameraPosition.Builder().target(LatLng(it.latitude, it.longitude)).build()),
+ 1000)
+ locationComponent.cameraMode = CameraMode.TRACKING
}
- else if (lastLoc != null) {
-
- if(LatLng(lastLoc.latitude, lastLoc.longitude).distanceTo(DEFAULT_LATLNG) <= MAX_DIST_KM*1000){
- Log.d(DEBUG_TAG, "Showing the user position")
- setMapLocationEnabled(true, true, false)
- } else{
- setMapLocationEnabled(false, false,false)
- context?.let{Toast.makeText(it,R.string.too_far_not_showing_location, Toast.LENGTH_SHORT).show()}
- }
- } else requestInitialUserLocation()
-
- } else{
- Toast.makeText(requireContext(),R.string.location_disabled, Toast.LENGTH_SHORT).show()
- Log.w(DEBUG_TAG, "No location permission")
+ zoomedToFirstLocation = true
}
- })
- private val showUserPositionRequestLauncher =
- registerForActivityResult, Map>(
- ActivityResultContracts.RequestMultiplePermissions(),
- ActivityResultCallback { result ->
- if (result == null) {
- Log.w(DEBUG_TAG, "Got asked permission but request is null, doing nothing?")
- } else if (java.lang.Boolean.TRUE == result[Manifest.permission.ACCESS_COARSE_LOCATION]
- && java.lang.Boolean.TRUE == result[Manifest.permission.ACCESS_FINE_LOCATION]) {
- // We can use the position, restart location overlay
- if (context == null || requireContext().getSystemService(Context.LOCATION_SERVICE) == null)
- return@ActivityResultCallback ///@registerForActivityResult
- setMapLocationEnabled(true, true, enablingPositionFromClick)
- } else Log.w(DEBUG_TAG, "No location permission")
- })
+ else{
+ stopsViewModel.lastUserLocation = location
+ }
+ }
+ }
+
+ override fun onFailure(exception: Exception) {
+ Log.e(DEBUG_TAG, "Errore: ${exception.message}")
+ }
+ }
//BUS POSITIONS
private var usingMQTTPositions = true // THIS IS INSIDE VIEW MODEL NOW
@@ -200,8 +204,8 @@
arrivalsCard = bottomSheet.findViewById(R.id.arrivalsCardButton)
directionsCard = bottomSheet.findViewById(R.id.directionsCardButton)
- showUserPositionButton = rootView.findViewById(R.id.locationEnableIcon)
- showUserPositionButton.setOnClickListener(this::switchUserLocationStatus)
+ userLocationButton = rootView.findViewById(R.id.locationEnableIcon)
+ userLocationButton.setOnClickListener(this::switchUserLocationStatus)
followUserButton = rootView.findViewById(R.id.followUserImageButton)
centerUserButton = rootView.findViewById(R.id.centerMapImageButton)
busPositionsIconButton = rootView.findViewById(R.id.busPositionsImageButton)
@@ -239,8 +243,10 @@
setFollowingUser(!followingUserLocation)
}
}
- locationManager = requireActivity().getSystemService(Context.LOCATION_SERVICE) as LocationManager
+ //locationManager = requireActivity().getSystemService(Context.LOCATION_SERVICE) as LocationManager
+ /*
if (Permissions.bothLocationPermissionsGranted(requireContext()) && deviceHasGpsProvider()) {
+
requestInitialUserLocation()
} else{
if (shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_FINE_LOCATION)) {
@@ -251,6 +257,8 @@
// PERMISSIONS REQUESTED AFTER MAP SETUP
}
+ */
+
// Setup close button
rootView.findViewById(R.id.btnClose).setOnClickListener {
@@ -310,7 +318,6 @@
//setupLayers(style)
addImagesStyle(style)
- initMapUserLocation(style, mapReady, requireContext())
//init stop layer with this
val stopsInCache = stopsViewModel.getAllStopsLoaded()
if(stopsInCache.isEmpty())
@@ -321,11 +328,22 @@
// Start observing data now that everything is set up
observeStops()
+
+ //enable location
+ val hasGps = deviceHasGpsProvider()
+ if(hasGps) {
+ if (Permissions.bothLocationPermissionsGranted(context)) {
+ initializeMapLocationComponent(mapReady, context, style)
+ } else {
+ setLocationIconEnabled(false)
+ setFollowingUser(false)
+ //positionRequestResponder.launch(Permissions.LOCATION_PERMISSIONS)
+ }
+ }
}
mapReady.addOnCameraIdleListener {
- isUserMovingCamera = false
map?.let {
val newBbox = it.projection.visibleRegion.latLngBounds
if ((newBbox.center==lastBBox.center) && (newBbox.latitudeSpan==lastBBox.latitudeSpan) && (newBbox.longitudeSpan==lastBBox.latitudeSpan)){
@@ -342,21 +360,20 @@
mapReady.addOnCameraMoveStartedListener { v->
if(v== MapLibreMap.OnCameraMoveStartedListener.REASON_API_GESTURE){
//the user is moving the map
- isUserMovingCamera = true
+ //isUserMovingCamera = true
+ setFollowingUser(false)
}
}
mapReady.addOnMapClickListener { point ->
onMapClickReact(point)
}
-
- mapInitCompleted = true
// we start requesting the bus positions now
observeBusPositionUpdates()
//Restoring data
- if (initialStopToShow!=null){
+ if (initialStopToShow!=null && initialStopToShow?.hasCoords() == true){
val s = initialStopToShow!!
if(s.hasCoords()){
mapReady.cameraPosition = CameraPosition.Builder().target(
@@ -381,15 +398,24 @@
}
if(!boundsRestored){
- mapReady.cameraPosition = CameraPosition.Builder().target(
- LatLng(DEFAULT_CENTER_LAT, DEFAULT_CENTER_LON)
- ).zoom(DEFAULT_ZOOM).build()
+ // we have not restored the bounds, open normally in target location
+ val lastLoc = stopsViewModel.lastUserLocation
+ val targetLoc = if(lastLoc == null)
+ LatLng(DEFAULT_CENTER_LAT,DEFAULT_CENTER_LON)
+ else
+ LatLng(lastLoc.latitude, lastLoc.longitude)
+
+ mapReady.cameraPosition = CameraPosition.Builder().target(targetLoc).zoom(DEFAULT_ZOOM).build()
}
restoredMapCamera.set(boundsRestored)
+
+
}
+ mapInitialized = true
+
+ //pendingLocationActivation = true
+ //positionRequestLauncher.launch(Permissions.LOCATION_PERMISSIONS)
- pendingLocationActivation = true
- positionRequestLauncher.launch(Permissions.LOCATION_PERMISSIONS)
}
private fun onMapClickReact(point: LatLng): Boolean{
@@ -627,9 +653,9 @@
livePositionsViewModel.updatesWithTripAndPatterns.observe(viewLifecycleOwner) { data: HashMap> ->
Log.d(
DEBUG_TAG,
- "Have " + data.size + " trip updates, has Map start finished: " + mapInitCompleted
+ "Have " + data.size + " trip updates, has Map start finished: $mapInitialized"
)
- if (mapInitCompleted) updateBusPositionsInMap(data, hasVehicleTracking = true) { veh ->
+ if (mapInitialized) updateBusPositionsInMap(data, hasVehicleTracking = true) { veh ->
showVehicleTripInBottomSheet(veh)
}
if (!isDetached && !livePositionsViewModel.useMQTTPositionsLiveData.value!!) livePositionsViewModel.requestDelayedGTFSUpdates(
@@ -640,6 +666,44 @@
// ------ LOCATION STUFF -----
+
+
+
+ @SuppressLint("MissingPermission")
+ override fun onMapLocationComponentInitialized() {
+ locationComponent.cameraMode = CameraMode.TRACKING
+ locationComponent.renderMode = RenderMode.COMPASS
+ locationComponent.locationEngine?.apply{
+ Log.d(DEBUG_TAG, "Map Location engine: it")
+ //it(mapLibreLocationCallback)
+ // this is only called once
+ getLastLocation(object : LocationEngineCallback {
+ override fun onSuccess(res: LocationEngineResult?) {
+ res?.let {
+ res.lastLocation?.let{ loc ->
+ if(mapInitialized)
+ map?.cameraPosition = CameraPosition.Builder().target(LatLng(loc.latitude, loc.longitude)).build()
+ else
+ stopsViewModel.lastUserLocation = loc
+ }
+ }
+ }
+
+ override fun onFailure(p0: java.lang.Exception) {
+ Log.e(DEBUG_TAG, "Failed to get the last location", p0)
+ }
+
+ })
+ requestLocationUpdates(LocationEngineRequest.Builder(500).setDisplacement(20.0f).build(),
+ mapLibreLocationCallback, null)
+ }
+
+ //UI Elements
+ setLocationIconEnabled(true)
+ setFollowingUser(true)
+ }
+
+ /*
@SuppressLint("MissingPermission")
private fun requestInitialUserLocation() {
val provider : String = LocationManager.GPS_PROVIDER//getBestLocationProvider()
@@ -673,10 +737,11 @@
}
-
+ */
/**
* Handles logic of enabling the user location on the map
*/
+ /*
@SuppressLint("MissingPermission")
private fun setMapLocationEnabled(enabled: Boolean, assumePermissions: Boolean, fromClick: Boolean) {
if (enabled) {
@@ -713,13 +778,21 @@
}
+ */
+
+ @SuppressLint("MissingPermission")
+ private fun setMapLocationEnabled(enabled: Boolean){
+ map?.locationComponent?.isLocationComponentEnabled = enabled
+ //map?.cameraPosition =
+ setLocationIconEnabled(enabled)
+ }
private fun setLocationIconEnabled(enabled: Boolean){
if (enabled)
- showUserPositionButton.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.location_circlew_red))
+ userLocationButton.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.location_circlew_red))
else
- showUserPositionButton.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.location_circlew_grey))
+ userLocationButton.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.location_circlew_grey))
}
@@ -733,27 +806,40 @@
private fun setFollowingUser(following: Boolean){
updateFollowingIcon(following)
followingUserLocation = following
- if(following)
- ignoreCameraMovementForFollowing = true
+ //if(following)
+ // ignoreCameraMovementForFollowing = true
}
/**
- * Method used for enabling / disabling the location
+ * Method used for enabling / disabling the location from the buttons
*/
private fun switchUserLocationStatus(view: View?){
- if(pendingLocationActivation || locationComponent.isLocationComponentEnabled)
- setMapLocationEnabled(false, false, true)
- else{
- if(locationManager.allProviders.contains(LocationManager.GPS_PROVIDER)) {
- pendingLocationActivation = true
- Log.d(DEBUG_TAG, "Request enable location")
- setMapLocationEnabled(true, false, true)
+ val enabled = if(locationInitialized) locationComponent.isLocationComponentEnabled else false
+ val context = context ?: return
+ if(enabled) {
+ setMapLocationEnabled(false)
+ }
+ else if(deviceHasGpsProvider()) {
+ if(Permissions.bothLocationPermissionsGranted(context)){
+ setMapLocationEnabled(true)
} else{
- Log.w(DEBUG_TAG, "Cannot find location, no GPS")
+ Log.d(DEBUG_TAG, "Requesting permissions to show location")
+ if(shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_FINE_LOCATION)){
+ Toast.makeText(context, R.string.enable_position_message_map, Toast.LENGTH_LONG).show()
+ positionRequestResponder.launch(Permissions.LOCATION_PERMISSIONS)
+ } else{
+ //cannot show the dialog anymore, go to the settings
+ openShowAppSettingsLocationDialog()
+ }
+ }
+ } else{
+ context.let {
+ Toast.makeText(it, R.string.no_gps_on_device, Toast.LENGTH_SHORT).show()
}
-
}
+
+
}
@@ -776,6 +862,8 @@
private const val STOP_ACTIVE_IMG = "Stop-active"
const val FRAGMENT_TAG = "BusTOMapFragment"
+ private const val PERM_LOC_COARSE = Manifest.permission.ACCESS_COARSE_LOCATION
+ private const val PERM_LOC_FINE = Manifest.permission.ACCESS_FINE_LOCATION
private const val LOCATION_PERMISSION_REQUEST_CODE = 981202
diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/ScreenBaseFragment.java b/app/src/main/java/it/reyboz/bustorino/fragments/ScreenBaseFragment.java
--- a/app/src/main/java/it/reyboz/bustorino/fragments/ScreenBaseFragment.java
+++ b/app/src/main/java/it/reyboz/bustorino/fragments/ScreenBaseFragment.java
@@ -2,7 +2,11 @@
import android.Manifest;
import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
import android.content.SharedPreferences;
+import android.net.Uri;
+import android.provider.Settings;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.Toast;
@@ -11,10 +15,12 @@
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.Nullable;
+import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.Fragment;
import com.google.android.material.snackbar.Snackbar;
import it.reyboz.bustorino.BuildConfig;
+import it.reyboz.bustorino.R;
import java.util.Map;
@@ -98,6 +104,25 @@
});
}
+ /**
+ * Show alert dialog to enable location permission
+ */
+ protected void openShowAppSettingsLocationDialog(){
+ Context mContext = getContext();
+ if (mContext==null) return;
+ AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
+
+ builder.setTitle(R.string.no_permission_dialog_title);
+ builder.setMessage(R.string.no_permission_dialog_text_location);
+ builder.setPositiveButton(R.string.no_permission_dialog_open, (dialogInterface, i) -> {
+ final Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
+ intent.setData(Uri.fromParts("package", mContext.getPackageName(), null));
+ startActivity(intent);
+ });
+ builder.setNegativeButton(android.R.string.cancel, null);
+ builder.show();
+ }
+
public interface LocationRequestListener{
void onPermissionResult(boolean isCoarseGranted, boolean isFineGranted);
}
diff --git a/app/src/main/java/it/reyboz/bustorino/viewmodels/ArrivalsViewModel.kt b/app/src/main/java/it/reyboz/bustorino/viewmodels/ArrivalsViewModel.kt
--- a/app/src/main/java/it/reyboz/bustorino/viewmodels/ArrivalsViewModel.kt
+++ b/app/src/main/java/it/reyboz/bustorino/viewmodels/ArrivalsViewModel.kt
@@ -1,3 +1,20 @@
+/*
+ BusTO - View Model components
+ Copyright (C) 2025 Fabio Mazza
+
+ 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 it.reyboz.bustorino.viewmodels
import android.app.Application
diff --git a/app/src/main/java/it/reyboz/bustorino/viewmodels/NearbyStopsViewModel.kt b/app/src/main/java/it/reyboz/bustorino/viewmodels/NearbyStopsViewModel.kt
--- a/app/src/main/java/it/reyboz/bustorino/viewmodels/NearbyStopsViewModel.kt
+++ b/app/src/main/java/it/reyboz/bustorino/viewmodels/NearbyStopsViewModel.kt
@@ -1,3 +1,20 @@
+/*
+ BusTO - View Model components
+ Copyright (C) 2023 Fabio Mazza
+
+ 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 it.reyboz.bustorino.viewmodels
import android.app.Application
diff --git a/app/src/main/java/it/reyboz/bustorino/viewmodels/ServiceAlertsViewModel.kt b/app/src/main/java/it/reyboz/bustorino/viewmodels/ServiceAlertsViewModel.kt
--- a/app/src/main/java/it/reyboz/bustorino/viewmodels/ServiceAlertsViewModel.kt
+++ b/app/src/main/java/it/reyboz/bustorino/viewmodels/ServiceAlertsViewModel.kt
@@ -1,3 +1,20 @@
+/*
+ BusTO - View Model components
+ Copyright (C) 2026 Fabio Mazza
+
+ 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 it.reyboz.bustorino.viewmodels
import android.app.Application
diff --git a/app/src/main/java/it/reyboz/bustorino/viewmodels/StopsMapViewModel.kt b/app/src/main/java/it/reyboz/bustorino/viewmodels/StopsMapViewModel.kt
--- a/app/src/main/java/it/reyboz/bustorino/viewmodels/StopsMapViewModel.kt
+++ b/app/src/main/java/it/reyboz/bustorino/viewmodels/StopsMapViewModel.kt
@@ -1,3 +1,20 @@
+/*
+ BusTO - View Model components
+ Copyright (C) 2025 Fabio Mazza
+
+ 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 it.reyboz.bustorino.viewmodels
import android.app.Application
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -175,6 +175,7 @@
Allow access to location to show it on the map
Allow access to location to show stops nearby
Please enable location on the device
+ No GPS receiver found on the device!
Database update in progress…
Updating the database
Force database update
@@ -238,6 +239,7 @@
Asked for %1$s permission too many times
Cannot use the map with the storage permission!
+
storage
The application has crashed because you encountered a bug.
\nIf you want, you can help the developers by sending the crash report via email.
@@ -342,6 +344,10 @@
Grant location permission
Location permission granted
Location permission has not been granted
+ Missing permission
+
+ To use this functionality, the application needs access to the location, which can not only be granted in the system settings.
+ Open settings
OK, close the tutorial
Close the tutorial