From 846cd976c08ffaba7b9fdeef2fdfbdeb0a36c7f0 Mon Sep 17 00:00:00 2001 From: mario7421 <114092717+mario7421@users.noreply.github.com> Date: Thu, 28 Aug 2025 08:10:48 +0200 Subject: [PATCH] Bug fix: the app should show the correct geohash also if a location is not yet known to the system. Remove the default location in San Francisco and show a spinner until the correct position becomes available. (#308) --- .../android/geohash/LocationChannelManager.kt | 145 ++++++++++++++++-- 1 file changed, 133 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/com/bitchat/android/geohash/LocationChannelManager.kt b/app/src/main/java/com/bitchat/android/geohash/LocationChannelManager.kt index 3ca45a73..7c645122 100644 --- a/app/src/main/java/com/bitchat/android/geohash/LocationChannelManager.kt +++ b/app/src/main/java/com/bitchat/android/geohash/LocationChannelManager.kt @@ -67,6 +67,10 @@ class LocationChannelManager private constructor(private val context: Context) { private val _locationNames = MutableLiveData>(emptyMap()) val locationNames: LiveData> = _locationNames + + // Add a new LiveData property to indicate when location is being fetched + private val _isLoadingLocation = MutableLiveData(false) + val isLoadingLocation: LiveData = _isLoadingLocation init { updatePermissionState() @@ -192,32 +196,137 @@ class LocationChannelManager private constructor(private val context: Context) { Log.d(TAG, "Requesting one-shot location") try { - // Get last known location first for quick result - val lastKnownLocation = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER) - ?: locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER) + // Try to get last known location from all available providers + var lastKnownLocation: Location? = null + // Get all available providers and try each one + val providers = locationManager.getProviders(true) + for (provider in providers) { + val location = locationManager.getLastKnownLocation(provider) + if (location != null) { + // If we find a location, check if it's more recent than what we have + if (lastKnownLocation == null || location.time > lastKnownLocation.time) { + lastKnownLocation = location + } + } + } + + if (lastKnownLocation == null) { + lastKnownLocation = lastLocation; + } + + // Use last known location if we have one if (lastKnownLocation != null) { Log.d(TAG, "Using last known location: ${lastKnownLocation.latitude}, ${lastKnownLocation.longitude}") lastLocation = lastKnownLocation + _isLoadingLocation.postValue(false) // Make sure loading state is off computeChannels(lastKnownLocation) reverseGeocodeIfNeeded(lastKnownLocation) } else { Log.d(TAG, "No last known location available") - // For demo purposes, use a default location (San Francisco) - val demoLocation = Location("demo").apply { - latitude = 37.7749 - longitude = -122.4194 - } - Log.d(TAG, "Using demo location (San Francisco): ${demoLocation.latitude}, ${demoLocation.longitude}") - lastLocation = demoLocation - computeChannels(demoLocation) - reverseGeocodeIfNeeded(demoLocation) + // Set loading state to true so UI can show a spinner + _isLoadingLocation.postValue(true) + + // Request a fresh location only when we don't have a last known location + Log.d(TAG, "Requesting fresh location...") + requestFreshLocation() } } catch (e: SecurityException) { Log.e(TAG, "Security exception requesting location: ${e.message}") + _isLoadingLocation.postValue(false) // Turn off loading state on error updatePermissionState() } } + + // One-time location listener to get a fresh location update + private val oneShotLocationListener = object : LocationListener { + override fun onLocationChanged(location: Location) { + Log.d(TAG, "Fresh location received: ${location.latitude}, ${location.longitude}") + lastLocation = location + computeChannels(location) + reverseGeocodeIfNeeded(location) + + // Update loading state to indicate we have a location now + _isLoadingLocation.postValue(false) + + // Remove this listener after getting the update + try { + locationManager.removeUpdates(this) + } catch (e: SecurityException) { + Log.e(TAG, "Error removing location listener: ${e.message}") + } + } + } + + // Request a fresh location update using getCurrentLocation instead of continuous updates + private fun requestFreshLocation() { + if (!hasLocationPermission()) { + _isLoadingLocation.postValue(false) // Turn off loading state if no permission + return + } + + try { + // Set loading state to true to indicate we're actively trying to get a location + _isLoadingLocation.postValue(true) + + // Try common providers in order of preference + val providers = listOf( + LocationManager.GPS_PROVIDER, + LocationManager.NETWORK_PROVIDER, + LocationManager.PASSIVE_PROVIDER + ) + + var providerFound = false + for (provider in providers) { + if (locationManager.isProviderEnabled(provider)) { + Log.d(TAG, "Getting current location from $provider") + + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) { + // For Android 11+ (API 30+), use getCurrentLocation + locationManager.getCurrentLocation( + provider, + null, // No cancellation signal + context.mainExecutor, + { location -> + if (location != null) { + Log.d(TAG, "Fresh location received: ${location.latitude}, ${location.longitude}") + lastLocation = location + computeChannels(location) + reverseGeocodeIfNeeded(location) + } else { + Log.w(TAG, "Received null location from getCurrentLocation") + } + // Update loading state to indicate we have a location now + _isLoadingLocation.postValue(false) + } + ) + } else { + // For older versions, fall back to one-shot requestSingleUpdate + locationManager.requestSingleUpdate( + provider, + oneShotLocationListener, + null // Looper - null uses the main thread + ) + } + + providerFound = true + break + } + } + + // If no provider was available, turn off loading state + if (!providerFound) { + Log.w(TAG, "No location providers available") + _isLoadingLocation.postValue(false) + } + } catch (e: SecurityException) { + Log.e(TAG, "Security exception requesting location: ${e.message}") + _isLoadingLocation.postValue(false) // Turn off loading state on error + } catch (e: Exception) { + Log.e(TAG, "Error requesting location: ${e.message}") + _isLoadingLocation.postValue(false) // Turn off loading state on error + } + } // MARK: - Helpers @@ -469,5 +578,17 @@ class LocationChannelManager private constructor(private val context: Context) { fun cleanup() { Log.d(TAG, "Cleaning up LocationChannelManager") endLiveRefresh() + + // For older Android versions, remove any remaining location listener to prevent memory leaks + if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.R) { + try { + locationManager.removeUpdates(oneShotLocationListener) + } catch (e: SecurityException) { + Log.e(TAG, "Error removing location listener during cleanup: ${e.message}") + } catch (e: Exception) { + Log.e(TAG, "Error during cleanup: ${e.message}") + } + } + // For Android 11+, getCurrentLocation doesn't need explicit cleanup as it's a one-time operation } }