summaryrefslogtreecommitdiff
path: root/app/src/main/java
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/main/java')
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationFragment.kt221
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationMapView.kt45
2 files changed, 241 insertions, 25 deletions
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 5b58293..24ea426 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
@@ -17,31 +17,72 @@
package foundation.e.privacycentralapp.features.location
+import android.annotation.SuppressLint
+import android.content.Context
import android.os.Bundle
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.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.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import com.google.android.material.textfield.TextInputLayout
+import com.mapbox.android.core.permissions.PermissionsListener
+import com.mapbox.android.core.permissions.PermissionsManager
+import com.mapbox.mapboxsdk.Mapbox
+import com.mapbox.mapboxsdk.geometry.LatLng
+import com.mapbox.mapboxsdk.location.LocationComponent
+import com.mapbox.mapboxsdk.location.LocationComponentActivationOptions
+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.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
class FakeLocationFragment :
Fragment(R.layout.fragment_fake_location),
- MVIView<FakeLocationFeature.State, FakeLocationFeature.Action> {
+ MVIView<FakeLocationFeature.State, FakeLocationFeature.Action>,
+ PermissionsListener {
+ private lateinit var permissionsManager: PermissionsManager
private val viewModel: FakeLocationViewModel by viewModels()
+ private lateinit var mapView: FakeLocationMapView
+ private lateinit var mapboxMap: MapboxMap
+ private var hoveringMarker: ImageView? = null
+
+ private var mutableLatLongFlow = MutableStateFlow(LatLng())
+ private var latLong = mutableLatLongFlow.asStateFlow()
+
+ companion object {
+ private const val DROPPED_MARKER_LAYER_ID = "DROPPED_MARKER_LAYER_ID"
+ }
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lifecycleScope.launchWhenStarted {
@@ -62,6 +103,11 @@ class FakeLocationFragment :
}
}
+ override fun onAttach(context: Context) {
+ super.onAttach(context)
+ Mapbox.getInstance(requireContext(), getString(R.string.mapbox_key))
+ }
+
private fun displayToast(message: String) {
Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT)
.show()
@@ -71,10 +117,38 @@ class FakeLocationFragment :
super.onViewCreated(view, savedInstanceState)
val toolbar = view.findViewById<Toolbar>(R.id.toolbar)
setupToolbar(toolbar)
+ 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)
+ val params = FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.CENTER
+ )
+ layoutParams = params
+ }
+ mapView.addView(hoveringMarker)
+ initDroppedMarker(style)
+ mapboxMap.addOnCameraMoveListener {
+ mutableLatLongFlow.value = mapboxMap.cameraPosition.target
+ }
+ mapboxMap.addOnCameraIdleListener { Log.d("Mapview", "camera move ended") }
+ }
+ }
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 ->
@@ -90,15 +164,20 @@ class FakeLocationFragment :
}
it.findViewById<Button>(R.id.button_add_location)
.setOnClickListener {
- val latitude =
- fragmentView.findViewById<TextInputLayout>(R.id.edittext_latitude).editText?.text.toString()
- .toDouble()
- val longitude =
- fragmentView.findViewById<TextInputLayout>(R.id.edittext_longitude).editText?.text.toString()
- .toDouble()
+ val latitude = latEditText?.text.toString().toDouble()
+ val longitude = longEditText?.text.toString().toDouble()
saveSpecificLocation(latitude, longitude)
}
}
+
+ lifecycleScope.launch {
+ latLong.collect {
+ latEditText?.text =
+ Editable.Factory.getInstance().newEditable(it.latitude.toString())
+ longEditText?.text =
+ Editable.Factory.getInstance().newEditable(it.longitude.toString())
+ }
+ }
}
private fun saveSpecificLocation(latitude: Double, longitude: Double) {
@@ -144,28 +223,10 @@ class FakeLocationFragment :
(state.location.mode == LocationMode.RANDOM_LOCATION)
it.findViewById<RadioButton>(R.id.radio_use_real_location).isChecked =
(state.location.mode == LocationMode.REAL_LOCATION)
- it.findViewById<ImageView>(R.id.dummy_img_map).visibility = View.GONE
- it.findViewById<TextInputLayout>(R.id.edittext_latitude).visibility =
- View.GONE
- it.findViewById<TextInputLayout>(R.id.edittext_longitude).visibility =
- View.GONE
- it.findViewById<Button>(R.id.button_add_location).visibility = View.GONE
}
LocationMode.CUSTOM_LOCATION -> view?.let {
it.findViewById<RadioButton>(R.id.radio_use_specific_location).isChecked =
true
- it.findViewById<ImageView>(R.id.dummy_img_map).visibility = View.VISIBLE
- it.findViewById<TextInputLayout>(R.id.edittext_latitude).apply {
- visibility = View.VISIBLE
- editText?.text = Editable.Factory.getInstance()
- .newEditable(state.location.latitude.toString())
- }
- it.findViewById<TextInputLayout>(R.id.edittext_longitude).apply {
- visibility = View.VISIBLE
- editText?.text = Editable.Factory.getInstance()
- .newEditable(state.location.longitude.toString())
- }
- it.findViewById<Button>(R.id.button_add_location).visibility = View.VISIBLE
}
}
}
@@ -175,4 +236,114 @@ class FakeLocationFragment :
}
override fun actions(): Flow<FakeLocationFeature.Action> = viewModel.actions
+
+ @SuppressLint("MissingPermission")
+ 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()
+ )
+ locationComponent.isLocationComponentEnabled = true
+ locationComponent.cameraMode = CameraMode.TRACKING
+ locationComponent.renderMode = RenderMode.NORMAL
+ } 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()
+ }
+
+ override fun onResume() {
+ super.onResume()
+ mapView.onResume()
+ }
+
+ override fun onPause() {
+ super.onPause()
+ mapView.onPause()
+ }
+
+ override fun onStop() {
+ super.onStop()
+ mapView.onStop()
+ }
+
+ override fun onLowMemory() {
+ super.onLowMemory()
+ mapView.onLowMemory()
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ mapView.onDestroy()
+ }
+
+ override fun onExplanationNeeded(permissionsToExplain: MutableList<String>?) {
+ Toast.makeText(
+ requireContext(),
+ R.string.user_location_permission_explanation,
+ Toast.LENGTH_LONG
+ ).show()
+ }
+
+ override fun onPermissionResult(granted: Boolean) {
+ if (granted) {
+ val style = mapboxMap.style
+ if (style != null) {
+ enableLocationPlugin(style)
+ }
+ } else {
+ Toast.makeText(
+ requireContext(),
+ R.string.user_location_permission_not_granted,
+ Toast.LENGTH_LONG
+ ).show()
+ }
+ }
}
+
+fun FakeLocationMapView.setup(savedInstanceState: Bundle?, callback: OnMapReadyCallback) =
+ this.apply {
+ onCreate(savedInstanceState)
+ getMapAsync(callback)
+ }
diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationMapView.kt b/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationMapView.kt
new file mode 100644
index 0000000..cd0030a
--- /dev/null
+++ b/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationMapView.kt
@@ -0,0 +1,45 @@
+/*
+ * 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.features.location
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.util.AttributeSet
+import android.view.MotionEvent
+import com.mapbox.mapboxsdk.maps.MapView
+
+class FakeLocationMapView @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0
+) : MapView(context, attrs, defStyleAttr) {
+
+ /**
+ * Overrides onTouchEvent because this MapView is part of a scroll view
+ * and we want this map view to consume all touch events originating on this view.
+ */
+ @SuppressLint("ClickableViewAccessibility")
+ override fun onTouchEvent(event: MotionEvent?): Boolean {
+ when (event?.action) {
+ MotionEvent.ACTION_DOWN -> parent.requestDisallowInterceptTouchEvent(true)
+ MotionEvent.ACTION_UP -> parent.requestDisallowInterceptTouchEvent(false)
+ }
+ super.onTouchEvent(event)
+ return true
+ }
+}