diff --git a/app/src/main/java/it/reyboz/bustorino/ActivityPrincipal.java b/app/src/main/java/it/reyboz/bustorino/ActivityPrincipal.java --- a/app/src/main/java/it/reyboz/bustorino/ActivityPrincipal.java +++ b/app/src/main/java/it/reyboz/bustorino/ActivityPrincipal.java @@ -569,36 +569,31 @@ private void requestMapFragment(final boolean allowReturn){ // starting from Android 11, we don't need to have the STORAGE permission anymore for the map cache - - /*if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.R){ - //nothing to do - Log.d(DEBUG_TAG, "Build codes allow the showing of the map"); - createAndShowMapFragment(null, allowReturn); - return; - } - final String permission = Manifest.permission.WRITE_EXTERNAL_STORAGE; - int result = askForPermissionIfNeeded(permission, STORAGE_PERMISSION_REQ); - Log.d(DEBUG_TAG, "Permission for storage: "+result); - switch (result) { - case PERMISSION_OK: + FragmentManager fm = getSupportFragmentManager(); + Fragment fragment = fm.findFragmentById(R.id.mainActContentFrame); + if(fragment instanceof MapLibreFragment){ + Log.d(DEBUG_TAG, "Requested map fragment, but it is already open"); + } else { + fragment = fm.findFragmentByTag(MapLibreFragment.FRAGMENT_TAG); + if(fragment != null){ + Log.d(DEBUG_TAG, "Found map fragment, reopening it"); + var ft = fm.beginTransaction(); + ft.replace(R.id.mainActContentFrame,fragment, MapLibreFragment.FRAGMENT_TAG); + if(allowReturn) ft.addToBackStack(null); + ft.commit(); + } else { + //create from scratch + //The permissions are handled in the MapLibreFragment instead createAndShowMapFragment(null, allowReturn); - break; - case PERMISSION_ASKING: - permissionDoneRunnables.put(permission, - () -> createAndShowMapFragment(null, allowReturn)); - break; - case PERMISSION_NEG_CANNOT_ASK: - String storage_perm = getString(R.string.storage_permission); - String text = getString(R.string.too_many_permission_asks, storage_perm); - Toast.makeText(getApplicationContext(),text, Toast.LENGTH_LONG).show(); + } } - - */ - //The permissions are handled in the MapLibreFragment instead - createAndShowMapFragment(null, allowReturn); } - private static void checkAndShowFavoritesFragment(FragmentManager fragmentManager, boolean addToBackStack){ + private void checkAndShowFavoritesFragment(FragmentManager fragmentManager, boolean addToBackStack){ + if(getSupportFragmentManager().findFragmentById(R.id.mainActContentFrame) instanceof FavoritesFragment){ + Log.d(DEBUG_TAG, "Requested favorites fragment, but it is already open"); + return; + } FragmentTransaction ft = fragmentManager.beginTransaction(); Fragment fragment = fragmentManager.findFragmentByTag(TAG_FAVORITES); if(fragment!=null){ @@ -614,7 +609,11 @@ ft.commit(); } - private static void showLinesFragment(@NonNull FragmentManager fragmentManager, boolean addToBackStack, @Nullable Bundle fragArgs){ + private void showLinesFragment(@NonNull FragmentManager fragmentManager, boolean addToBackStack, @Nullable Bundle fragArgs){ + if(getSupportFragmentManager().findFragmentById(R.id.mainActContentFrame) instanceof LinesGridShowingFragment){ + Log.d(DEBUG_TAG, "Requested lines grid fragment, but it is already open"); + return; + } FragmentTransaction ft = fragmentManager.beginTransaction(); Fragment f = fragmentManager.findFragmentByTag(LinesGridShowingFragment.FRAGMENT_TAG); if(f!=null){ diff --git a/app/src/main/java/it/reyboz/bustorino/backend/utils.java b/app/src/main/java/it/reyboz/bustorino/backend/utils.java --- a/app/src/main/java/it/reyboz/bustorino/backend/utils.java +++ b/app/src/main/java/it/reyboz/bustorino/backend/utils.java @@ -32,6 +32,8 @@ import androidx.annotation.Nullable; import androidx.preference.PreferenceManager; +import java.math.BigDecimal; +import java.math.RoundingMode; import java.text.SimpleDateFormat; import java.util.*; import java.util.regex.Matcher; @@ -405,4 +407,9 @@ SimpleDateFormat format = new SimpleDateFormat(patternFormat, Locale.getDefault()); return format.format(date); } + + public static Double roundDecimalUsingBigDecimal(Double value, int decimalPlace) { + return new BigDecimal(value).setScale(decimalPlace, + RoundingMode.HALF_UP).stripTrailingZeros().doubleValue(); + } } 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 @@ -132,10 +132,13 @@ if(key == SettingsFragment.LIBREMAP_STYLE_PREF_KEY){ Log.d(DEBUG_TAG,"ASKING RELOAD OF MAP") - reloadMap() + //reloadMap() } } + /** + * What to do when requesting the permission, when it's ok, initialize the map location component + */ protected val positionRequestResponder = registerForActivityResult( ActivityResultContracts.RequestMultiplePermissions(), ActivityResultCallback{ res -> if(!(res.containsKey(PERM_LOC_COARSE)&&res.containsKey(PERM_LOC_FINE))){ @@ -215,7 +218,7 @@ } if(receivedFirstLocation){ - //remove this + //remove this listener once we have received the location locationEngine?.removeLocationUpdates(this) } @@ -275,9 +278,10 @@ mapView?.onResume() val newMapStyle = PreferencesHolder.getMapLibreStyleFile(requireContext()) Log.d(DEBUG_TAG, "onResume newMapStyle: $newMapStyle, lastMapStyle: $lastMapStyle") - if(newMapStyle!=lastMapStyle){ - reloadMap() - } + // TODO: reload style if user changed preferences + //if(newMapStyle!=lastMapStyle){ + // reloadMap() + //} } override fun onLowMemory() { @@ -335,7 +339,6 @@ } */ - //TODO figure out how to switch map safely } //For extra stuff to do when the map is destroyed @@ -474,9 +477,8 @@ .build() locationComponent.activateLocationComponent(options) - if(BuildConfig.DEBUG) Log.d(DEBUG_TAG, "Requesting location updates") - locationEngine!!.requestLocationUpdates(LocationEngineRequest.Builder(500).setDisplacement(20.0f).build(), - mapLibreLocationCallback, null) + if(BuildConfig.DEBUG) Log.d(DEBUG_TAG, "Initializing location, request initial position") + startInitialPositionRequest() if(!locationEnabledOnDevice){ warnLocationNotEnabledOnDevice() @@ -488,6 +490,16 @@ } } + @SuppressLint("MissingPermission") + protected fun startInitialPositionRequest(){ + locationEngine?.requestLocationUpdates(LocationEngineRequest.Builder(500).setDisplacement(20.0f).build(), + mapLibreLocationCallback, null) + + } + protected fun stopInitialPositionRequest(){ + locationEngine?.removeLocationUpdates(mapLibreLocationCallback) + } + /** * Update function for the bus positions @@ -970,6 +982,11 @@ val enabled = if(locationInitialized) locationComponent.isLocationComponentEnabled else false val context = context ?: return if(enabled) { + if(!receivedFirstLocation){ + //use case: the user has decided to disable the location before the first position arrived + stopInitialPositionRequest() + } + // we have to disable it setMapLocationEnabled(false) } else if(deviceHasLocationProvider()) { @@ -987,6 +1004,8 @@ context.let { Toast.makeText(it, R.string.no_gps_on_device, Toast.LENGTH_SHORT).show() } + //adjust ui + setLocationIconEnabled(false) } } @@ -1002,6 +1021,12 @@ mapStateViewModel.locationUserActive.value = enabled onMapLocationEnabled(enabled) } + + /** + * Function to run at the first time the fragment is opened + * Check if we have the permissions, and then initialize the map location component + * If we don't have it, request the permission + */ protected fun checkInitMapLocation(mapReady: MapLibreMap,style: Style, context: Context) { //enable location val hasGps = deviceHasLocationProvider() @@ -1045,7 +1070,7 @@ protected fun deviceHasLocationProvider(): Boolean{ val locManager = requireContext().getSystemService(LOCATION_SERVICE) as LocationManager - return locManager.allProviders.size > 0 + return locManager.allProviders.isNotEmpty() } /** 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 @@ -277,13 +277,11 @@ mapReady.addOnCameraIdleListener { map?.let { val newBbox = it.projection.visibleRegion.latLngBounds - if ((newBbox.center==lastBBox.center) && (newBbox.latitudeSpan==lastBBox.latitudeSpan) && (newBbox.longitudeSpan==lastBBox.latitudeSpan)){ - //do nothing - } else { - stopsViewModel.loadStopsInLatLngBounds(newBbox) - lastBBox = newBbox - } + stopsViewModel.loadStopsInLatLngBounds(newBbox) + lastBBox = newBbox + + } @@ -539,7 +537,7 @@ if (stops.isNullOrEmpty()) return if (stops.size==lastStopsSizeShown){ - Log.d(DEBUG_TAG, "Not updating, have same number of stops. After 3 times") + Log.d(DEBUG_TAG, "Not updating, have same number of stops") return } /*if(stops.size> lastStopsSizeShown){ @@ -559,7 +557,7 @@ } - Log.d(DEBUG_TAG,"Have put ${features.size} stops to display") + Log.d(DEBUG_TAG,"Displaying ${features.size} stops") // if the layer is already started, substitute the stops inside, otherwise start it if (stopsLayerStarted) { diff --git a/app/src/main/java/it/reyboz/bustorino/map/MapLibreLocationEngine.kt b/app/src/main/java/it/reyboz/bustorino/map/MapLibreLocationEngine.kt --- a/app/src/main/java/it/reyboz/bustorino/map/MapLibreLocationEngine.kt +++ b/app/src/main/java/it/reyboz/bustorino/map/MapLibreLocationEngine.kt @@ -17,10 +17,6 @@ * Separa completamente la logica di fusione dei provider (in [FusedNativeLocationProvider]) * dalla traduzione nel contratto MapLibre (qui). * - * Uso: - * val provider = FusedNativeLocationProvider(context) - * val engine = MapLibreLocationEngine(provider) - * // poi passa engine a LocationComponentActivationOptions */ class MapLibreLocationEngine( private val provider: FusedNativeLocationProvider, @@ -112,7 +108,5 @@ companion object { const val DEBUG_TAG = "BusTO-MapLocationEngine" - - } } 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 @@ -141,7 +141,7 @@ requestArrivalsForStop(stopId, fetchers) } - private suspend fun runArrivalsFetching(stopId: String, fetchers: List, appContext: Context) { + private fun runArrivalsFetching(stopId: String, fetchers: List, appContext: Context) { if (fetchers.isEmpty()) { //do nothing 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 @@ -19,7 +19,6 @@ import android.app.Application import android.location.Location -import android.os.Bundle import android.util.Log import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.MediatorLiveData @@ -27,6 +26,7 @@ import androidx.lifecycle.switchMap import it.reyboz.bustorino.backend.Stop import it.reyboz.bustorino.data.OldDataRepository +import org.maplibre.android.geometry.LatLng import org.maplibre.android.geometry.LatLngBounds import org.maplibre.geojson.BoundingBox import java.util.concurrent.Executors @@ -43,15 +43,7 @@ //private var allStopsLoaded = HashMap() private val boundingBoxLoaded = MutableLiveData() - val stopsInBoundingBox = MutableLiveData>() - - private val callback = - OldDataRepository.Callback> { res -> - if(res.isSuccess){ - stopsInBoundingBox.postValue(res.result) - Log.d(DEBUG_TAG, "Setting value of stops in bounding box") - } - } + //val stopsToShow = MediatorLiveData>() fun getStopByID(id: String): Stop? { return stopsToShow.value?.firstOrNull{ s-> s.ID == id} @@ -61,30 +53,37 @@ return stopsToShow.value } - /*fun requestStopsInBoundingBox(bb: BoundingBox) { - bb.let { - Log.d(DEBUG_TAG, "Launching stop request") - oldRepo.requestStopsInArea(it.latSouth, it.latNorth, it.lonWest, it.lonEast, callback) - } + private fun checkDistanceBbox(boxCurrent: BoundingBox, boxNew: BoundingBox): Double{ + val d1 = LatLng(boxCurrent.north(), boxCurrent.east()).distanceTo( + LatLng(boxNew.north(), boxNew.east()) + ) + val d2 = LatLng(boxCurrent.south(), boxCurrent.west()).distanceTo( + LatLng(boxNew.south(), boxNew.west()) + ) + return Math.max(d1,d2) } - - */ private fun updateBoundingBox(boundingBox: BoundingBox){ val current = boundingBoxLoaded.value if(current == null){ boundingBoxLoaded.value = boundingBox } else{ val bb = boundingBox - val mix = BoundingBox.fromLngLats(Math.min(current.west(), bb.west()), + val bnew = BoundingBox.fromLngLats(Math.min(current.west(), bb.west()), Math.min(current.south(), bb.south()), Math.max(current.north(), bb.east()), Math.max(current.north(), bb.north())) - boundingBoxLoaded.value = mix + val newDistance = checkDistanceBbox(current, bnew) + if(newDistance > 5) { + Log.d(DEBUG_TAG, "New box is larger than current, new max distance: $newDistance") + boundingBoxLoaded.value = bnew + } else{ + //Log.d(DEBUG_TAG, "New box is NOT larger than current, not updating") + } } } fun loadStopsInLatLngBounds(bb: LatLngBounds){ - val extra = 0.05 + val extra = 0.01 val deltaLong = abs(bb.longitudeEast - bb.longitudeWest) * extra val deltaLat = abs(bb.latitudeNorth - bb.latitudeSouth) * extra @@ -104,38 +103,6 @@ } - /*private val addStopsCallback = - OldDataRepository.Callback> { res -> - if(res.isSuccess) res.result?.let{ newStops -> - //val stopsAdd = stopsToShow.value ?: ArrayList() - /*for (s in newStops){ - if (s.ID !in stopsShownIDs){ - stopsShownIDs.add(s.ID) - stopsAdd.add(s) - allStopsLoaded[s.ID] = s - } - } - - */ - allStopsLoaded.clear() - for(stop in newStops){ - allStopsLoaded[stop.ID] = stop - } - Log.d(DEBUG_TAG, "Loaded ${newStops.size} stops") - stopsToShow.postValue(newStops) - //Log.d(DEBUG_TAG, "Loaded ${stopsAdd.size} stops in total") - } - } - - */ - /*stopsToShow.addSource(boundingBoxLoaded){ - oldRepo.requestStopsInArea(it.south(), it.north(), - it.west(), it.east(), - addStopsCallback) - } - - */ - val stopsToShow = boundingBoxLoaded.switchMap { oldRepo.requestStopsInAreaLiveData(it.south(), it.north(), it.west(), it.east()) } @@ -146,5 +113,7 @@ companion object{ private const val DEBUG_TAG = "BusTOStopMapViewModel" + + private const val DECIMAL_PLACES = 8 } } \ No newline at end of file