commit eff6100eb59979db4c0544efa9a8e3344d1eb7f0 Author: Fabio Mazza Date: Wed Apr 23 12:06:41 2025 +0200 Restore map state on re-drawing of the map diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/MapLibreFragment.kt b/app/src/main/java/it/reyboz/bustorino/fragments/MapLibreFragment.kt index 8d30067..5c29d21 100644 --- a/app/src/main/java/it/reyboz/bustorino/fragments/MapLibreFragment.kt +++ b/app/src/main/java/it/reyboz/bustorino/fragments/MapLibreFragment.kt @@ -124,7 +124,7 @@ class MapLibreFragment : ScreenBaseFragment(), OnMapReadyCallback { private lateinit var centerUserButton: ImageButton private lateinit var followUserButton: ImageButton private var followingUserLocation = false - private var shouldShowLocation = false + private var pendingLocationActivation = false private var ignoreCameraMovementForFollowing = true private var enablingPositionFromClick = false private val positionRequestLauncher = @@ -181,6 +181,11 @@ class MapLibreFragment : ScreenBaseFragment(), OnMapReadyCallback { private var initialStopToShow : Stop? = null private var initialStopShown = false + //shown stuff + private var savedStateOnStop : Bundle? = null + private var savedMapStateOnPause : Bundle? = null + private var shownStopInBottomSheet : Stop? = null + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) arguments?.let { @@ -204,9 +209,13 @@ class MapLibreFragment : ScreenBaseFragment(), OnMapReadyCallback { // Init the MapView mapView = rootView.findViewById(R.id.libreMapView) + if(savedStateOnStop!=null){ + mapView.onCreate(savedStateOnStop) + } else mapView.onCreate(savedInstanceState) mapView.getMapAsync(this) //{ //map -> //map.setStyle("https://demotiles.maplibre.org/style.json") } + //init bottom sheet val bottomSheet = rootView.findViewById(R.id.bottom_sheet) bottomLayout = bottomSheet @@ -277,12 +286,10 @@ class MapLibreFragment : ScreenBaseFragment(), OnMapReadyCallback { */ override fun onMapReady(mapReady: MapLibreMap) { this.map = mapReady - //TODO: Check if we have the user last position and start the map there val mjson = Styles.getJsonStyleFromAsset(requireContext(), "map_style_good_noshops.json") //ViewUtils.loadJsonFromAsset(requireContext(),"map_style_good.json") - activity?.run { val builder = Style.Builder().fromJson(mjson!!) @@ -310,6 +317,14 @@ class MapLibreFragment : ScreenBaseFragment(), OnMapReadyCallback { //init stop layer with this displayStops(stopsViewModel.getAllStopsLoaded()) setupBusLayer(style) + + savedMapStateOnPause?.let{ + restoreMapStateFromBundle(it) + pendingLocationActivation = false + Log.d(DEBUG_TAG, "Restored map state from the saved bundle") + } + //reset saved State at the end + } mapReady.addOnCameraIdleListener { @@ -377,13 +392,17 @@ class MapLibreFragment : ScreenBaseFragment(), OnMapReadyCallback { // we start requesting the bus positions now startRequestingPositions() } - - val zoom = 15.0 - //center position - val latlngTarget = initialStopToShow?.let { - LatLng(it.latitude!!, it.longitude!!) } ?: LatLng(DEFAULT_CENTER_LAT, DEFAULT_CENTER_LON) - mapReady.cameraPosition = CameraPosition.Builder().target(latlngTarget).zoom(zoom).build() - + if( savedMapStateOnPause == null) { + //set initial state + val zoom = 15.0 + //center position + val latlngTarget = initialStopToShow?.let { + LatLng(it.latitude!!, it.longitude!!) + } ?: LatLng(DEFAULT_CENTER_LAT, DEFAULT_CENTER_LON) + mapReady.cameraPosition = CameraPosition.Builder().target(latlngTarget).zoom(zoom).build() + } + //reset saved state + savedMapStateOnPause = null } @@ -429,16 +448,6 @@ class MapLibreFragment : ScreenBaseFragment(), OnMapReadyCallback { PropertyFactory.iconRotate(Expression.get("bearing")), PropertyFactory.iconRotationAlignment(ICON_ROTATION_ALIGNMENT_MAP) - /*PropertyFactory.textField("label"), - PropertyFactory.textSize(12f), - PropertyFactory.textColor(Color.BLACK), - //PropertyFactory.textHaloColor(Color.BLACK), - //PropertyFactory.textHaloWidth(1f), - PropertyFactory.textAnchor(TEXT_ANCHOR_CENTER), - PropertyFactory.textAllowOverlap(true), - PropertyFactory.textRotationAlignment(TEXT_ROTATION_ALIGNMENT_VIEWPORT) - - */ ) } style.addLayerAbove(busesLayer, STOPS_LAYER_ID) @@ -488,20 +497,6 @@ class MapLibreFragment : ScreenBaseFragment(), OnMapReadyCallback { } //add stop marker if (stop.latitude!=null && stop.longitude!=null) { - /*val marker = map?.addMarker( - MarkerOptions() - .position(LatLng(stop.latitude!!, stop.longitude!!)) // example coords - .icon( - //IconFactory.getInstance(requireContext()).fromBitmap( - getIconFromVectorDrawable(requireContext(), R.drawable.bus_stop_new_highlight) - //R.drawable.bus_stop_new_highlight) - //IconFactory.getInstance(requireContext()) - //.fromResource(R.drawable.bus_stop_new_highlight) - ) - .title(stop.stopDefaultName) - - ) - */ stopActiveSymbol = symbolManager.create( SymbolOptions() .withLatLng(LatLng(stop.latitude!!, stop.longitude!!)) @@ -511,6 +506,7 @@ class MapLibreFragment : ScreenBaseFragment(), OnMapReadyCallback { ) } + shownStopInBottomSheet = stop bottomSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED } @@ -564,12 +560,18 @@ class MapLibreFragment : ScreenBaseFragment(), OnMapReadyCallback { super.onPause() mapView.onPause() Log.d(DEBUG_TAG, "Fragment paused") + + savedMapStateOnPause = saveMapStateInBundle() } override fun onStop() { super.onStop() mapView.onStop() Log.d(DEBUG_TAG, "Fragment stopped!") + savedStateOnStop = Bundle().let { + mapView.onSaveInstanceState(it) + it + } } @@ -591,6 +593,7 @@ class MapLibreFragment : ScreenBaseFragment(), OnMapReadyCallback { override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) mapView.onSaveInstanceState(outState) + Log.d(DEBUG_TAG, "Saved instanceState") } private fun observeViewModels() { @@ -664,6 +667,8 @@ class MapLibreFragment : ScreenBaseFragment(), OnMapReadyCallback { if(initialStopToShow!=null){ initialStopToShow = null } + + shownStopInBottomSheet = null } // --------------- BUS LOCATIONS STUFF -------------------------- /** @@ -893,37 +898,35 @@ class MapLibreFragment : ScreenBaseFragment(), OnMapReadyCallback { @SuppressLint("MissingPermission") private fun requestInitialUserLocation() { - val provider :String? = LocationManager.GPS_PROVIDER//getBestLocationProvider() - - provider?.let { - setLocationIconEnabled(true) - Toast.makeText(requireContext(), R.string.position_searching_message, Toast.LENGTH_SHORT).show() - shouldShowLocation = true - locationManager.requestSingleUpdate(it, object : LocationListener { - override fun onLocationChanged(location: Location) { - val userLatLng = LatLng(location.latitude, location.longitude) - val distanceToTarget = userLatLng.distanceTo(DEFAULT_LATLNG) - - if (distanceToTarget <= MAX_DIST_KM*1000.0) { - map?.let{ - // if we are still waiting for the position to enable - if(shouldShowLocation) - setMapLocationEnabled(true, true, false) - } - } else { - Toast.makeText(context, "You are too far, not showing the position", Toast.LENGTH_SHORT).show() + val provider : String = LocationManager.GPS_PROVIDER//getBestLocationProvider() + + //provider.let { + setLocationIconEnabled(true) + Toast.makeText(requireContext(), R.string.position_searching_message, Toast.LENGTH_SHORT).show() + pendingLocationActivation = true + locationManager.requestSingleUpdate(provider, object : LocationListener { + override fun onLocationChanged(location: Location) { + val userLatLng = LatLng(location.latitude, location.longitude) + val distanceToTarget = userLatLng.distanceTo(DEFAULT_LATLNG) + + if (distanceToTarget <= MAX_DIST_KM*1000.0) { + map?.let{ + // if we are still waiting for the position to enable + if(pendingLocationActivation) + setMapLocationEnabled(true, true, false) } + } else { + Toast.makeText(context, "You are too far, not showing the position", Toast.LENGTH_SHORT).show() } + } - override fun onProviderDisabled(provider: String) {} - override fun onProviderEnabled(provider: String) {} + override fun onProviderDisabled(provider: String) {} + override fun onProviderEnabled(provider: String) {} + + @Deprecated("Deprecated in Java") + override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {} + }, null) - @Deprecated("Deprecated in Java") - override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {} - }, null) - } ?: run { - Toast.makeText(context, "No suitable location provider found.", Toast.LENGTH_SHORT).show() - } } /** @@ -1003,7 +1006,7 @@ class MapLibreFragment : ScreenBaseFragment(), OnMapReadyCallback { setLocationIconEnabled(false) if (fromClick) { Toast.makeText(requireContext(), R.string.location_disabled, Toast.LENGTH_SHORT).show() - if(shouldShowLocation) shouldShowLocation=false //Cancel the request for the enablement of the position + if(pendingLocationActivation) pendingLocationActivation=false //Cancel the request for the enablement of the position } } @@ -1036,13 +1039,46 @@ class MapLibreFragment : ScreenBaseFragment(), OnMapReadyCallback { private fun switchUserLocationStatus(view: View?){ - if(shouldShowLocation || locationComponent.isLocationComponentEnabled) setMapLocationEnabled(false, false, true) + if(pendingLocationActivation || locationComponent.isLocationComponentEnabled) setMapLocationEnabled(false, false, true) else{ Log.d(DEBUG_TAG, "Request enable location") setMapLocationEnabled(true, false, true) } } + private fun saveMapStateBeforePause(bundle: Bundle){ + map?.let { + val newBbox = it.projection.visibleRegion.latLngBounds + + bundle.putDouble("center_map_lat", newBbox.center.latitude) + bundle.putDouble("center_map_lon", newBbox.center.longitude) + it.cameraPosition.zoom.let { z-> bundle.putDouble("map_zoom",z) } + } + shownStopInBottomSheet?.let { + bundle.putBundle("shown_stop", it.toBundle()) + } + } + private fun saveMapStateInBundle(): Bundle { + val b = Bundle() + saveMapStateBeforePause(b) + return b + } + + private fun restoreMapStateFromBundle(bundle: Bundle){ + val latCenter = bundle.getDouble("center_map_lat", -10.0) + val lonCenter = bundle.getDouble("center_map_lon",-10.0) + val zoom = bundle.getDouble("map_zoom", -10.0) + if(lonCenter>=0 &&latCenter>=0) map?.let { + val newPos = CameraPosition.Builder().target(LatLng(latCenter,lonCenter)) + if(zoom>0) newPos.zoom(zoom) + it.cameraPosition=newPos.build() + } + val mStop = bundle.getBundle("shown_stop")?.let { + Stop.fromBundle(it) + } + mStop?.let { openStopInBottomSheet(it) } + } + companion object { private const val STOPS_SOURCE_ID = "stops-source" private const val STOPS_LAYER_ID = "stops-layer" @@ -1058,8 +1094,8 @@ class MapLibreFragment : ScreenBaseFragment(), OnMapReadyCallback { private const val POSITION_FOUND_ZOOM = 16.5 private const val NO_POSITION_ZOOM = 17.1 private const val MAX_DIST_KM = 90.0 - private const val ACCESS_TOKEN="KxO8lF4U3kiO63m0c7lzqDCDrMUVg1OA2JVzRXxxmYSyjugr1xpe4W4Db5rFNvbQ" - private const val MAPLIBRE_URL = "https://api.jawg.io/styles/" + //private const val ACCESS_TOKEN="KxO8lF4U3kiO63m0c7lzqDCDrMUVg1OA2JVzRXxxmYSyjugr1xpe4W4Db5rFNvbQ" + //private const val MAPLIBRE_URL = "https://api.jawg.io/styles/" private const val DEBUG_TAG = "BusTO-MapLibreFrag" private const val STOP_ACTIVE_IMG = "Stop-active" @@ -1069,21 +1105,20 @@ class MapLibreFragment : ScreenBaseFragment(), OnMapReadyCallback { * Use this factory method to create a new instance of * this fragment using the provided parameters. * - * @param param1 Parameter 1. - * @param param2 Parameter 2. + * @param stop Eventual stop to center the map into * @return A new instance of fragment MapLibreFragment. */ - // TODO: Use parcelable interface? @JvmStatic fun newInstance(stop: Stop?) = MapLibreFragment().apply { arguments = Bundle().let { + // Cannot use Parcelable as it requires higher version of Android //stop?.let{putParcelable(STOP_TO_SHOW, it)} stop?.toBundle(it) } } - private fun makeStyleUrl(style: String = "jawg-streets") = - "${MAPLIBRE_URL+ style}.json?access-token=${ACCESS_TOKEN}" + //private fun makeStyleUrl(style: String = "jawg-streets") = + // "${MAPLIBRE_URL+ style}.json?access-token=${ACCESS_TOKEN}" private fun makeStyleMapBoxUrl(dark: Boolean) = if(dark) "https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json"