diff options
Diffstat (limited to 'app/src/main/java/foundation/e/advancedprivacy/features/location/FakeLocationFragment.kt')
-rw-r--r-- | app/src/main/java/foundation/e/advancedprivacy/features/location/FakeLocationFragment.kt | 208 |
1 files changed, 127 insertions, 81 deletions
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 |