summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/dummy/CityDataSource.kt65
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/dummy/DummyDataSource.kt5
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationFeature.kt93
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationFragment.kt276
-rw-r--r--app/src/main/res/layout/fragment_fake_location.xml75
-rw-r--r--app/src/main/res/values/arrays.xml32
6 files changed, 367 insertions, 179 deletions
diff --git a/app/src/main/java/foundation/e/privacycentralapp/dummy/CityDataSource.kt b/app/src/main/java/foundation/e/privacycentralapp/dummy/CityDataSource.kt
new file mode 100644
index 0000000..3bb2f12
--- /dev/null
+++ b/app/src/main/java/foundation/e/privacycentralapp/dummy/CityDataSource.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2021 E FOUNDATION
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package foundation.e.privacycentralapp.dummy
+
+import kotlin.random.Random
+
+data class City(val name: String, val latitude: Double, val longitude: Double) {
+
+ fun toRandomLocation(): Location {
+ return Location(LocationMode.RANDOM_LOCATION, this.latitude, this.longitude)
+ }
+}
+
+object CityDataSource {
+ private val BARCELONA = Pair(41.3851, 2.1734)
+ private val BUDAPEST = Pair(47.4979, 19.0402)
+ private val ABU_DHABI = Pair(24.4539, 54.3773)
+ private val HYDERABAD = Pair(17.3850, 78.4867)
+ private val QUEZON_CITY = Pair(14.6760, 121.0437)
+ private val PARIS = Pair(48.8566, 2.3522)
+ private val LONDON = Pair(51.5074, 0.1278)
+ private val SHANGHAI = Pair(31.2304, 121.4737)
+ private val MADRID = Pair(40.4168, 3.7038)
+ private val LAHORE = Pair(31.5204, 74.3587)
+ private val CHICAGO = Pair(41.8781, 87.6298)
+
+ // LatLong Array, the order should be the same as that of R.array.cities
+ private val latLongArray = arrayOf(
+ BARCELONA,
+ BUDAPEST,
+ ABU_DHABI,
+ HYDERABAD,
+ QUEZON_CITY,
+ PARIS,
+ LONDON,
+ SHANGHAI,
+ MADRID,
+ LAHORE,
+ CHICAGO
+ )
+
+ fun getRandomCity(cities: Array<String>): City {
+ if (cities.size != latLongArray.size) {
+ throw IllegalStateException("LatLong array must have the same number of element as in cities array.")
+ }
+ val randomIndex = Random.nextInt(cities.size)
+ val latLong = latLongArray[randomIndex]
+ return City(cities[randomIndex], latLong.first, latLong.second)
+ }
+}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/dummy/DummyDataSource.kt b/app/src/main/java/foundation/e/privacycentralapp/dummy/DummyDataSource.kt
index aef994b..fe61354 100644
--- a/app/src/main/java/foundation/e/privacycentralapp/dummy/DummyDataSource.kt
+++ b/app/src/main/java/foundation/e/privacycentralapp/dummy/DummyDataSource.kt
@@ -185,7 +185,10 @@ object DummyDataSource {
LocationMode.REAL_LOCATION ->
_location.value =
Location(LocationMode.REAL_LOCATION, 24.39, 71.80)
- LocationMode.RANDOM_LOCATION -> _location.value = randomLocation()
+ LocationMode.RANDOM_LOCATION -> {
+ requireNotNull(location) { "Custom location should be null" }
+ _location.value = location
+ }
LocationMode.CUSTOM_LOCATION -> {
requireNotNull(location) { "Custom location should be null" }
_location.value = location.copy(mode = LocationMode.CUSTOM_LOCATION)
diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationFeature.kt b/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationFeature.kt
index a11619a..d94f71c 100644
--- a/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationFeature.kt
+++ b/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationFeature.kt
@@ -18,16 +18,17 @@
package foundation.e.privacycentralapp.features.location
import android.util.Log
+import com.mapbox.mapboxsdk.geometry.LatLng
import foundation.e.flowmvi.Actor
import foundation.e.flowmvi.Reducer
import foundation.e.flowmvi.SingleEventProducer
import foundation.e.flowmvi.feature.BaseFeature
+import foundation.e.privacycentralapp.dummy.CityDataSource
import foundation.e.privacycentralapp.dummy.DummyDataSource
import foundation.e.privacycentralapp.dummy.Location
import foundation.e.privacycentralapp.dummy.LocationMode
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.map
// Define a state machine for Fake location feature
class FakeLocationFeature(
@@ -44,10 +45,7 @@ class FakeLocationFeature(
{ message -> Log.d("FakeLocationFeature", message) },
singleEventProducer
) {
- sealed class State {
- object InitialState : State()
- data class LocationState(val location: Location) : State()
- }
+ data class State(val location: Location)
sealed class SingleEvent {
object RandomLocationSelectedEvent : SingleEvent()
@@ -57,47 +55,91 @@ class FakeLocationFeature(
}
sealed class Action {
- object ObserveLocationAction : Action()
+ data class UpdateLocationAction(val latLng: LatLng) : Action()
object UseRealLocationAction : Action()
- object UseRandomLocationAction : Action()
+ data class UseRandomLocationAction(val cities: Array<String>) : Action() {
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (javaClass != other?.javaClass) return false
+
+ other as UseRandomLocationAction
+
+ if (!cities.contentEquals(other.cities)) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ return cities.contentHashCode()
+ }
+ }
+
object UseSpecificLocationAction : Action()
- data class AddSpecificLocationAction(val latitude: Double, val longitude: Double) : Action()
+ data class SetFakeLocationAction(val latitude: Double, val longitude: Double) : Action()
}
sealed class Effect {
- data class LocationUpdatedEffect(val location: Location) : Effect()
+ data class LocationUpdatedEffect(val latitude: Double, val longitude: Double) : Effect()
object RealLocationSelectedEffect : Effect()
object RandomLocationSelectedEffect : Effect()
- data class SpecificLocationSelectedEffect(val location: Location) : Effect()
+ object SpecificLocationSelectedEffect : Effect()
object SpecificLocationSavedEffect : Effect()
data class ErrorEffect(val message: String) : Effect()
}
companion object {
fun create(
- initialState: State = State.InitialState,
+ initialState: State = State(
+ Location(
+ LocationMode.REAL_LOCATION,
+ 0.0,
+ 0.0
+ )
+ ),
coroutineScope: CoroutineScope
) = FakeLocationFeature(
initialState, coroutineScope,
reducer = { state, effect ->
when (effect) {
- Effect.RandomLocationSelectedEffect,
- Effect.RealLocationSelectedEffect, is Effect.ErrorEffect, Effect.SpecificLocationSavedEffect -> state
- is Effect.LocationUpdatedEffect -> State.LocationState(effect.location)
- is Effect.SpecificLocationSelectedEffect -> State.LocationState(effect.location)
+ Effect.RandomLocationSelectedEffect -> state.copy(
+ location = state.location.copy(
+ mode = LocationMode.RANDOM_LOCATION
+ )
+ )
+ Effect.RealLocationSelectedEffect -> state.copy(
+ location = state.location.copy(
+ mode = LocationMode.REAL_LOCATION
+ )
+ )
+ is Effect.ErrorEffect, Effect.SpecificLocationSavedEffect -> state
+ is Effect.LocationUpdatedEffect -> state.copy(
+ location = state.location.copy(
+ latitude = effect.latitude,
+ longitude = effect.longitude
+ )
+ )
+ is Effect.SpecificLocationSelectedEffect -> state.copy(
+ location = state.location.copy(
+ mode = LocationMode.CUSTOM_LOCATION
+ )
+ )
}
},
actor = { _, action ->
when (action) {
- is Action.ObserveLocationAction -> DummyDataSource.location.map {
- Effect.LocationUpdatedEffect(it)
- }
- is Action.AddSpecificLocationAction -> {
+ is Action.UpdateLocationAction -> flowOf(
+ Effect.LocationUpdatedEffect(
+ action.latLng.latitude,
+ action.latLng.longitude
+ )
+ )
+ is Action.SetFakeLocationAction -> {
val location = Location(
LocationMode.CUSTOM_LOCATION,
action.latitude,
action.longitude
)
+ // TODO: Call fake location api with specific coordinates here.
val success = DummyDataSource.setLocationMode(
LocationMode.CUSTOM_LOCATION,
location
@@ -112,8 +154,13 @@ class FakeLocationFeature(
)
}
}
- Action.UseRandomLocationAction -> {
- val success = DummyDataSource.setLocationMode(LocationMode.RANDOM_LOCATION)
+ is Action.UseRandomLocationAction -> {
+ val randomCity = CityDataSource.getRandomCity(action.cities)
+ // TODO: Call fake location api with random location here.
+ val success = DummyDataSource.setLocationMode(
+ LocationMode.RANDOM_LOCATION,
+ randomCity.toRandomLocation()
+ )
if (success) {
flowOf(
Effect.RandomLocationSelectedEffect
@@ -125,6 +172,7 @@ class FakeLocationFeature(
}
}
Action.UseRealLocationAction -> {
+ // TODO: Call turn off fake location api here.
val success = DummyDataSource.setLocationMode(LocationMode.REAL_LOCATION)
if (success) {
flowOf(
@@ -137,8 +185,7 @@ class FakeLocationFeature(
}
}
Action.UseSpecificLocationAction -> {
- val location = DummyDataSource.location.value
- flowOf(Effect.SpecificLocationSelectedEffect(location.copy(mode = LocationMode.CUSTOM_LOCATION)))
+ flowOf(Effect.SpecificLocationSelectedEffect)
}
}
},
diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationFragment.kt b/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationFragment.kt
index 24ea426..96bebb7 100644
--- a/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationFragment.kt
+++ b/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationFragment.kt
@@ -20,47 +20,47 @@ package foundation.e.privacycentralapp.features.location
import android.annotation.SuppressLint
import android.content.Context
import android.os.Bundle
+import android.os.Looper
import android.text.Editable
import android.util.Log
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
-import android.widget.Button
+import android.widget.EditText
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.RadioButton
import android.widget.Toast
import android.widget.Toolbar
import androidx.annotation.NonNull
-import androidx.core.content.res.ResourcesCompat
+import androidx.core.widget.addTextChangedListener
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import com.google.android.material.textfield.TextInputLayout
+import com.mapbox.android.core.location.LocationEngineCallback
+import com.mapbox.android.core.location.LocationEngineRequest
+import com.mapbox.android.core.location.LocationEngineResult
import com.mapbox.android.core.permissions.PermissionsListener
import com.mapbox.android.core.permissions.PermissionsManager
import com.mapbox.mapboxsdk.Mapbox
+import com.mapbox.mapboxsdk.camera.CameraUpdateFactory
import com.mapbox.mapboxsdk.geometry.LatLng
import com.mapbox.mapboxsdk.location.LocationComponent
import com.mapbox.mapboxsdk.location.LocationComponentActivationOptions
+import com.mapbox.mapboxsdk.location.LocationUpdate
import com.mapbox.mapboxsdk.location.modes.CameraMode
import com.mapbox.mapboxsdk.location.modes.RenderMode
import com.mapbox.mapboxsdk.maps.MapboxMap
import com.mapbox.mapboxsdk.maps.OnMapReadyCallback
import com.mapbox.mapboxsdk.maps.Style
-import com.mapbox.mapboxsdk.style.layers.Property.NONE
-import com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconAllowOverlap
-import com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconIgnorePlacement
-import com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconImage
-import com.mapbox.mapboxsdk.style.layers.PropertyFactory.visibility
-import com.mapbox.mapboxsdk.style.layers.SymbolLayer
-import com.mapbox.mapboxsdk.style.sources.GeoJsonSource
import foundation.e.flowmvi.MVIView
import foundation.e.privacycentralapp.R
import foundation.e.privacycentralapp.dummy.LocationMode
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
@@ -69,18 +69,66 @@ class FakeLocationFragment :
MVIView<FakeLocationFeature.State, FakeLocationFeature.Action>,
PermissionsListener {
+ private var isCameraMoved: Boolean = false
private lateinit var permissionsManager: PermissionsManager
private val viewModel: FakeLocationViewModel by viewModels()
private lateinit var mapView: FakeLocationMapView
private lateinit var mapboxMap: MapboxMap
+ private lateinit var useRealLocationRadioBtn: RadioButton
+ private lateinit var useRandomLocationRadioBtn: RadioButton
+ private lateinit var useSpecificLocationRadioBtn: RadioButton
+ private lateinit var latEditText: EditText
+ private lateinit var longEditText: EditText
+
private var hoveringMarker: ImageView? = null
- private var mutableLatLongFlow = MutableStateFlow(LatLng())
- private var latLong = mutableLatLongFlow.asStateFlow()
+ private var inputJob: Job? = null
+
+ // Callback which updates the map in realtime.
+ private val locationChangeCallback: LocationEngineCallback<LocationEngineResult> =
+ object : LocationEngineCallback<LocationEngineResult> {
+ override fun onSuccess(result: LocationEngineResult?) {
+ result?.lastLocation?.let {
+ Log.d(TAG, "Last location: ${it.latitude}, ${it.longitude}")
+ mapboxMap.locationComponent.forceLocationUpdate(
+ LocationUpdate.Builder().location(it).animationDuration(200)
+ .build()
+ )
+ if (!isCameraMoved) {
+ mapboxMap.animateCamera(
+ CameraUpdateFactory.newLatLng(
+ LatLng(
+ it.latitude,
+ it.longitude
+ )
+ )
+ )
+ }
+ // Only update location when location mode is set to real location
+ if (viewModel.fakeLocationFeature.state.value.location.mode != LocationMode.CUSTOM_LOCATION) {
+ viewModel.submitAction(
+ FakeLocationFeature.Action.UpdateLocationAction(
+ LatLng(
+ it.latitude,
+ it.longitude
+ )
+ )
+ )
+ }
+ }
+ }
+
+ override fun onFailure(exception: Exception) {
+ Log.e(TAG, "${exception.message}")
+ }
+ }
companion object {
- private const val DROPPED_MARKER_LAYER_ID = "DROPPED_MARKER_LAYER_ID"
+ private const val DEBOUNCE_PERIOD = 1000L
+ private const val TAG = "FakeLocationFragment"
+ private const val DEFAULT_INTERVAL_IN_MILLISECONDS = 1000L
+ private const val DEFAULT_MAX_WAIT_TIME = DEFAULT_INTERVAL_IN_MILLISECONDS * 5
}
override fun onCreate(savedInstanceState: Bundle?) {
@@ -91,16 +139,28 @@ class FakeLocationFragment :
lifecycleScope.launchWhenStarted {
viewModel.fakeLocationFeature.singleEvents.collect { event ->
when (event) {
- is FakeLocationFeature.SingleEvent.RandomLocationSelectedEvent -> displayToast("Random location selected")
- is FakeLocationFeature.SingleEvent.SpecificLocationSavedEvent -> displayToast("Specific location selected")
- is FakeLocationFeature.SingleEvent.ErrorEvent -> displayToast(event.error)
- FakeLocationFeature.SingleEvent.RealLocationSelectedEvent -> displayToast("Real location selected")
+ is FakeLocationFeature.SingleEvent.RandomLocationSelectedEvent -> {
+ displayToast("Random location selected")
+ hoveringMarker?.visibility = View.GONE
+ isCameraMoved = false
+ }
+ is FakeLocationFeature.SingleEvent.SpecificLocationSavedEvent -> {
+ // Hide camera hover marker when custom location is picked from map.
+ hoveringMarker?.visibility = View.GONE
+ isCameraMoved = false
+ }
+ is FakeLocationFeature.SingleEvent.ErrorEvent -> {
+ displayToast(event.error)
+ isCameraMoved = false
+ }
+ FakeLocationFeature.SingleEvent.RealLocationSelectedEvent -> {
+ displayToast("Real location selected")
+ hoveringMarker?.visibility = View.GONE
+ isCameraMoved = false
+ }
}
}
}
- lifecycleScope.launchWhenStarted {
- viewModel.submitAction(FakeLocationFeature.Action.ObserveLocationAction)
- }
}
override fun onAttach(context: Context) {
@@ -117,12 +177,12 @@ class FakeLocationFragment :
super.onViewCreated(view, savedInstanceState)
val toolbar = view.findViewById<Toolbar>(R.id.toolbar)
setupToolbar(toolbar)
+ setupViews(view)
mapView = view.findViewById<FakeLocationMapView>(R.id.mapView)
.setup(savedInstanceState) { mapboxMap ->
this.mapboxMap = mapboxMap
mapboxMap.setStyle(Style.MAPBOX_STREETS) { style ->
enableLocationPlugin(style)
-
hoveringMarker = ImageView(requireContext())
.apply {
setImageResource(R.drawable.mapbox_marker_icon_default)
@@ -133,56 +193,72 @@ class FakeLocationFragment :
layoutParams = params
}
mapView.addView(hoveringMarker)
- initDroppedMarker(style)
+ hoveringMarker?.visibility = View.GONE // Keep hovering marker hidden by default
+
+ mapboxMap.addOnCameraMoveStartedListener {
+ // Show marker when user starts to move across the map.
+ if (mapView.isEnabled) {
+ hoveringMarker?.visibility = View.VISIBLE
+ }
+ isCameraMoved = true
+ }
+
mapboxMap.addOnCameraMoveListener {
- mutableLatLongFlow.value = mapboxMap.cameraPosition.target
+ if (mapView.isEnabled) {
+ viewModel.submitAction(
+ FakeLocationFeature.Action.UpdateLocationAction(
+ mapboxMap.cameraPosition.target
+ )
+ )
+ }
}
- mapboxMap.addOnCameraIdleListener { Log.d("Mapview", "camera move ended") }
+ // Bind click listeners once map is ready.
+ bindClickListeners()
}
}
- bindClickListeners(view)
}
- private fun bindClickListeners(fragmentView: View) {
- val latEditText =
- fragmentView.findViewById<TextInputLayout>(R.id.edittext_latitude).editText
- val longEditText =
- fragmentView.findViewById<TextInputLayout>(R.id.edittext_longitude).editText
-
- fragmentView.let {
- it.findViewById<RadioButton>(R.id.radio_use_real_location)
- .setOnClickListener { radioButton ->
- toggleLocationType(radioButton)
- }
- it.findViewById<RadioButton>(R.id.radio_use_random_location)
- .setOnClickListener { radioButton ->
- toggleLocationType(radioButton)
- }
- it.findViewById<RadioButton>(R.id.radio_use_specific_location)
- .setOnClickListener { radioButton ->
- toggleLocationType(radioButton)
- }
- it.findViewById<Button>(R.id.button_add_location)
- .setOnClickListener {
- val latitude = latEditText?.text.toString().toDouble()
- val longitude = longEditText?.text.toString().toDouble()
- saveSpecificLocation(latitude, longitude)
- }
- }
+ private fun setupViews(view: View) {
+ useRealLocationRadioBtn = view.findViewById(R.id.radio_use_real_location)
+ useRandomLocationRadioBtn = view.findViewById(R.id.radio_use_random_location)
+ useSpecificLocationRadioBtn = view.findViewById(R.id.radio_use_specific_location)
+ latEditText = view.findViewById<TextInputLayout>(R.id.edittext_latitude).editText!!
+ longEditText = view.findViewById<TextInputLayout>(R.id.edittext_longitude).editText!!
+ }
- lifecycleScope.launch {
- latLong.collect {
- latEditText?.text =
- Editable.Factory.getInstance().newEditable(it.latitude.toString())
- longEditText?.text =
- Editable.Factory.getInstance().newEditable(it.longitude.toString())
+ private fun bindClickListeners() {
+ useRealLocationRadioBtn
+ .setOnClickListener { radioButton ->
+ toggleLocationType(radioButton)
+ }
+ useRandomLocationRadioBtn
+ .setOnClickListener { radioButton ->
+ toggleLocationType(radioButton)
}
+ useSpecificLocationRadioBtn
+ .setOnClickListener { radioButton ->
+ toggleLocationType(radioButton)
+ }
+
+ arrayOf(latEditText, longEditText).forEach { editText ->
+ editText.addTextChangedListener(
+ afterTextChanged = {
+ inputJob?.cancel()
+ if (it?.length ?: 0 > 0 && editText.isEnabled) {
+ inputJob = lifecycleScope.launch {
+ delay(DEBOUNCE_PERIOD)
+ ensureActive()
+ Log.d("FakeLocation", "Call save location here")
+ }
+ }
+ }
+ )
}
}
private fun saveSpecificLocation(latitude: Double, longitude: Double) {
viewModel.submitAction(
- FakeLocationFeature.Action.AddSpecificLocationAction(latitude, longitude)
+ FakeLocationFeature.Action.SetFakeLocationAction(latitude, longitude)
)
}
@@ -196,7 +272,11 @@ class FakeLocationFragment :
}
R.id.radio_use_random_location ->
if (checked) {
- viewModel.submitAction(FakeLocationFeature.Action.UseRandomLocationAction)
+ viewModel.submitAction(
+ FakeLocationFeature.Action.UseRandomLocationAction(
+ resources.getStringArray(R.array.cities)
+ )
+ )
}
R.id.radio_use_specific_location ->
if (checked) {
@@ -213,26 +293,18 @@ class FakeLocationFragment :
}
override fun render(state: FakeLocationFeature.State) {
- when (state) {
- is FakeLocationFeature.State.LocationState -> {
- Log.d("FakeMyLocation", "State: $state")
- when (state.location.mode) {
- LocationMode.REAL_LOCATION, LocationMode.RANDOM_LOCATION ->
- view?.let {
- it.findViewById<RadioButton>(R.id.radio_use_random_location).isChecked =
- (state.location.mode == LocationMode.RANDOM_LOCATION)
- it.findViewById<RadioButton>(R.id.radio_use_real_location).isChecked =
- (state.location.mode == LocationMode.REAL_LOCATION)
- }
- LocationMode.CUSTOM_LOCATION -> view?.let {
- it.findViewById<RadioButton>(R.id.radio_use_specific_location).isChecked =
- true
- }
- }
- }
- FakeLocationFeature.State.InitialState -> {
- }
- }
+ Log.d("FakeMyLocation", "State: $state")
+ latEditText.text =
+ Editable.Factory.getInstance().newEditable(state.location.latitude.toString())
+ longEditText.text =
+ Editable.Factory.getInstance().newEditable(state.location.longitude.toString())
+ useRandomLocationRadioBtn.isChecked = (state.location.mode == LocationMode.RANDOM_LOCATION)
+ useSpecificLocationRadioBtn.isChecked =
+ (state.location.mode == LocationMode.CUSTOM_LOCATION)
+ useRealLocationRadioBtn.isChecked = (state.location.mode == LocationMode.REAL_LOCATION)
+ latEditText.isEnabled = (state.location.mode == LocationMode.CUSTOM_LOCATION)
+ longEditText.isEnabled = (state.location.mode == LocationMode.CUSTOM_LOCATION)
+ mapView.isEnabled = (state.location.mode == LocationMode.CUSTOM_LOCATION)
}
override fun actions(): Flow<FakeLocationFeature.Action> = viewModel.actions
@@ -241,53 +313,31 @@ class FakeLocationFragment :
private fun enableLocationPlugin(@NonNull loadedMapStyle: Style) {
// Check if permissions are enabled and if not request
if (PermissionsManager.areLocationPermissionsGranted(requireContext())) {
-
val locationComponent: LocationComponent = mapboxMap.locationComponent
locationComponent.activateLocationComponent(
LocationComponentActivationOptions.builder(
requireContext(), loadedMapStyle
- ).build()
+ ).useDefaultLocationEngine(true).build()
)
locationComponent.isLocationComponentEnabled = true
locationComponent.cameraMode = CameraMode.TRACKING
locationComponent.renderMode = RenderMode.NORMAL
+ locationComponent.locationEngine?.let {
+ it.requestLocationUpdates(
+ LocationEngineRequest.Builder(DEFAULT_INTERVAL_IN_MILLISECONDS)
+ .setPriority(LocationEngineRequest.PRIORITY_HIGH_ACCURACY)
+ .setMaxWaitTime(DEFAULT_MAX_WAIT_TIME).build(),
+ locationChangeCallback,
+ Looper.getMainLooper()
+ )
+ it.getLastLocation(locationChangeCallback)
+ }
} else {
permissionsManager = PermissionsManager(this)
permissionsManager.requestLocationPermissions(requireActivity())
}
}
- private fun initDroppedMarker(loadedMapStyle: Style) {
- // Add the marker image to map
- loadedMapStyle.apply {
- ResourcesCompat.getDrawable(
- resources,
- R.drawable.ic_map_marker_blue,
- requireContext().theme
- )
- ?.let {
- addImage(
- "dropped-icon-image",
- it
- )
- }
- addSource(GeoJsonSource("dropped-marker-source-id"))
- addLayer(
- SymbolLayer(
- DROPPED_MARKER_LAYER_ID,
- "dropped-marker-source-id"
- ).apply {
- setProperties(
- iconImage("dropped-icon-image"),
- visibility(NONE),
- iconAllowOverlap(true),
- iconIgnorePlacement(true)
- )
- }
- )
- }
- }
-
override fun onStart() {
super.onStart()
mapView.onStart()
diff --git a/app/src/main/res/layout/fragment_fake_location.xml b/app/src/main/res/layout/fragment_fake_location.xml
index 38faf67..d67a981 100644
--- a/app/src/main/res/layout/fragment_fake_location.xml
+++ b/app/src/main/res/layout/fragment_fake_location.xml
@@ -3,30 +3,30 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:mapbox="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
android:background="@color/white"
+ android:layout_height="match_parent"
+ android:layout_width="match_parent"
>
<Toolbar
+ android:background="@color/white"
android:id="@+id/toolbar"
- android:layout_width="match_parent"
- android:layout_height="?android:attr/actionBarSize"
android:layout_gravity="top|center"
- android:background="@color/white"
+ android:layout_height="?android:attr/actionBarSize"
+ android:layout_width="match_parent"
tools:layout_height="56dp"
/>
<androidx.core.widget.NestedScrollView
- android:layout_width="match_parent"
android:layout_height="match_parent"
- android:layout_marginTop="?android:attr/actionBarSize"
android:layout_marginBottom="32dp"
+ android:layout_marginTop="?android:attr/actionBarSize"
+ android:layout_width="match_parent"
>
<LinearLayout
- android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:layout_width="match_parent"
android:orientation="vertical"
android:paddingLeft="32dp"
android:paddingRight="32dp"
@@ -35,9 +35,9 @@
<TextView
android:id="@+id/fake_location_info"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
android:paddingTop="16dp"
android:text="@string/fake_location_info"
android:textColor="@color/black"
@@ -45,23 +45,23 @@
/>
<TextView
- android:id="@+id/learn_more_fake_location"
- android:layout_width="wrap_content"
- android:layout_height="48dp"
android:fontFamily="sans-serif-medium"
android:gravity="center_vertical"
+ android:id="@+id/learn_more_fake_location"
+ android:layout_height="48dp"
+ android:layout_width="wrap_content"
android:text="@string/learn_more"
android:textColor="#007fff"
android:textSize="14sp"
/>
<TextView
+ android:fontFamily="sans-serif-medium"
android:id="@+id/my_location_header"
- android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:fontFamily="sans-serif-medium"
- android:paddingTop="16dp"
+ android:layout_width="wrap_content"
android:paddingBottom="8dp"
+ android:paddingTop="16dp"
android:text="@string/my_location_title"
android:textColor="@color/black"
android:textSize="14sp"
@@ -69,31 +69,31 @@
<RadioGroup
android:id="@+id/location_choices"
- android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:layout_width="match_parent"
android:orientation="vertical"
>
<foundation.e.privacycentralapp.common.RightRadioButton
android:id="@+id/radio_use_real_location"
- android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:layout_width="match_parent"
android:text="@string/use_real_location"
android:textSize="16sp"
/>
<foundation.e.privacycentralapp.common.RightRadioButton
android:id="@+id/radio_use_random_location"
- android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:layout_width="match_parent"
android:text="@string/use_random_location"
android:textSize="16sp"
/>
<foundation.e.privacycentralapp.common.RightRadioButton
android:id="@+id/radio_use_specific_location"
- android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:layout_width="match_parent"
android:text="@string/use_specific_location"
android:textSize="16sp"
/>
@@ -102,53 +102,44 @@
<foundation.e.privacycentralapp.features.location.FakeLocationMapView
android:id="@+id/mapView"
- android:layout_width="match_parent"
android:layout_height="240dp"
- mapbox:mapbox_cameraZoom="15"
- android:layout_marginTop="32dp"
android:layout_marginBottom="32dp"
+ android:layout_marginTop="32dp"
+ android:layout_width="match_parent"
+ mapbox:mapbox_cameraZoom="8"
/>
<com.google.android.material.textfield.TextInputLayout
- android:id="@+id/edittext_longitude"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
android:hint="@string/longitude"
+ android:id="@+id/edittext_longitude"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
>
<com.google.android.material.textfield.TextInputEditText
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
android:inputType="numberDecimal"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
- android:id="@+id/edittext_latitude"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
- android:layout_width="match_parent"
+ android:hint="@string/latitude"
+ android:id="@+id/edittext_latitude"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
- android:hint="@string/latitude"
+ android:layout_width="match_parent"
>
<com.google.android.material.textfield.TextInputEditText
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
android:inputType="numberDecimal"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
/>
</com.google.android.material.textfield.TextInputLayout>
- <Button
- android:id="@+id/button_add_location"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center_horizontal"
- android:layout_marginTop="32dp"
- android:text="@string/add_location"
- app:backgroundTint="#007fff"
- />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</FrameLayout> \ No newline at end of file
diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml
new file mode 100644
index 0000000..702947b
--- /dev/null
+++ b/app/src/main/res/values/arrays.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2021 E FOUNDATION
+ ~
+ ~ This program is free software: you can redistribute it and/or modify
+ ~ it under the terms of the GNU General Public License as published by
+ ~ the Free Software Foundation, either version 3 of the License, or
+ ~ (at your option) any later version.
+ ~
+ ~ This program is distributed in the hope that it will be useful,
+ ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ ~ GNU General Public License for more details.
+ ~
+ ~ You should have received a copy of the GNU General Public License
+ ~ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ -->
+
+<resources>
+ <string-array name="cities">
+ <item>Barcelona</item>
+ <item>Budapest</item>
+ <item>Abu Dhabi</item>
+ <item>Hyderabad</item>
+ <item>Quezon City</item>
+ <item>Paris</item>
+ <item>London</item>
+ <item>Shanghai</item>
+ <item>Madrid</item>
+ <item>Lahore</item>
+ <item>Chicago</item>
+ </string-array>
+</resources> \ No newline at end of file