diff options
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 |