commit efa9a9bed663fb50435874423de50727366a1304 Author: Fabio Mazza Date: Wed May 27 15:26:43 2026 +0200 Fix bugs, improve map Summary: - Lower batch size of trips to download - Add Iveco name - Filter better the trips, do not ask for null ones - Do not zoom on position if the bottom sheet is open Fix T1443 Thanks to Federica Esposito for testing Test Plan: Open the map and: - before location is updated, click on a stop or on a bus: the map should not zoom on the user position any more - pause the app, do something else - reopen the app and check that there are no explosions or weird stuff Reviewers: #libre_busto_hackers, valerio.bozzolan Reviewed By: #libre_busto_hackers, valerio.bozzolan Subscribers: valerio.bozzolan Project Tags: #libre_busto Maniphest Tasks: T1443 Differential Revision: https://gitpull.it/D248 diff --git a/app/src/main/java/it/reyboz/bustorino/backend/VehicleUtils.kt b/app/src/main/java/it/reyboz/bustorino/backend/VehicleUtils.kt index e87bd82..4c95abb 100644 --- a/app/src/main/java/it/reyboz/bustorino/backend/VehicleUtils.kt +++ b/app/src/main/java/it/reyboz/bustorino/backend/VehicleUtils.kt @@ -46,8 +46,8 @@ object VehicleUtils { VehicleClassInfo(9000, "BYD K9", "E-Bus", 9120, 9121), VehicleClassInfo(9200, "Citymood", "Bus", 9200, 9251), VehicleClassInfo(9200, "Citymood", "Bus", 9252, 9261), - VehicleClassInfo(9400, "E-Way", "E-Bus", 9400, 9599), - VehicleClassInfo(9600, "E-Way 18m", "E-Bus", 9600, 9699) + VehicleClassInfo(9400, "Iveco E-Way", "E-Bus", 9400, 9599), + VehicleClassInfo(9600, "Iveco E-Way 18m", "E-Bus", 9600, 9699) ) fun getTypeForLabel(label: String): VehicleClassInfo? { diff --git a/app/src/main/java/it/reyboz/bustorino/data/MatoTripsDownloadWorker.kt b/app/src/main/java/it/reyboz/bustorino/data/MatoTripsDownloadWorker.kt index f2015b3..c592a23 100644 --- a/app/src/main/java/it/reyboz/bustorino/data/MatoTripsDownloadWorker.kt +++ b/app/src/main/java/it/reyboz/bustorino/data/MatoTripsDownloadWorker.kt @@ -28,6 +28,7 @@ import kotlin.math.min class MatoTripsDownloadWorker(appContext: Context, workerParams: WorkerParameters) : CoroutineWorker(appContext, workerParams) { + private val DOWNLOAD_STEP = 50 override suspend fun doWork(): Result { @@ -41,7 +42,7 @@ class MatoTripsDownloadWorker(appContext: Context, workerParams: WorkerParameter var i = 0 var totDown = 0 while (i protected var locationEngine: MapLibreLocationEngine? = null - protected lateinit var locationProvider: FusedNativeLocationProvider + protected var locationProvider: FusedNativeLocationProvider? = null protected var shownToastNoPosition = false protected var locationEnabledOnDevice = true + protected var busLayerStarted = false //TODO ACTIVATE THIS private val preferenceChangeListener = SharedPreferences.OnSharedPreferenceChangeListener(){ pref, key -> @@ -183,10 +185,11 @@ abstract class GeneralMapLibreFragment: ScreenBaseFragment(), OnMapReadyCallback //BUS POSITIONS protected val updatesByVehDict = HashMap(5) protected val animatorsByVeh = HashMap() - protected var vehShowing = "" + protected var vehShowing: String? = null protected var lastUpdateTime:Long = -2 + protected var jobUpdate: Job? = null - private val lifecycleOwnerLiveData = getViewLifecycleOwnerLiveData() + //private val lifecycleOwnerLiveData = viewLifecycleOwnerLiveData //extra items to use the LibreMap @@ -253,6 +256,7 @@ abstract class GeneralMapLibreFragment: ScreenBaseFragment(), OnMapReadyCallback } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + //TODO: Re-create map when this preference changes lastMapStyle = PreferencesHolder.getMapLibreStyleFile(requireContext()) Log.d(DEBUG_TAG, "onCreateView lastMapStyle: $lastMapStyle") return super.onCreateView(inflater, container, savedInstanceState) @@ -292,6 +296,8 @@ abstract class GeneralMapLibreFragment: ScreenBaseFragment(), OnMapReadyCallback //if(newMapStyle!=lastMapStyle){ // reloadMap() //} + if(busLayerStarted) + updatePositionsIcons(false) } override fun onLowMemory() { @@ -307,15 +313,18 @@ abstract class GeneralMapLibreFragment: ScreenBaseFragment(), OnMapReadyCallback override fun onDestroy() { mapView?.onDestroy() Log.d(DEBUG_TAG, "Destroyed mapView Fragment!!") + busLayerStarted = false super.onDestroy() } override fun onStop() { + locationProvider?.removeListener(deviceLocationStatusListener) mapView?.onStop() super.onStop() } override fun onPause() { + jobUpdate?.cancel() mapView?.onPause() super.onPause() } @@ -323,7 +332,6 @@ abstract class GeneralMapLibreFragment: ScreenBaseFragment(), OnMapReadyCallback override fun onDestroyView() { bottomLayout = null - locationProvider.removeListener(deviceLocationStatusListener) mapInitialized = false locationInitialized = false super.onDestroyView() @@ -414,9 +422,9 @@ abstract class GeneralMapLibreFragment: ScreenBaseFragment(), OnMapReadyCallback //reset states shownStopInBottomSheet = null - if (vehShowing!=""){ + if (vehShowing!=null){ //we are hiding a vehicle - vehShowing = "" + vehShowing = null updatePositionsIcons(true) } extraBottomTextView.visibility = View.GONE @@ -478,9 +486,10 @@ abstract class GeneralMapLibreFragment: ScreenBaseFragment(), OnMapReadyCallback mStyle?.let{ style -> locationComponent = map.locationComponent - locationProvider = FusedNativeLocationProvider(context) - locationProvider.addListener(deviceLocationStatusListener) - locationEngine = MapLibreLocationEngine(locationProvider) + val locProvider = FusedNativeLocationProvider(context) + locProvider.addListener(deviceLocationStatusListener) + locationEngine = MapLibreLocationEngine(locProvider) + locationProvider = locProvider val options = LocationComponentActivationOptions.builder(context, style) .useDefaultLocationEngine(false) .locationEngine(locationEngine) @@ -522,7 +531,7 @@ abstract class GeneralMapLibreFragment: ScreenBaseFragment(), OnMapReadyCallback */ protected fun updateBusPositionsInMap( incomingData: HashMap>, - hasVehicleTracking: Boolean = false, + hasVehicleTracking: Boolean = true, trackVehicleCallback: ((String) -> Unit)? = null ) { val vehsNew = HashSet(incomingData.values.map { up -> up.first.vehicle }) @@ -613,7 +622,7 @@ abstract class GeneralMapLibreFragment: ScreenBaseFragment(), OnMapReadyCallback } // Update vehicle details if this is the shown/tracked vehicle - if (hasVehicleTracking && vehShowing.isNotEmpty() && vehID == vehShowing) { + if (hasVehicleTracking && vehShowing?.isNotEmpty() == true && vehID == vehShowing) { trackVehicleCallback?.invoke(vehID) } } @@ -700,7 +709,7 @@ abstract class GeneralMapLibreFragment: ScreenBaseFragment(), OnMapReadyCallback } } - vehShowing = veh + vehShowing = null bottomSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED updatePositionsIcons(true) Log.d(DEBUG_TAG, "Shown vehicle $veh in bottom sheet") @@ -709,20 +718,30 @@ abstract class GeneralMapLibreFragment: ScreenBaseFragment(), OnMapReadyCallback /** * Update the bus positions displayed on the map, from the existing data * - * @param forced If true, forces immediate update ignoring the 60ms throttle + * @param forced If true, forces immediate update ignoring the 100ms throttle */ protected fun updatePositionsIcons(forced: Boolean) { // Avoid frequent updates - throttle to max once per 60ms val currentTime = System.currentTimeMillis() - if (!forced && currentTime - lastUpdateTime < 60) { + val isStarted = (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) + if(forced){ + // if we're running a forced update, cancel the pending one + jobUpdate?.cancel() + } + else if (currentTime - lastUpdateTime < 100) { // Schedule delayed update - if(lifecycleOwnerLiveData.value != null) - viewLifecycleOwner.lifecycleScope.launch { - delay(200.milliseconds) - updatePositionsIcons(forced) + if(viewLifecycleOwnerLiveData.value != null) { + jobUpdate?.cancel() + jobUpdate = viewLifecycleOwner.lifecycleScope.launch { + delay(100.milliseconds) + updatePositionsIcons(false) } + } return } + if(!isStarted){ + Log.w(DEBUG_TAG, "fragment is not started, ") + } val busFeatures = ArrayList() val selectedBusFeatures = ArrayList() @@ -742,7 +761,7 @@ abstract class GeneralMapLibreFragment: ScreenBaseFragment(), OnMapReadyCallback ) // Separate selected vehicle from others - if (vehShowing.isNotEmpty() && vehShowing == dat.posUpdate.vehicle) { + if (vehShowing?.isNotEmpty() == true && vehShowing == dat.posUpdate.vehicle) { selectedBusFeatures.add(newFeature) } else { busFeatures.add(newFeature) @@ -1005,7 +1024,7 @@ abstract class GeneralMapLibreFragment: ScreenBaseFragment(), OnMapReadyCallback } style.addLayerAbove(selectedBusLayer, BUSES_LAYER_ID) - + busLayerStarted = true } /** * Method used for enabling / disabling the location from the buttons diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/LinesDetailFragment.kt b/app/src/main/java/it/reyboz/bustorino/fragments/LinesDetailFragment.kt index 83118e7..74e0d9f 100644 --- a/app/src/main/java/it/reyboz/bustorino/fragments/LinesDetailFragment.kt +++ b/app/src/main/java/it/reyboz/bustorino/fragments/LinesDetailFragment.kt @@ -366,7 +366,7 @@ class LinesDetailFragment() : GeneralMapLibreFragment() { if (clearPos) { livePositionsViewModel.clearAllPositions() //force clear of the viewed data - if(vehShowing.isNotEmpty()) hideStopOrBusBottomSheet() + if(!vehShowing.isNullOrEmpty()) hideStopOrBusBottomSheet() clearAllBusPositionsInMap() } @@ -595,7 +595,7 @@ class LinesDetailFragment() : GeneralMapLibreFragment() { val id = feature.getStringProperty("id") val stop = viewModel.getStopByID(id) stop?.let { - if (isBottomSheetShowing() || vehShowing.isNotEmpty()) { + if (isBottomSheetShowing()) { hideStopOrBusBottomSheet() } openStopInBottomSheet(it) 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 9b01873..6309490 100644 --- a/app/src/main/java/it/reyboz/bustorino/fragments/MapLibreFragment.kt +++ b/app/src/main/java/it/reyboz/bustorino/fragments/MapLibreFragment.kt @@ -224,7 +224,7 @@ class MapLibreFragment : GeneralMapLibreFragment() { if (clearPos) { livePositionsViewModel.clearAllPositions() //force clear of the viewed data - if(vehShowing.isNotEmpty()) hideStopOrBusBottomSheet() + if(!vehShowing.isNullOrEmpty()) hideStopOrBusBottomSheet() clearAllBusPositionsInMap() } @@ -628,20 +628,24 @@ class MapLibreFragment : GeneralMapLibreFragment() { }) } if(locationEnabledOnDevice){ - setFollowUserLocation(true) + if(shownStopInBottomSheet == null && vehShowing == null) + setFollowUserLocation(true) } } override fun onMapLocationEnabled(active: Boolean) { //Extra stuff to do - setFollowUserLocation(active) + // this check should always pass + if(shownStopInBottomSheet == null && vehShowing == null) + setFollowUserLocation(active) } @SuppressLint("MissingPermission") override fun onFirstReceivedLocation(location: Location) { val it = location + val notShowingStopOrVehicle = vehShowing.isNullOrEmpty() && shownStopInBottomSheet != null if(locationInitialized && !receivedFirstLocation) { //only zoom if the user position is close enough to the center val newPoint = LatLng(it.latitude, it.longitude) @@ -655,7 +659,7 @@ class MapLibreFragment : GeneralMapLibreFragment() { //Update UI Status mapStateViewModel.locationUserActive.value = false mapStateViewModel.followingUserPosition.value = false - } else { + } else if(notShowingStopOrVehicle) { map?.apply { animateCamera( CameraUpdateFactory.newCameraPosition( @@ -668,6 +672,10 @@ class MapLibreFragment : GeneralMapLibreFragment() { mapStateViewModel.locationUserActive.value = true } setFollowUserLocation(true) + } else{ + map?.apply { + setLocationComponentEnabled(true) + } } } else{ diff --git a/app/src/main/java/it/reyboz/bustorino/viewmodels/LivePositionsViewModel.kt b/app/src/main/java/it/reyboz/bustorino/viewmodels/LivePositionsViewModel.kt index 239c92e..b8703e8 100644 --- a/app/src/main/java/it/reyboz/bustorino/viewmodels/LivePositionsViewModel.kt +++ b/app/src/main/java/it/reyboz/bustorino/viewmodels/LivePositionsViewModel.kt @@ -46,6 +46,7 @@ import kotlin.collections.HashMap import kotlin.collections.HashSet import androidx.core.content.edit import androidx.lifecycle.MutableLiveData +import kotlin.text.contains typealias FullPositionUpdatesMap = HashMap> typealias FullPositionUpdate = Pair @@ -177,7 +178,9 @@ class LivePositionsViewModel(application: Application): AndroidViewModel(applica //find the trip IDs in the updates private val tripsIDsInUpdates = positionsToBeMatchedLiveData.map { it -> //Log.d(DEBUG_TI, "Updates map has keys ${upMap.keys}") - it.map { pos -> "gtt:"+pos.tripID } + it.map { pos -> "gtt:"+pos.tripID }.filter{ s-> + !(s.contains("null") || s.trim() =="gtt:") + } } // get the trip IDs in the DB @@ -191,7 +194,9 @@ class LivePositionsViewModel(application: Application): AndroidViewModel(applica val tripNames=tripswithPatterns.map { twp-> twp.trip.tripID } Log.i(DEBUG_TI, "Have ${tripswithPatterns.size} trips in the DB") if (tripsIDsInUpdates.value!=null) - return@map tripsIDsInUpdates.value!!.filter { !(tripNames.contains(it) || it.contains("null"))} + return@map tripsIDsInUpdates.value!!.filter { !( + tripNames.contains(it) || it.contains("null") || it =="gtt:" + )}.distinct() else { Log.e(DEBUG_TI,"Got results for gtfsTripsInDB but not tripsIDsInUpdates??") return@map ArrayList()