summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/src/main/AndroidManifest.xml3
-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
-rw-r--r--app/src/main/res/drawable/ic_map_marker_blue.xml11
-rw-r--r--app/src/main/res/drawable/ic_map_marker_red.xml28
-rw-r--r--app/src/main/res/layout/fragment_fake_location.xml12
-rw-r--r--app/src/main/res/values/strings.xml2
7 files changed, 292 insertions, 30 deletions
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 2c3b055..74c226c 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -2,6 +2,9 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="foundation.e.privacycentralapp">
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
+
<application
android:name=".PrivacyCentralApplication"
android:allowBackup="true"
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
+ }
+}
diff --git a/app/src/main/res/drawable/ic_map_marker_blue.xml b/app/src/main/res/drawable/ic_map_marker_blue.xml
new file mode 100644
index 0000000..619dc47
--- /dev/null
+++ b/app/src/main/res/drawable/ic_map_marker_blue.xml
@@ -0,0 +1,11 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="512"
+ android:viewportHeight="512"
+ >
+ <path
+ android:fillColor="#FF5290F5"
+ android:pathData="M256,0C153.755,0 70.573,83.182 70.573,185.426c0,126.888 165.939,313.167 173.004,321.035c6.636,7.391 18.222,7.378 24.846,0c7.065,-7.868 173.004,-194.147 173.004,-321.035C441.425,83.182 358.244,0 256,0zM256,278.719c-51.442,0 -93.292,-41.851 -93.292,-93.293S204.559,92.134 256,92.134s93.291,41.851 93.291,93.293S307.441,278.719 256,278.719z"
+ />
+</vector>
diff --git a/app/src/main/res/drawable/ic_map_marker_red.xml b/app/src/main/res/drawable/ic_map_marker_red.xml
new file mode 100644
index 0000000..48fae25
--- /dev/null
+++ b/app/src/main/res/drawable/ic_map_marker_red.xml
@@ -0,0 +1,28 @@
+<!--
+ ~ 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/>.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="512"
+ android:viewportHeight="512"
+ >
+ <path
+ android:fillColor="#FFEA4335"
+ android:pathData="M256,0C153.755,0 70.573,83.182 70.573,185.426c0,126.888 165.939,313.167 173.004,321.035c6.636,7.391 18.222,7.378 24.846,0c7.065,-7.868 173.004,-194.147 173.004,-321.035C441.425,83.182 358.244,0 256,0zM256,278.719c-51.442,0 -93.292,-41.851 -93.292,-93.293S204.559,92.134 256,92.134s93.291,41.851 93.291,93.293S307.441,278.719 256,278.719z"
+ />
+</vector>
diff --git a/app/src/main/res/layout/fragment_fake_location.xml b/app/src/main/res/layout/fragment_fake_location.xml
index 1ebe9ef..38faf67 100644
--- a/app/src/main/res/layout/fragment_fake_location.xml
+++ b/app/src/main/res/layout/fragment_fake_location.xml
@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
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"
@@ -98,13 +99,14 @@
/>
</RadioGroup>
- <ImageView
- android:id="@+id/dummy_img_map"
+
+ <foundation.e.privacycentralapp.features.location.FakeLocationMapView
+ android:id="@+id/mapView"
android:layout_width="match_parent"
- android:layout_height="254dp"
+ android:layout_height="240dp"
+ mapbox:mapbox_cameraZoom="15"
android:layout_marginTop="32dp"
android:layout_marginBottom="32dp"
- android:src="@drawable/dummy_img_map_picker"
/>
<com.google.android.material.textfield.TextInputLayout
@@ -143,9 +145,9 @@
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"
- android:layout_marginTop="32dp"
/>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index ff0cf0a..c0b8348 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -35,4 +35,6 @@
<string name="real_location_mode">Real location mode</string>
<string name="random_location_mode">Random location mode</string>
<string name="fake_location_mode">Fake location mode</string>
+ <string name="user_location_permission_explanation">This app needs location permissions in order to show its functionality.</string>
+ <string name="user_location_permission_not_granted">You didn\'t grant location permission</string>
</resources> \ No newline at end of file