summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGuillaume Jacquart <guillaume.jacquart@hoodbrains.com>2023-07-31 14:48:57 +0000
committerGuillaume Jacquart <guillaume.jacquart@hoodbrains.com>2023-07-31 14:48:57 +0000
commitb153164c20449c78772860426b27cb4fe8466939 (patch)
tree5ee3bbc19e2ce6dc8bd4f62abcf1ad1f6a41f371
parent466b3947268bb9e053da492e376f94af58b6a9c5 (diff)
parent5c36bc424d610d0226694782d29d80f428fd7ca3 (diff)
Merge branch '5842-fix_back_to_real_location' into 'main'
5842: fix back to REAL location See merge request e/os/advanced-privacy!143
-rw-r--r--app/src/main/java/foundation/e/advancedprivacy/DependencyContainer.kt1
-rw-r--r--app/src/main/java/foundation/e/advancedprivacy/domain/usecases/FakeLocationStateUseCase.kt30
-rw-r--r--app/src/main/java/foundation/e/advancedprivacy/features/location/FakeLocationFragment.kt208
-rw-r--r--app/src/main/java/foundation/e/advancedprivacy/features/location/FakeLocationMapView.kt5
-rw-r--r--app/src/main/java/foundation/e/advancedprivacy/features/location/FakeLocationViewModel.kt18
-rw-r--r--gradle/libs.versions.toml2
6 files changed, 149 insertions, 115 deletions
diff --git a/app/src/main/java/foundation/e/advancedprivacy/DependencyContainer.kt b/app/src/main/java/foundation/e/advancedprivacy/DependencyContainer.kt
index 820a868..f6f2038 100644
--- a/app/src/main/java/foundation/e/advancedprivacy/DependencyContainer.kt
+++ b/app/src/main/java/foundation/e/advancedprivacy/DependencyContainer.kt
@@ -190,7 +190,6 @@ class ViewModelsFactory(
)
FakeLocationViewModel::class.java ->
FakeLocationViewModel(
- getQuickPrivacyStateUseCase = getQuickPrivacyStateUseCase,
fakeLocationStateUseCase = fakeLocationStateUseCase
)
InternetPrivacyViewModel::class.java ->
diff --git a/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/FakeLocationStateUseCase.kt b/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/FakeLocationStateUseCase.kt
index 5a16308..8831fff 100644
--- a/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/FakeLocationStateUseCase.kt
+++ b/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/FakeLocationStateUseCase.kt
@@ -25,7 +25,6 @@ import android.location.Location
import android.location.LocationListener
import android.location.LocationManager
import android.os.Bundle
-import android.util.Log
import foundation.e.advancedprivacy.data.repositories.LocalStateRepository
import foundation.e.advancedprivacy.domain.entities.LocationMode
import foundation.e.advancedprivacy.dummy.CityDataSource
@@ -38,6 +37,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
+import timber.log.Timber
import kotlin.random.Random
class FakeLocationStateUseCase(
@@ -134,15 +134,7 @@ class FakeLocationStateUseCase(
private var localListener = object : LocationListener {
override fun onLocationChanged(location: Location) {
- currentLocation.update { previous ->
- if ((previous?.time ?: 0) + 1800 < location.time ||
- (previous?.accuracy ?: Float.MAX_VALUE) > location.accuracy
- ) {
- location
- } else {
- previous
- }
- }
+ currentLocation.update { location }
}
@Deprecated("Deprecated since API 29, never called.")
@@ -198,13 +190,19 @@ class FakeLocationStateUseCase(
)
}
- networkProvider?.let { locationManager.getLastKnownLocation(it) }
- ?: gpsProvider?.let { locationManager.getLastKnownLocation(it) }
- ?.let {
- localListener.onLocationChanged(it)
- }
+ var lastKnownLocation = networkProvider?.let {
+ locationManager.getLastKnownLocation(it)
+ }
+
+ if (lastKnownLocation == null) {
+ lastKnownLocation = gpsProvider?.let {
+ locationManager.getLastKnownLocation(it)
+ }
+ }
+
+ lastKnownLocation?.let { localListener.onLocationChanged(it) }
} catch (se: SecurityException) {
- Log.e(TAG, "Missing permission", se)
+ Timber.e("Missing permission", se)
}
}
}
diff --git a/app/src/main/java/foundation/e/advancedprivacy/features/location/FakeLocationFragment.kt b/app/src/main/java/foundation/e/advancedprivacy/features/location/FakeLocationFragment.kt
index 09409f2..7d18930 100644
--- a/app/src/main/java/foundation/e/advancedprivacy/features/location/FakeLocationFragment.kt
+++ b/app/src/main/java/foundation/e/advancedprivacy/features/location/FakeLocationFragment.kt
@@ -25,6 +25,7 @@ import android.location.Location
import android.os.Bundle
import android.text.Editable
import android.view.View
+import android.view.inputmethod.InputMethodManager
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.NonNull
@@ -34,10 +35,10 @@ import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
-import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout
import com.google.android.material.textfield.TextInputLayout.END_ICON_CUSTOM
import com.google.android.material.textfield.TextInputLayout.END_ICON_NONE
+import com.mapbox.android.gestures.MoveGestureDetector
import com.mapbox.mapboxsdk.Mapbox
import com.mapbox.mapboxsdk.WellKnownTileServer
import com.mapbox.mapboxsdk.camera.CameraPosition
@@ -58,8 +59,8 @@ import foundation.e.advancedprivacy.domain.entities.LocationMode
import foundation.e.advancedprivacy.features.location.FakeLocationViewModel.Action
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
-import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.launch
+import timber.log.Timber
class FakeLocationFragment : NavToolbarFragment(R.layout.fragment_fake_location) {
@@ -81,6 +82,8 @@ class FakeLocationFragment : NavToolbarFragment(R.layout.fragment_fake_location)
private var inputJob: Job? = null
+ private var updateLocationJob: Job? = null
+
private val locationPermissionRequest = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { permissions ->
@@ -92,7 +95,6 @@ class FakeLocationFragment : NavToolbarFragment(R.layout.fragment_fake_location)
}
companion object {
- private const val DEBOUNCE_PERIOD = 1000L
private const val MAP_STYLE = "mapbox://styles/mapbox/outdoors-v12"
}
@@ -115,18 +117,8 @@ class FakeLocationFragment : NavToolbarFragment(R.layout.fragment_fake_location)
mapboxMap.uiSettings.isRotateGesturesEnabled = false
mapboxMap.setStyle(MAP_STYLE) { style ->
enableLocationPlugin(style)
- mapboxMap.addOnCameraMoveListener {
- if (binding.mapView.isEnabled) {
- mapboxMap.cameraPosition.target?.let {
- viewModel.submitAction(
- Action.SetSpecificLocationAction(
- it.latitude.toFloat(),
- it.longitude.toFloat()
- )
- )
- }
- }
- }
+
+ mapboxMap.addOnMoveListener(onMoveListener)
mapboxMap.cameraPosition = CameraPosition.Builder().zoom(8.0).build()
@@ -134,18 +126,44 @@ class FakeLocationFragment : NavToolbarFragment(R.layout.fragment_fake_location)
bindClickListeners()
render(viewModel.state.value)
- viewLifecycleOwner.lifecycleScope.launch {
- viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
- viewModel.singleEvents.collect { event ->
- if (event is FakeLocationViewModel.SingleEvent.LocationUpdatedEvent) {
- updateLocation(event.location, event.mode)
- }
- }
+ startUpdateLocationJob()
+ }
+ }
+
+ startListening()
+ }
+
+ private val onMoveListener = object : MapboxMap.OnMoveListener {
+ private val cameraIdleListener: MapboxMap.OnCameraIdleListener =
+ object : MapboxMap.OnCameraIdleListener {
+ override fun onCameraIdle() {
+ mapboxMap?.cameraPosition?.target?.let {
+ viewModel.submitAction(
+ Action.SetSpecificLocationAction(
+ it.latitude.toFloat(),
+ it.longitude.toFloat()
+ )
+ )
+ startUpdateLocationJob()
}
+ mapboxMap?.removeOnCameraIdleListener(this)
}
}
+
+ override fun onMoveBegin(detector: MoveGestureDetector) {
+ updateLocationJob?.cancel()
+ updateLocationJob = null
+ mapboxMap?.removeOnCameraIdleListener(cameraIdleListener)
}
+ override fun onMove(detector: MoveGestureDetector) {}
+
+ override fun onMoveEnd(detector: MoveGestureDetector) {
+ mapboxMap?.addOnCameraIdleListener(cameraIdleListener)
+ }
+ }
+
+ private fun startListening() {
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
render(viewModel.state.value)
@@ -169,9 +187,6 @@ class FakeLocationFragment : NavToolbarFragment(R.layout.fragment_fake_location)
)
)
}
- is FakeLocationViewModel.SingleEvent.LocationUpdatedEvent -> {
- // Nothing here, another collect linked to mapbox view.
- }
}
}
}
@@ -184,49 +199,90 @@ class FakeLocationFragment : NavToolbarFragment(R.layout.fragment_fake_location)
}
}
- private fun getCoordinatesAfterTextChanged(
- inputLayout: TextInputLayout,
- editText: TextInputEditText,
- isLat: Boolean
- ) = { editable: Editable? ->
- inputJob?.cancel()
- if (editable != null && editable.isNotEmpty() && editText.isEnabled) {
- inputJob = lifecycleScope.launch {
- delay(DEBOUNCE_PERIOD)
- ensureActive()
- try {
- val value = editable.toString().toFloat()
- val maxValue = if (isLat) 90f else 180f
-
- if (value > maxValue || value < -maxValue) {
- throw NumberFormatException("value $value is out of bounds")
- }
- inputLayout.error = null
-
- inputLayout.setEndIconDrawable(R.drawable.ic_valid)
- inputLayout.endIconMode = END_ICON_CUSTOM
-
- // Here, value is valid, try to send the values
- try {
- val lat = binding.edittextLatitude.text.toString().toFloat()
- val lon = binding.edittextLongitude.text.toString().toFloat()
- if (lat <= 90f && lat >= -90f && lon <= 180f && lon >= -180f) {
- mapboxMap?.moveCamera(
- CameraUpdateFactory.newLatLng(
- LatLng(lat.toDouble(), lon.toDouble())
- )
- )
- }
- } catch (e: NumberFormatException) {
- }
- } catch (e: NumberFormatException) {
- inputLayout.endIconMode = END_ICON_NONE
- inputLayout.error = getString(R.string.location_input_error)
+ private fun startUpdateLocationJob() {
+ updateLocationJob?.cancel()
+ updateLocationJob = viewLifecycleOwner.lifecycleScope.launch {
+ viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
+ // Without this delay, onResume, map apply the updateLocation and then
+ // move to an old fake location.
+ delay(1000)
+ viewModel.currentLocation.collect { location ->
+ updateLocation(location, viewModel.state.value.mode)
}
}
}
}
+ private fun validateCoordinate(
+ inputLayout: TextInputLayout,
+ maxValue: Float
+ ): Boolean {
+ return try {
+ val value = inputLayout.editText?.text?.toString()?.toFloat()!!
+
+ if (value > maxValue || value < -maxValue) {
+ throw NumberFormatException("value $value is out of bounds")
+ }
+ inputLayout.error = null
+
+ inputLayout.setEndIconDrawable(R.drawable.ic_valid)
+ inputLayout.endIconMode = END_ICON_CUSTOM
+ true
+ } catch (e: Exception) {
+ inputLayout.endIconMode = END_ICON_NONE
+ inputLayout.error = getString(R.string.location_input_error)
+ false
+ }
+ }
+
+ private fun updateSpecificCoordinates() {
+ try {
+ val lat = binding.edittextLatitude.text.toString().toFloat()
+ val lon = binding.edittextLongitude.text.toString().toFloat()
+ if (lat <= 90f && lat >= -90f && lon <= 180f && lon >= -180f) {
+ viewModel.submitAction(
+ Action.SetSpecificLocationAction(
+ lat,
+ lon
+ )
+ )
+ }
+ } catch (e: NumberFormatException) {
+ Timber.e("Unfiltered wrong lat lon format")
+ }
+ }
+
+ @Suppress("UNUSED_PARAMETER")
+ private fun onLatTextChanged(editable: Editable?) {
+ if (!binding.edittextLatitude.isFocused ||
+ !validateCoordinate(binding.textlayoutLatitude, 90f)
+ ) return
+
+ updateSpecificCoordinates()
+ }
+
+ @Suppress("UNUSED_PARAMETER")
+ private fun onLonTextChanged(editable: Editable?) {
+ if (!binding.edittextLongitude.isFocused ||
+ !validateCoordinate(binding.textlayoutLongitude, 180f)
+ ) return
+
+ updateSpecificCoordinates()
+ }
+
+ private val isEditingLatLon get() = binding.edittextLongitude.isFocused || binding.edittextLatitude.isFocused
+
+ private val latLonOnFocusChangeListener = object : View.OnFocusChangeListener {
+ override fun onFocusChange(v: View?, hasFocus: Boolean) {
+ if (!isEditingLatLon) {
+ (context?.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager)?.hideSoftInputFromWindow(
+ v?.windowToken,
+ 0
+ )
+ }
+ }
+ }
+
@SuppressLint("ClickableViewAccessibility")
private fun bindClickListeners() {
binding.radioUseRealLocation.setOnClickListener {
@@ -242,21 +298,11 @@ class FakeLocationFragment : NavToolbarFragment(R.layout.fragment_fake_location)
)
}
}
- binding.edittextLatitude.addTextChangedListener(
- afterTextChanged = getCoordinatesAfterTextChanged(
- binding.textlayoutLatitude,
- binding.edittextLatitude,
- true
- )
- )
- binding.edittextLongitude.addTextChangedListener(
- afterTextChanged = getCoordinatesAfterTextChanged(
- binding.textlayoutLongitude,
- binding.edittextLongitude,
- false
- )
- )
+ binding.edittextLatitude.addTextChangedListener(afterTextChanged = ::onLatTextChanged)
+ binding.edittextLongitude.addTextChangedListener(afterTextChanged = ::onLonTextChanged)
+ binding.edittextLatitude.onFocusChangeListener = latLonOnFocusChangeListener
+ binding.edittextLongitude.onFocusChangeListener = latLonOnFocusChangeListener
}
@SuppressLint("MissingPermission")
@@ -275,7 +321,6 @@ class FakeLocationFragment : NavToolbarFragment(R.layout.fragment_fake_location)
binding.mapLoader.isVisible = false
binding.mapOverlay.isVisible = state.mode != LocationMode.SPECIFIC_LOCATION
binding.centeredMarker.isVisible = true
-
mapboxMap?.moveCamera(
CameraUpdateFactory.newLatLng(
LatLng(
@@ -289,8 +334,10 @@ class FakeLocationFragment : NavToolbarFragment(R.layout.fragment_fake_location)
binding.textlayoutLatitude.isVisible = (state.mode == LocationMode.SPECIFIC_LOCATION)
binding.textlayoutLongitude.isVisible = (state.mode == LocationMode.SPECIFIC_LOCATION)
- binding.edittextLatitude.setText(state.specificLatitude?.toString())
- binding.edittextLongitude.setText(state.specificLongitude?.toString())
+ if (!isEditingLatLon) {
+ binding.edittextLatitude.setText(state.specificLatitude?.toString())
+ binding.edittextLongitude.setText(state.specificLongitude?.toString())
+ }
}
@SuppressLint("MissingPermission")
@@ -306,7 +353,6 @@ class FakeLocationFragment : NavToolbarFragment(R.layout.fragment_fake_location)
val update = CameraUpdateFactory.newLatLng(
LatLng(location.latitude, location.longitude)
)
-
if (isFirstLaunch) {
mapboxMap?.moveCamera(update)
isFirstLaunch = false
diff --git a/app/src/main/java/foundation/e/advancedprivacy/features/location/FakeLocationMapView.kt b/app/src/main/java/foundation/e/advancedprivacy/features/location/FakeLocationMapView.kt
index fbb5b6c..f80c25e 100644
--- a/app/src/main/java/foundation/e/advancedprivacy/features/location/FakeLocationMapView.kt
+++ b/app/src/main/java/foundation/e/advancedprivacy/features/location/FakeLocationMapView.kt
@@ -38,7 +38,10 @@ class FakeLocationMapView @JvmOverloads constructor(
@SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent?): Boolean {
when (event?.action) {
- MotionEvent.ACTION_DOWN -> parent.requestDisallowInterceptTouchEvent(true)
+ MotionEvent.ACTION_DOWN -> {
+ parent.requestDisallowInterceptTouchEvent(true)
+ requestFocus()
+ }
MotionEvent.ACTION_UP -> parent.requestDisallowInterceptTouchEvent(false)
}
super.onTouchEvent(event)
diff --git a/app/src/main/java/foundation/e/advancedprivacy/features/location/FakeLocationViewModel.kt b/app/src/main/java/foundation/e/advancedprivacy/features/location/FakeLocationViewModel.kt
index 87b64c5..deca4c1 100644
--- a/app/src/main/java/foundation/e/advancedprivacy/features/location/FakeLocationViewModel.kt
+++ b/app/src/main/java/foundation/e/advancedprivacy/features/location/FakeLocationViewModel.kt
@@ -20,13 +20,12 @@ package foundation.e.advancedprivacy.features.location
import android.location.Location
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
-import foundation.e.advancedprivacy.domain.entities.LocationMode
import foundation.e.advancedprivacy.domain.usecases.FakeLocationStateUseCase
-import foundation.e.advancedprivacy.domain.usecases.GetQuickPrivacyStateUseCase
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.debounce
@@ -38,7 +37,6 @@ import kotlinx.coroutines.withContext
import kotlin.time.Duration.Companion.milliseconds
class FakeLocationViewModel(
- private val getQuickPrivacyStateUseCase: GetQuickPrivacyStateUseCase,
private val fakeLocationStateUseCase: FakeLocationStateUseCase
) : ViewModel() {
companion object {
@@ -48,6 +46,8 @@ class FakeLocationViewModel(
private val _state = MutableStateFlow(FakeLocationState())
val state = _state.asStateFlow()
+ val currentLocation: StateFlow<Location?> = fakeLocationStateUseCase.currentLocation
+
private val _singleEvents = MutableSharedFlow<SingleEvent>()
val singleEvents = _singleEvents.asSharedFlow()
@@ -72,17 +72,6 @@ class FakeLocationViewModel(
}
).collect {}
}
-
- launch {
- fakeLocationStateUseCase.currentLocation.collect { location ->
- _singleEvents.emit(
- SingleEvent.LocationUpdatedEvent(
- mode = _state.value.mode,
- location = location
- )
- )
- }
- }
}
fun submitAction(action: Action) = viewModelScope.launch {
@@ -108,7 +97,6 @@ class FakeLocationViewModel(
}
sealed class SingleEvent {
- data class LocationUpdatedEvent(val mode: LocationMode, val location: Location?) : SingleEvent()
object RequestLocationPermission : SingleEvent()
data class ErrorEvent(val error: String) : SingleEvent()
}
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index b5b7a35..e85838d 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -35,7 +35,7 @@ junit = { group = "junit", name = "junit", version = "4.13.1" }
kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "kotlinx-coroutines" }
kotlinx-coroutines = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" }
leakcanary = { group = "com.squareup.leakcanary", name = "leakcanary-android", version = "2.9.1" }
-maplibre = { group = "org.maplibre.gl", name = "android-sdk", version = "10.0.2" }
+maplibre = { group = "org.maplibre.gl", name = "android-sdk", version = "10.2.0" }
mockk = { group = "io.mockk", name = "mockk", version = "1.10.5" }
mpandroidcharts = { group = "com.github.PhilJay", name = "MPAndroidChart", version = "v3.1.0" }
retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" }