diff --git a/androidshared/src/main/res/color/color_primary_low_emphasis.xml b/androidshared/src/main/res/color/color_primary_low_emphasis.xml
deleted file mode 100644
index 6d09caa48c1..00000000000
--- a/androidshared/src/main/res/color/color_primary_low_emphasis.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
-
-
diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/support/FakeClickableMapFragment.kt b/collect_app/src/androidTest/java/org/odk/collect/android/support/FakeClickableMapFragment.kt
index 1e7711f0355..b0f5e0a34c0 100644
--- a/collect_app/src/androidTest/java/org/odk/collect/android/support/FakeClickableMapFragment.kt
+++ b/collect_app/src/androidTest/java/org/odk/collect/android/support/FakeClickableMapFragment.kt
@@ -3,13 +3,13 @@ package org.odk.collect.android.support
import android.os.Handler
import android.os.Looper
import androidx.fragment.app.Fragment
-import org.odk.collect.maps.traces.LineDescription
import org.odk.collect.maps.MapFragment
import org.odk.collect.maps.MapPoint
import org.odk.collect.maps.circles.CircleDescription
-import org.odk.collect.maps.traces.PolygonDescription
import org.odk.collect.maps.markers.MarkerDescription
import org.odk.collect.maps.markers.MarkerIconDescription
+import org.odk.collect.maps.traces.LineDescription
+import org.odk.collect.maps.traces.PolygonDescription
class FakeClickableMapFragment : Fragment(), MapFragment {
@@ -46,11 +46,6 @@ class FakeClickableMapFragment : Fragment(), MapFragment {
animate: Boolean
) {}
- override fun addMarker(markerDescription: MarkerDescription): Int {
- val id = idCounter++
- return id
- }
-
override fun updateMarker(
featureId: Int,
markerDescription: MarkerDescription
@@ -60,7 +55,7 @@ class FakeClickableMapFragment : Fragment(), MapFragment {
override fun addMarkers(markers: List): List {
return markers.map {
- addMarker(it)
+ idCounter++
}
}
@@ -97,6 +92,7 @@ class FakeClickableMapFragment : Fragment(), MapFragment {
}
override fun clearFeatures() {}
+ override fun clearFeatures(ids: List) {}
override fun setClickListener(listener: MapFragment.PointListener?) {}
@@ -108,16 +104,6 @@ class FakeClickableMapFragment : Fragment(), MapFragment {
override fun setDragEndListener(listener: MapFragment.FeatureListener?) {}
- override fun setGpsLocationEnabled(enabled: Boolean) {}
-
- override fun getGpsLocation(): MapPoint? {
- return null
- }
-
- override fun setGpsLocationListener(listener: MapFragment.PointListener?) {}
-
- override fun setRetainMockAccuracy(retainMockAccuracy: Boolean) {}
-
override fun hasCenter(): Boolean {
return false
}
diff --git a/collect_app/src/main/java/org/odk/collect/android/injection/config/CollectGoogleMapsDependencyModule.kt b/collect_app/src/main/java/org/odk/collect/android/injection/config/CollectGoogleMapsDependencyModule.kt
index ff322a6bbb3..7a3d32cdd12 100644
--- a/collect_app/src/main/java/org/odk/collect/android/injection/config/CollectGoogleMapsDependencyModule.kt
+++ b/collect_app/src/main/java/org/odk/collect/android/injection/config/CollectGoogleMapsDependencyModule.kt
@@ -12,10 +12,6 @@ class CollectGoogleMapsDependencyModule(
return appDependencyComponent.referenceLayerRepository()
}
- override fun providesLocationClient(): LocationClient {
- return appDependencyComponent.locationClient()
- }
-
override fun providesSettingsProvider(): SettingsProvider {
return appDependencyComponent.settingsProvider()
}
diff --git a/collect_app/src/main/java/org/odk/collect/android/injection/config/CollectOsmDroidDependencyModule.kt b/collect_app/src/main/java/org/odk/collect/android/injection/config/CollectOsmDroidDependencyModule.kt
index 77630904a55..2887a64f1f5 100644
--- a/collect_app/src/main/java/org/odk/collect/android/injection/config/CollectOsmDroidDependencyModule.kt
+++ b/collect_app/src/main/java/org/odk/collect/android/injection/config/CollectOsmDroidDependencyModule.kt
@@ -1,7 +1,6 @@
package org.odk.collect.android.injection.config
import org.odk.collect.android.geo.MapConfiguratorProvider
-import org.odk.collect.location.LocationClient
import org.odk.collect.maps.MapConfigurator
import org.odk.collect.maps.layers.ReferenceLayerRepository
import org.odk.collect.osmdroid.OsmDroidDependencyModule
@@ -15,10 +14,6 @@ class CollectOsmDroidDependencyModule(
return appDependencyComponent.referenceLayerRepository()
}
- override fun providesLocationClient(): LocationClient {
- return appDependencyComponent.locationClient()
- }
-
override fun providesMapConfigurator(): MapConfigurator {
return MapConfiguratorProvider.getConfigurator(
appDependencyComponent.settingsProvider().getUnprotectedSettings().getString(ProjectKeys.KEY_BASEMAP_SOURCE)
diff --git a/collect_app/src/test/java/org/odk/collect/android/widgets/support/NoOpMapFragment.kt b/collect_app/src/test/java/org/odk/collect/android/widgets/support/NoOpMapFragment.kt
index 78d3d9c5bec..9c58b194c5f 100644
--- a/collect_app/src/test/java/org/odk/collect/android/widgets/support/NoOpMapFragment.kt
+++ b/collect_app/src/test/java/org/odk/collect/android/widgets/support/NoOpMapFragment.kt
@@ -44,10 +44,6 @@ class NoOpMapFragment : Fragment(), MapFragment {
) {
}
- override fun addMarker(markerDescription: MarkerDescription): Int {
- TODO("Not yet implemented")
- }
-
override fun updateMarker(
featureId: Int,
markerDescription: MarkerDescription
@@ -106,6 +102,9 @@ class NoOpMapFragment : Fragment(), MapFragment {
override fun clearFeatures() {
}
+ override fun clearFeatures(ids: List) {
+ }
+
override fun setClickListener(listener: MapFragment.PointListener?) {
}
@@ -118,19 +117,6 @@ class NoOpMapFragment : Fragment(), MapFragment {
override fun setDragEndListener(listener: MapFragment.FeatureListener?) {
}
- override fun setGpsLocationEnabled(enabled: Boolean) {
- }
-
- override fun getGpsLocation(): MapPoint? {
- TODO("Not yet implemented")
- }
-
- override fun setGpsLocationListener(listener: MapFragment.PointListener?) {
- }
-
- override fun setRetainMockAccuracy(retainMockAccuracy: Boolean) {
- }
-
override fun hasCenter(): Boolean {
return false
}
diff --git a/geo/src/main/java/org/odk/collect/geo/GeoUtils.kt b/geo/src/main/java/org/odk/collect/geo/GeoUtils.kt
index 4dca90e210d..e53801e7661 100644
--- a/geo/src/main/java/org/odk/collect/geo/GeoUtils.kt
+++ b/geo/src/main/java/org/odk/collect/geo/GeoUtils.kt
@@ -2,8 +2,14 @@ package org.odk.collect.geo
import android.content.Context
import android.location.Location
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.asLiveData
import org.javarosa.core.model.data.GeoPointData
+import org.odk.collect.location.tracker.LocationTracker
+import org.odk.collect.location.tracker.bindToLifecycle
+import org.odk.collect.maps.MapFragment
import org.odk.collect.maps.MapPoint
+import org.odk.collect.maps.circles.CurrentLocationDelegate
import org.odk.collect.shared.strings.StringUtils.removeEnd
import org.odk.collect.strings.R
import java.text.DecimalFormat
@@ -93,7 +99,27 @@ object GeoUtils {
return MapPoint(this.getPart(0), this.getPart(1), this.getPart(2), this.getPart(3))
}
+ @JvmStatic
fun org.odk.collect.location.Location.toMapPoint(): MapPoint {
return MapPoint(this.latitude, this.longitude, this.altitude, this.accuracy.toDouble())
}
+
+ @JvmStatic
+ @JvmOverloads
+ fun MapFragment.showCurrentLocation(
+ locationTracker: LocationTracker,
+ currentLocationDelegate: CurrentLocationDelegate,
+ retainMockAccuracy: Boolean = false,
+ afterUpdate: (MapPoint) -> Unit = {}
+ ) {
+ val lifecycleOwner = this as Fragment
+ locationTracker.bindToLifecycle(lifecycleOwner, retainMockAccuracy)
+ locationTracker.getLocation().asLiveData().observe(lifecycleOwner) {
+ if (it != null) {
+ val mapPoint = it.toMapPoint()
+ currentLocationDelegate.update(this, mapPoint)
+ afterUpdate(mapPoint)
+ }
+ }
+ }
}
diff --git a/geo/src/main/java/org/odk/collect/geo/geopoint/GeoPointMapActivity.java b/geo/src/main/java/org/odk/collect/geo/geopoint/GeoPointMapActivity.java
index 686a8b427d6..62c81b6614f 100644
--- a/geo/src/main/java/org/odk/collect/geo/geopoint/GeoPointMapActivity.java
+++ b/geo/src/main/java/org/odk/collect/geo/geopoint/GeoPointMapActivity.java
@@ -19,9 +19,14 @@
import static org.odk.collect.geo.Constants.EXTRA_READ_ONLY;
import static org.odk.collect.geo.Constants.EXTRA_RETAIN_MOCK_ACCURACY;
import static org.odk.collect.geo.GeoActivityUtils.requireLocationPermissions;
+import static org.odk.collect.geo.GeoUtils.showCurrentLocation;
+import static org.odk.collect.geo.GeoUtils.toMapPoint;
+import static org.odk.collect.location.tracker.LocationTrackerKt.getCurrentLocation;
+import static org.odk.collect.maps.MapFragmentKt.addMarker;
import android.annotation.SuppressLint;
import android.content.Intent;
+import android.graphics.Color;
import android.os.Bundle;
import android.view.View;
import android.view.Window;
@@ -38,9 +43,12 @@
import org.odk.collect.externalapp.ExternalAppUtils;
import org.odk.collect.geo.GeoDependencyComponentProvider;
import org.odk.collect.geo.R;
+import org.odk.collect.location.Location;
+import org.odk.collect.location.tracker.LocationTracker;
import org.odk.collect.maps.MapFragment;
import org.odk.collect.maps.MapFragmentFactory;
import org.odk.collect.maps.MapPoint;
+import org.odk.collect.maps.circles.CurrentLocationDelegate;
import org.odk.collect.maps.layers.OfflineMapLayersPickerBottomSheetDialogFragment;
import org.odk.collect.maps.layers.ReferenceLayerRepository;
import org.odk.collect.maps.markers.MarkerDescription;
@@ -49,8 +57,11 @@
import org.odk.collect.strings.localization.LocalizedActivity;
import org.odk.collect.webpage.WebPageService;
+import java.util.Arrays;
+
import javax.inject.Inject;
+import kotlin.Unit;
import timber.log.Timber;
/**
@@ -96,6 +107,9 @@ public class GeoPointMapActivity extends LocalizedActivity {
@Inject
WebPageService webPageService;
+ @Inject
+ LocationTracker locationTracker;
+
private MapFragment map;
private int featureId = -1; // will be a positive featureId once map is ready
@@ -110,7 +124,6 @@ public class GeoPointMapActivity extends LocalizedActivity {
private ImageButton clearButton;
private boolean captureLocation;
- private boolean foundFirstLocation;
/**
* True if a tap on the clear button removed an existing marker and
@@ -118,18 +131,28 @@ public class GeoPointMapActivity extends LocalizedActivity {
*/
private boolean setClear;
- /** True if the current point came from the intent. */
+ /**
+ * True if the current point came from the intent.
+ */
private boolean pointFromIntent;
- /** True if the intent requested for the point to be read-only. */
+ /**
+ * True if the intent requested for the point to be read-only.
+ */
private boolean intentReadOnly;
- /** True if the intent requested for the marker to be draggable. */
+ /**
+ * True if the intent requested for the marker to be draggable.
+ */
private boolean intentDraggable;
- /** While true, the point cannot be moved by dragging or long-pressing. */
+ /**
+ * While true, the point cannot be moved by dragging or long-pressing.
+ */
private boolean isPointLocked;
+ private final CurrentLocationDelegate currentLocationDelegate = new CurrentLocationDelegate();
+
@Override
public void onCreate(Bundle savedInstanceState) {
((GeoDependencyComponentProvider) getApplication()).getGeoDependencyComponent().inject(this);
@@ -162,7 +185,8 @@ public void onCreate(Bundle savedInstanceState) {
mapFragment.init(this::initMap, this::finish);
}
- @Override protected void onSaveInstanceState(Bundle state) {
+ @Override
+ protected void onSaveInstanceState(Bundle state) {
super.onSaveInstanceState(state);
if (map == null) {
// initMap() is called asynchronously, so map can be null if the activity
@@ -179,7 +203,6 @@ public void onCreate(Bundle savedInstanceState) {
// Flags
state.putBoolean(IS_DRAGGED_KEY, isDragged);
state.putBoolean(CAPTURE_LOCATION_KEY, captureLocation);
- state.putBoolean(FOUND_FIRST_LOCATION_KEY, foundFirstLocation);
state.putBoolean(SET_CLEAR_KEY, setClear);
state.putBoolean(POINT_FROM_INTENT_KEY, pointFromIntent);
state.putBoolean(INTENT_READ_ONLY_KEY, intentReadOnly);
@@ -222,8 +245,10 @@ public void initMap(MapFragment newMapFragment) {
placeMarkerButton.setEnabled(false);
placeMarkerButton.setOnClickListener(v -> {
- MapPoint mapPoint = map.getGpsLocation();
- if (mapPoint != null) {
+ Location currentLocation = getCurrentLocation(locationTracker);
+
+ if (currentLocation != null) {
+ MapPoint mapPoint = toMapPoint(currentLocation);
placeMarker(mapPoint);
zoomToMarker(true);
}
@@ -231,7 +256,7 @@ public void initMap(MapFragment newMapFragment) {
// Focuses on marked location
zoomButton.setEnabled(false);
- zoomButton.setOnClickListener(v -> map.zoomToCurrentLocation(map.getGpsLocation()));
+ zoomButton.setOnClickListener(v -> currentLocationDelegate.zoomToCurrentLocation(map));
// Menu Layer Toggle
findViewById(R.id.layer_menu).setOnClickListener(v -> {
@@ -242,11 +267,6 @@ public void initMap(MapFragment newMapFragment) {
clearButton.setEnabled(false);
clearButton.setOnClickListener(v -> {
clear();
- if (map.getGpsLocation() != null) {
- placeMarkerButton.setEnabled(true);
- // locationStatus.setVisibility(View.VISIBLE);
- }
- // placeMarkerButton.setEnabled(true);
locationStatus.setVisibility(View.VISIBLE);
pointFromIntent = false;
});
@@ -279,24 +299,24 @@ public void initMap(MapFragment newMapFragment) {
pointFromIntent = true;
locationStatus.setVisibility(View.GONE);
zoomButton.setEnabled(true);
- foundFirstLocation = true;
zoomToMarker(false);
}
}
- map.setRetainMockAccuracy(intent.getBooleanExtra(EXTRA_RETAIN_MOCK_ACCURACY, false));
- map.setGpsLocationListener(this::onLocationChanged);
- map.setGpsLocationEnabled(true);
-
if (previousState != null) {
restoreFromInstanceState(previousState);
}
+
+ boolean retainMockAccuracy = getIntent().getBooleanExtra(EXTRA_RETAIN_MOCK_ACCURACY, false);
+ showCurrentLocation(map, locationTracker, currentLocationDelegate, retainMockAccuracy, mapPoint -> {
+ onLocationChanged(mapPoint);
+ return Unit.INSTANCE;
+ });
}
protected void restoreFromInstanceState(Bundle state) {
isDragged = state.getBoolean(IS_DRAGGED_KEY, false);
captureLocation = state.getBoolean(CAPTURE_LOCATION_KEY, false);
- foundFirstLocation = state.getBoolean(FOUND_FIRST_LOCATION_KEY, false);
setClear = state.getBoolean(SET_CLEAR_KEY, false);
pointFromIntent = state.getBoolean(POINT_FROM_INTENT_KEY, false);
intentReadOnly = state.getBoolean(INTENT_READ_ONLY_KEY, false);
@@ -307,14 +327,11 @@ protected void restoreFromInstanceState(Bundle state) {
MapPoint point = state.getParcelable(POINT_KEY);
if (point != null) {
placeMarker(point);
- } else {
- clear();
}
// Restore the flags again, because placeMarker() and clear() modify some of them.
isDragged = state.getBoolean(IS_DRAGGED_KEY, false);
captureLocation = state.getBoolean(CAPTURE_LOCATION_KEY, false);
- foundFirstLocation = state.getBoolean(FOUND_FIRST_LOCATION_KEY, false);
setClear = state.getBoolean(SET_CLEAR_KEY, false);
pointFromIntent = state.getBoolean(POINT_FROM_INTENT_KEY, false);
intentReadOnly = state.getBoolean(INTENT_READ_ONLY_KEY, false);
@@ -333,8 +350,6 @@ public void onLocationChanged(MapPoint point) {
placeMarkerButton.setEnabled(true);
}
- this.location = point;
-
if (point != null) {
enableZoomButton(true);
@@ -343,11 +358,6 @@ public void onLocationChanged(MapPoint point) {
placeMarkerButton.setEnabled(true);
}
- if (!foundFirstLocation) {
- map.zoomToCurrentLocation(map.getGpsLocation());
- foundFirstLocation = true;
- }
-
locationStatus.setAccuracy(new LocationAccuracy.Improving((float) point.accuracy));
}
}
@@ -384,9 +394,10 @@ public void zoomToMarker(boolean animate) {
}
private void clear() {
- map.clearFeatures();
+ map.clearFeatures(Arrays.asList(featureId));
featureId = -1;
clearButton.setEnabled(false);
+ placeMarkerButton.setEnabled(true);
isPointLocked = false;
isDragged = false;
@@ -394,10 +405,18 @@ private void clear() {
setClear = true;
}
- /** Places the marker and enables the button to remove it. */
+ /**
+ * Places the marker and enables the button to remove it.
+ */
private void placeMarker(@NonNull MapPoint point) {
- map.clearFeatures();
- featureId = map.addMarker(new MarkerDescription(point, intentDraggable && !intentReadOnly && !isPointLocked, MapFragment.IconAnchor.CENTER, new MarkerIconDescription.DrawableResource(org.odk.collect.icons.R.drawable.ic_map_point)));
+ this.location = point;
+
+ if (featureId != -1) {
+ map.clearFeatures(Arrays.asList(featureId));
+ }
+
+ MarkerIconDescription.DrawableResource iconDescription = new MarkerIconDescription.DrawableResource(org.odk.collect.icons.R.drawable.ic_map_marker_with_hole_big, Color.parseColor("#52C268"));
+ featureId = addMarker(map, new MarkerDescription(point, intentDraggable && !intentReadOnly && !isPointLocked, MapFragment.IconAnchor.BOTTOM, iconDescription));
if (!intentReadOnly) {
clearButton.setEnabled(true);
}
diff --git a/geo/src/main/java/org/odk/collect/geo/selection/SelectionMapFragment.kt b/geo/src/main/java/org/odk/collect/geo/selection/SelectionMapFragment.kt
index eb0566f359a..872be3669fc 100644
--- a/geo/src/main/java/org/odk/collect/geo/selection/SelectionMapFragment.kt
+++ b/geo/src/main/java/org/odk/collect/geo/selection/SelectionMapFragment.kt
@@ -25,16 +25,19 @@ import org.odk.collect.androidshared.ui.multiclicksafe.setMultiClickSafeOnClickL
import org.odk.collect.androidshared.utils.sanitizeToColorInt
import org.odk.collect.async.Scheduler
import org.odk.collect.geo.GeoDependencyComponentProvider
+import org.odk.collect.geo.GeoUtils.showCurrentLocation
import org.odk.collect.geo.databinding.SelectionMapLayoutBinding
-import org.odk.collect.maps.traces.LineDescription
+import org.odk.collect.location.tracker.LocationTracker
import org.odk.collect.maps.MapFragment
import org.odk.collect.maps.MapFragmentFactory
import org.odk.collect.maps.MapPoint
-import org.odk.collect.maps.traces.PolygonDescription
+import org.odk.collect.maps.circles.CurrentLocationDelegate
import org.odk.collect.maps.layers.OfflineMapLayersPickerBottomSheetDialogFragment
import org.odk.collect.maps.layers.ReferenceLayerRepository
import org.odk.collect.maps.markers.MarkerDescription
import org.odk.collect.maps.markers.MarkerIconDescription
+import org.odk.collect.maps.traces.LineDescription
+import org.odk.collect.maps.traces.PolygonDescription
import org.odk.collect.material.BottomSheetBehavior
import org.odk.collect.material.MaterialProgressDialogFragment
import org.odk.collect.permissions.PermissionsChecker
@@ -72,6 +75,9 @@ class SelectionMapFragment(
@Inject
lateinit var webPageService: WebPageService
+ @Inject
+ lateinit var locationTracker: LocationTracker
+
private val selectedItemViewModel by viewModels()
private lateinit var map: MapFragment
@@ -91,6 +97,7 @@ class SelectionMapFragment(
private var featureCount: Int = 0
private var previousState: Bundle? = null
+ private val currentLocationDelegate = CurrentLocationDelegate()
override fun onCreate(savedInstanceState: Bundle?) {
childFragmentManager.fragmentFactory = FragmentFactoryBuilder()
@@ -190,7 +197,7 @@ class SelectionMapFragment(
map = newMapFragment
binding.zoomToLocation.setMultiClickSafeOnClickListener {
- map.zoomToCurrentLocation(map.getGpsLocation())
+ currentLocationDelegate.zoomToCurrentLocation(map)
}
binding.zoomToBounds.setMultiClickSafeOnClickListener {
@@ -217,8 +224,6 @@ class SelectionMapFragment(
binding.newItem.visibility = View.GONE
}
- map.setGpsLocationEnabled(true)
-
map.setFeatureClickListener(::onFeatureSelected)
map.setClickListener { onClick() }
@@ -228,6 +233,8 @@ class SelectionMapFragment(
updateCounts(binding)
}
}
+
+ map.showCurrentLocation(locationTracker, currentLocationDelegate)
}
private fun updateCounts(binding: SelectionMapLayoutBinding) {
@@ -367,11 +374,6 @@ class SelectionMapFragment(
} else if (!map.hasCenter()) {
if (zoomToFitItems && points.isNotEmpty()) {
map.zoomToBoundingBox(points, 0.8, false)
- } else {
- map.setGpsLocationListener { point ->
- map.zoomToCurrentLocation(point)
- map.setGpsLocationListener(null)
- }
}
}
}
@@ -391,7 +393,7 @@ class SelectionMapFragment(
*/
private fun updateFeatures(items: List) {
points.clear()
- map.clearFeatures()
+ map.clearFeatures(featureIdsByItemId.values.toList())
itemsByFeatureId.clear()
val singlePoints = items.filterIsInstance()
diff --git a/geo/src/test/java/org/odk/collect/geo/geopoint/GeoPointMapActivityTest.java b/geo/src/test/java/org/odk/collect/geo/geopoint/GeoPointMapActivityTest.java
deleted file mode 100644
index 39fe1763819..00000000000
--- a/geo/src/test/java/org/odk/collect/geo/geopoint/GeoPointMapActivityTest.java
+++ /dev/null
@@ -1,191 +0,0 @@
-package org.odk.collect.geo.geopoint;
-
-import static android.app.Activity.RESULT_OK;
-import static androidx.test.espresso.Espresso.onView;
-import static androidx.test.espresso.action.ViewActions.click;
-import static androidx.test.espresso.matcher.ViewMatchers.withId;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.equalTo;
-import static org.hamcrest.Matchers.is;
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Mockito.mock;
-import static org.odk.collect.geo.Constants.EXTRA_RETAIN_MOCK_ACCURACY;
-import static org.robolectric.Shadows.shadowOf;
-
-import android.app.Activity;
-import android.app.Application;
-import android.content.Intent;
-import android.widget.TextView;
-
-import androidx.annotation.NonNull;
-import androidx.test.core.app.ActivityScenario;
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.odk.collect.androidtest.ActivityScenarioLauncherRule;
-import org.odk.collect.async.Scheduler;
-import org.odk.collect.externalapp.ExternalAppUtils;
-import org.odk.collect.geo.DaggerGeoDependencyComponent;
-import org.odk.collect.geo.GeoDependencyModule;
-import org.odk.collect.geo.R;
-import org.odk.collect.geo.support.FakeMapFragment;
-import org.odk.collect.geo.support.RobolectricApplication;
-import org.odk.collect.maps.MapFragmentFactory;
-import org.odk.collect.maps.MapPoint;
-import org.odk.collect.maps.layers.ReferenceLayerRepository;
-import org.odk.collect.settings.InMemSettingsProvider;
-import org.odk.collect.settings.SettingsProvider;
-import org.odk.collect.webpage.WebPageService;
-import org.robolectric.shadows.ShadowApplication;
-
-import java.util.List;
-
-@RunWith(AndroidJUnit4.class)
-public class GeoPointMapActivityTest {
-
- private final FakeMapFragment mapFragment = new FakeMapFragment();
-
- @Rule
- public ActivityScenarioLauncherRule launcherRule = new ActivityScenarioLauncherRule();
-
- @Before
- public void setUp() {
- ShadowApplication shadowApplication = shadowOf(ApplicationProvider.getApplicationContext());
- shadowApplication.grantPermissions("android.permission.ACCESS_FINE_LOCATION");
- shadowApplication.grantPermissions("android.permission.ACCESS_COARSE_LOCATION");
-
- RobolectricApplication application = ApplicationProvider.getApplicationContext();
- application.geoDependencyComponent = DaggerGeoDependencyComponent.builder()
- .application(application)
- .geoDependencyModule(new GeoDependencyModule() {
- @NonNull
- @Override
- public MapFragmentFactory providesMapFragmentFactory() {
- return () -> mapFragment;
- }
-
- @NonNull
- @Override
- public ReferenceLayerRepository providesReferenceLayerRepository() {
- return mock();
- }
-
- @NonNull
- @Override
- public Scheduler providesScheduler() {
- return mock();
- }
-
- @NonNull
- @Override
- public SettingsProvider providesSettingsProvider() {
- return new InMemSettingsProvider();
- }
-
- @NonNull
- @Override
- public WebPageService providesWebPageService() {
- return mock();
- }
- })
- .build();
- }
-
- @Test
- public void whenLocationNotSetShouldDisplayPleaseWaitMessage() {
- ActivityScenario scenario = launcherRule.launchForResult(GeoPointMapActivity.class);
- mapFragment.ready();
-
- scenario.onActivity(activity -> assertEquals(activity.getString(org.odk.collect.strings.R.string.please_wait_long), getLocationStatus(activity)));
- }
-
- @Test
- public void whenLocationSetShouldDisplayStatusMessage() {
- ActivityScenario scenario = launcherRule.launchForResult(GeoPointMapActivity.class);
- mapFragment.ready();
- mapFragment.setLocationProvider("GPS");
- mapFragment.setLocation(new MapPoint(1, 2, 3, 4f));
-
- scenario.onActivity(activity -> assertEquals("Accuracy: 4 m", getLocationStatus(activity)));
- }
-
- @Test
- public void shouldReturnPointFromLastLocationFix() {
- ActivityScenario scenario = launcherRule.launchForResult(GeoPointMapActivity.class);
- mapFragment.ready();
- mapFragment.setLocationProvider("GPS");
-
- // First location
- mapFragment.setLocation(new MapPoint(1, 2, 3, 4f));
-
- // Second location
- mapFragment.setLocation(new MapPoint(5, 6, 7, 8f));
-
- // When the user clicks the "Save" button, the fix location should be returned.
- scenario.onActivity(activity -> activity.findViewById(R.id.accept_location).performClick());
-
- assertThat(scenario.getResult().getResultCode(), is(RESULT_OK));
- scenario.onActivity(activity -> {
- Intent resultData = scenario.getResult().getResultData();
- assertThat(ExternalAppUtils.getReturnedSingleValue(resultData), is(activity.formatResult(new MapPoint(5, 6, 7, 8))));
- });
- }
-
- @Test
- public void whenLocationExtraIncluded_showsMarker() {
- Intent intent = new Intent(ApplicationProvider.getApplicationContext(), GeoPointMapActivity.class);
- intent.putExtra(GeoPointMapActivity.EXTRA_LOCATION, new MapPoint(1.0, 2.0));
- launcherRule.launch(intent);
- mapFragment.ready();
-
- List markers = mapFragment.getMarkers();
- assertThat(markers.size(), equalTo(1));
- assertThat(markers.get(0).latitude, equalTo(1.0));
- assertThat(markers.get(0).longitude, equalTo(2.0));
- }
-
- @Test
- public void mapFragmentRetainMockAccuracy_isFalse() {
- launcherRule.launch(GeoPointMapActivity.class);
- mapFragment.ready();
-
- assertThat(mapFragment.isRetainMockAccuracy(), is(false));
- }
-
- @Test
- public void passingRetainMockAccuracyExtra_updatesMapFragmentState() {
- Intent intent = new Intent(ApplicationProvider.getApplicationContext(), GeoPointMapActivity.class);
- intent.putExtra(EXTRA_RETAIN_MOCK_ACCURACY, true);
- launcherRule.launch(intent);
- mapFragment.ready();
-
- assertThat(mapFragment.isRetainMockAccuracy(), is(true));
-
- intent.putExtra(EXTRA_RETAIN_MOCK_ACCURACY, false);
- launcherRule.launch(intent);
- mapFragment.ready();
-
- assertThat(mapFragment.isRetainMockAccuracy(), is(false));
- }
-
- @Test
- public void recreatingTheActivityWithTheLayersDialogDisplayedDoesNotCrashTheApp() {
- ActivityScenario scenario = launcherRule.launch(GeoPointMapActivity.class);
- mapFragment.ready();
-
- onView(withId(R.id.layer_menu)).perform(click());
-
- scenario.recreate();
- }
-
- private String getLocationStatus(Activity activity) {
- return activity
- .findViewById(R.id.status_section)
- .findViewById(R.id.location_status)
- .getText().toString();
- }
-}
diff --git a/geo/src/test/java/org/odk/collect/geo/geopoint/GeoPointMapActivityTest.kt b/geo/src/test/java/org/odk/collect/geo/geopoint/GeoPointMapActivityTest.kt
new file mode 100644
index 00000000000..955f2cc0bb7
--- /dev/null
+++ b/geo/src/test/java/org/odk/collect/geo/geopoint/GeoPointMapActivityTest.kt
@@ -0,0 +1,295 @@
+package org.odk.collect.geo.geopoint
+
+import android.app.Activity
+import android.app.Application
+import android.content.Intent
+import android.view.View
+import android.widget.TextView
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.espresso.Espresso
+import androidx.test.espresso.action.ViewActions
+import androidx.test.espresso.matcher.ViewMatchers
+import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.hamcrest.CoreMatchers.equalTo
+import org.hamcrest.MatcherAssert.assertThat
+import org.hamcrest.Matchers.not
+import org.junit.Assert
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito
+import org.odk.collect.androidtest.ActivityScenarioLauncherRule
+import org.odk.collect.async.Scheduler
+import org.odk.collect.externalapp.ExternalAppUtils.getReturnedSingleValue
+import org.odk.collect.geo.Constants.EXTRA_RETAIN_MOCK_ACCURACY
+import org.odk.collect.geo.DaggerGeoDependencyComponent
+import org.odk.collect.geo.GeoDependencyModule
+import org.odk.collect.geo.GeoUtils
+import org.odk.collect.geo.GeoUtils.toMapPoint
+import org.odk.collect.geo.support.FakeLocationTracker
+import org.odk.collect.geo.support.FakeMapFragment
+import org.odk.collect.geo.support.MapFragmentAssertions.hasZoomedToCurrentLocation
+import org.odk.collect.geo.support.MapFragmentAssertions.showsCurrentLocation
+import org.odk.collect.geo.support.RobolectricApplication
+import org.odk.collect.location.Location
+import org.odk.collect.location.tracker.LocationTracker
+import org.odk.collect.maps.MapFragmentFactory
+import org.odk.collect.maps.MapPoint
+import org.odk.collect.maps.circles.CurrentLocationDelegate
+import org.odk.collect.maps.layers.ReferenceLayerRepository
+import org.odk.collect.settings.InMemSettingsProvider
+import org.odk.collect.settings.SettingsProvider
+import org.odk.collect.strings.R
+import org.odk.collect.strings.R.string
+import org.odk.collect.testshared.EspressoAssertions
+import org.odk.collect.testshared.EspressoInteractions
+import org.odk.collect.webpage.WebPageService
+import org.robolectric.Shadows
+
+@RunWith(AndroidJUnit4::class)
+class GeoPointMapActivityTest {
+
+ private val mapFragment = FakeMapFragment()
+
+ private val locationTracker = FakeLocationTracker()
+
+ @get:Rule
+ val launcherRule: ActivityScenarioLauncherRule = ActivityScenarioLauncherRule()
+
+ @Before
+ fun setUp() {
+ val shadowApplication =
+ Shadows.shadowOf(ApplicationProvider.getApplicationContext())
+ shadowApplication.grantPermissions("android.permission.ACCESS_FINE_LOCATION")
+ shadowApplication.grantPermissions("android.permission.ACCESS_COARSE_LOCATION")
+
+ val application = ApplicationProvider.getApplicationContext()
+ application.geoDependencyComponent = DaggerGeoDependencyComponent.builder()
+ .application(application)
+ .geoDependencyModule(object : GeoDependencyModule() {
+ override fun providesMapFragmentFactory(): MapFragmentFactory {
+ return MapFragmentFactory { mapFragment }
+ }
+
+ override fun providesReferenceLayerRepository(): ReferenceLayerRepository {
+ return Mockito.mock()
+ }
+
+ override fun providesScheduler(): Scheduler {
+ return Mockito.mock()
+ }
+
+ override fun providesSettingsProvider(): SettingsProvider {
+ return InMemSettingsProvider()
+ }
+
+ override fun providesWebPageService(): WebPageService {
+ return Mockito.mock()
+ }
+
+ override fun providesLocationTracker(application: Application): LocationTracker {
+ return locationTracker
+ }
+ })
+ .build()
+ }
+
+ @Test
+ fun whenLocationNotSetShouldDisplayPleaseWaitMessage() {
+ val scenario = launcherRule.launchForResult(GeoPointMapActivity::class.java)
+ mapFragment.ready()
+
+ scenario.onActivity { activity: GeoPointMapActivity? ->
+ Assert.assertEquals(
+ activity!!.getString(
+ R.string.please_wait_long
+ ), getLocationStatus(activity)
+ )
+ }
+ }
+
+ @Test
+ fun whenLocationSetShouldDisplayStatusMessage() {
+ val scenario = launcherRule.launchForResult(GeoPointMapActivity::class.java)
+ mapFragment.ready()
+ locationTracker.currentLocation = Location(1.0, 2.0, 3.0, 4.0f)
+
+ scenario.onActivity { activity: GeoPointMapActivity? ->
+ Assert.assertEquals(
+ "Accuracy: 4 m",
+ getLocationStatus(activity!!)
+ )
+ }
+ }
+
+ @Test
+ fun `returns point from first location fix`() {
+ val scenario = launcherRule.launchForResult(GeoPointMapActivity::class.java)
+ mapFragment.ready()
+
+ val firstLocation = Location(1.0, 2.0, 3.0, 4.0f)
+ locationTracker.currentLocation = firstLocation
+ locationTracker.currentLocation = Location(5.0, 6.0, 7.0, 8.0f)
+
+ EspressoInteractions.clickOn(withContentDescription(string.save))
+ assertThat(scenario.result.resultCode, equalTo(Activity.RESULT_OK))
+ val resultData = scenario.result.resultData
+ assertThat(
+ getReturnedSingleValue(resultData),
+ equalTo(GeoUtils.formatLocationResultString(firstLocation))
+ )
+ }
+
+ @Test
+ fun `shows marker at first location fix`() {
+ launcherRule.launchForResult(GeoPointMapActivity::class.java)
+ mapFragment.ready()
+
+ val firstLocation = Location(1.0, 2.0, 3.0, 4.0f)
+ locationTracker.currentLocation = firstLocation
+ locationTracker.currentLocation = Location(5.0, 6.0, 7.0, 8.0f)
+
+ val markers = mapFragment.getMarkers()
+ .filter { it.iconDescription != CurrentLocationDelegate.ICON_DESCRIPTION }
+ assertThat(markers.size, equalTo(1))
+ assertThat(markers[0].point, equalTo(firstLocation.toMapPoint()))
+ }
+
+ @Test
+ fun `clicking add marker moves marker to the current location`() {
+ launcherRule.launchForResult(GeoPointMapActivity::class.java)
+ mapFragment.ready()
+
+ locationTracker.currentLocation = Location(1.0, 2.0, 3.0, 4.0f)
+ val secondLocation = Location(5.0, 6.0, 7.0, 8.0f)
+ locationTracker.currentLocation = secondLocation
+
+ EspressoInteractions.clickOn(withContentDescription(string.record_geopoint))
+
+ val markers = mapFragment.getMarkers()
+ .filter { it.iconDescription != CurrentLocationDelegate.ICON_DESCRIPTION }
+ assertThat(markers.size, equalTo(1))
+ assertThat(markers[0].point, equalTo(secondLocation.toMapPoint()))
+ }
+
+ @Test
+ fun whenLocationExtraIncluded_showsMarker() {
+ val intent = Intent(
+ ApplicationProvider.getApplicationContext(),
+ GeoPointMapActivity::class.java
+ )
+ intent.putExtra(GeoPointMapActivity.EXTRA_LOCATION, MapPoint(1.0, 2.0))
+ launcherRule.launch(intent)
+ mapFragment.ready()
+
+ val markers = mapFragment.getMarkersPoints()
+ assertThat(markers.size, equalTo(1))
+ assertThat(markers[0].latitude, equalTo(1.0))
+ assertThat(
+ markers[0].longitude,
+ equalTo(2.0)
+ )
+ }
+
+ @Test
+ fun mapFragmentRetainMockAccuracy_isFalse() {
+ launcherRule.launch(GeoPointMapActivity::class.java)
+ mapFragment.ready()
+
+ assertThat(mapFragment.isRetainMockAccuracy(), equalTo(false))
+ }
+
+ @Test
+ fun passingRetainMockAccuracyExtra_updatesLocationTracker() {
+ val intent = Intent(
+ ApplicationProvider.getApplicationContext(),
+ GeoPointMapActivity::class.java
+ )
+ intent.putExtra(EXTRA_RETAIN_MOCK_ACCURACY, true)
+ launcherRule.launch(intent)
+ mapFragment.ready()
+
+ assertThat(locationTracker.retainMockAccuracy, equalTo(true))
+
+ intent.putExtra(EXTRA_RETAIN_MOCK_ACCURACY, false)
+ launcherRule.launch(intent)
+ mapFragment.ready()
+
+ assertThat(locationTracker.retainMockAccuracy, equalTo(false))
+ }
+
+ @Test
+ fun recreatingTheActivityWithTheLayersDialogDisplayedDoesNotCrashTheApp() {
+ val scenario = launcherRule.launch(GeoPointMapActivity::class.java)
+ mapFragment.ready()
+
+ Espresso.onView(ViewMatchers.withId(org.odk.collect.geo.R.id.layer_menu)).perform(
+ ViewActions.click()
+ )
+
+ scenario.recreate()
+ }
+
+ @Test
+ fun `clicking zoom zooms to the current location`() {
+ launcherRule.launch(GeoPointMapActivity::class.java)
+ mapFragment.ready()
+
+ locationTracker.currentLocation = Location(5.0, 5.0)
+ locationTracker.currentLocation = Location(6.0, 6.0)
+
+ EspressoInteractions.clickOn(withContentDescription(string.show_my_location))
+ assertThat(mapFragment, hasZoomedToCurrentLocation(MapPoint(6.0, 6.0)))
+ }
+
+ @Test
+ fun `shows current location`() {
+ launcherRule.launch(GeoPointMapActivity::class.java)
+ mapFragment.ready()
+
+ val firstLocation = Location(2.0, 2.0, accuracy = 5.2f)
+ locationTracker.currentLocation = firstLocation
+ assertThat(mapFragment, showsCurrentLocation(firstLocation.toMapPoint()))
+
+ val secondLocation = Location(3.0, 2.0, accuracy = 2.1f)
+ locationTracker.currentLocation = secondLocation
+ assertThat(mapFragment, showsCurrentLocation(secondLocation.toMapPoint()))
+ assertThat(mapFragment, not(showsCurrentLocation(firstLocation.toMapPoint())))
+ }
+
+ @Test
+ fun `clicking clear clears marker`() {
+ launcherRule.launch(GeoPointMapActivity::class.java)
+ mapFragment.ready()
+
+ val location = Location(2.0, 2.0, accuracy = 5.2f)
+ locationTracker.currentLocation = location
+
+ EspressoInteractions.clickOn(withContentDescription(string.clear))
+ assertThat(mapFragment.getMarkers().size, equalTo(1))
+ assertThat(mapFragment, showsCurrentLocation(location.toMapPoint()))
+ }
+
+ @Test
+ fun `clearing an existing location enables the place marker button`() {
+ val intent = Intent(
+ ApplicationProvider.getApplicationContext(),
+ GeoPointMapActivity::class.java
+ )
+ intent.putExtra(GeoPointMapActivity.EXTRA_LOCATION, MapPoint(1.0, 2.0))
+ launcherRule.launch(intent)
+ mapFragment.ready()
+
+ EspressoInteractions.clickOn(withContentDescription(string.clear))
+ EspressoAssertions.assertEnabled(withContentDescription(string.record_geopoint))
+ }
+
+ private fun getLocationStatus(activity: Activity): String {
+ return activity
+ .findViewById(org.odk.collect.geo.R.id.status_section)
+ .findViewById(org.odk.collect.geo.R.id.location_status)
+ .text.toString()
+ }
+}
diff --git a/geo/src/test/java/org/odk/collect/geo/geopoly/GeoPolyFragmentTest.kt b/geo/src/test/java/org/odk/collect/geo/geopoly/GeoPolyFragmentTest.kt
index af7586549f0..2e99b0290ee 100644
--- a/geo/src/test/java/org/odk/collect/geo/geopoly/GeoPolyFragmentTest.kt
+++ b/geo/src/test/java/org/odk/collect/geo/geopoly/GeoPolyFragmentTest.kt
@@ -40,6 +40,8 @@ import org.odk.collect.geo.geopoly.GeoPolyFragment.OutputMode
import org.odk.collect.geo.support.AccuracyStatusViewMatcher.Companion.hasAccuracy
import org.odk.collect.geo.support.FakeLocationTracker
import org.odk.collect.geo.support.FakeMapFragment
+import org.odk.collect.geo.support.MapFragmentAssertions.hasZoomedToCurrentLocation
+import org.odk.collect.geo.support.MapFragmentAssertions.showsCurrentLocation
import org.odk.collect.geo.support.RobolectricApplication
import org.odk.collect.location.Location
import org.odk.collect.location.tracker.LocationTracker
@@ -54,9 +56,9 @@ import org.odk.collect.strings.R.string
import org.odk.collect.testshared.EspressoAssertions
import org.odk.collect.testshared.EspressoAssertions.assertNotVisible
import org.odk.collect.testshared.EspressoAssertions.assertVisible
+import org.odk.collect.testshared.EspressoInteractions
import org.odk.collect.testshared.FakeScheduler
import org.odk.collect.testshared.FragmentResultRecorder
-import org.odk.collect.testshared.EspressoInteractions
import org.odk.collect.webpage.WebPageService
import org.robolectric.Shadows
@@ -95,8 +97,7 @@ class GeoPolyFragmentTest {
}
locationTracker.currentLocation = Location(2.0, 2.0)
- assertThat(mapFragment.getCenter(), equalTo(MapPoint(2.0, 2.0)))
- assertThat(mapFragment.getZoom(), not(equalTo(0.0)))
+ assertThat(mapFragment, hasZoomedToCurrentLocation(MapPoint(2.0, 2.0)))
}
@Test
@@ -105,27 +106,14 @@ class GeoPolyFragmentTest {
GeoPolyFragment({ OnBackPressedDispatcher() })
}
- locationTracker.currentLocation = Location(2.0, 2.0, accuracy = 5.2f)
- assertThat(
- mapFragment.getMarkers(),
- equalTo(listOf(locationTracker.currentLocation!!.toMapPoint()))
- )
- mapFragment.getCircles().let {
- assertThat(it.size, equalTo(1))
- assertThat(it[0].center, equalTo(locationTracker.currentLocation!!.toMapPoint()))
- assertThat(it[0].radius, equalTo(5.2f))
- }
+ val firstLocation = Location(2.0, 2.0, accuracy = 5.2f)
+ locationTracker.currentLocation = firstLocation
+ assertThat(mapFragment, showsCurrentLocation(firstLocation.toMapPoint()))
- locationTracker.currentLocation = Location(3.0, 2.0, accuracy = 2.1f)
- assertThat(
- mapFragment.getMarkers(),
- equalTo(listOf(locationTracker.currentLocation!!.toMapPoint()))
- )
- mapFragment.getCircles().let {
- assertThat(it.size, equalTo(1))
- assertThat(it[0].center, equalTo(locationTracker.currentLocation!!.toMapPoint()))
- assertThat(it[0].radius, equalTo(2.1f))
- }
+ val secondLocation = Location(3.0, 2.0, accuracy = 2.1f)
+ locationTracker.currentLocation = secondLocation
+ assertThat(mapFragment, showsCurrentLocation(secondLocation.toMapPoint()))
+ assertThat(mapFragment, not(showsCurrentLocation(firstLocation.toMapPoint())))
}
@Test
@@ -977,8 +965,10 @@ class GeoPolyFragmentTest {
}
locationTracker.currentLocation = Location(5.0, 5.0)
+ locationTracker.currentLocation = Location(6.0, 6.0)
+
EspressoInteractions.clickOn(withContentDescription(string.show_my_location))
- assertThat(mapFragment.getCenter(), equalTo(MapPoint(5.0, 5.0)))
+ assertThat(mapFragment, hasZoomedToCurrentLocation(MapPoint(6.0, 6.0)))
}
@Test
diff --git a/geo/src/test/java/org/odk/collect/geo/selection/SelectionMapFragmentTest.kt b/geo/src/test/java/org/odk/collect/geo/selection/SelectionMapFragmentTest.kt
index 82942d656ae..4697b181375 100644
--- a/geo/src/test/java/org/odk/collect/geo/selection/SelectionMapFragmentTest.kt
+++ b/geo/src/test/java/org/odk/collect/geo/selection/SelectionMapFragmentTest.kt
@@ -1,5 +1,6 @@
package org.odk.collect.geo.selection
+import android.app.Application
import android.content.Context
import android.graphics.Color
import android.os.Bundle
@@ -39,21 +40,31 @@ import org.odk.collect.async.Scheduler
import org.odk.collect.fragmentstest.FragmentScenarioLauncherRule
import org.odk.collect.geo.DaggerGeoDependencyComponent
import org.odk.collect.geo.GeoDependencyModule
+import org.odk.collect.geo.GeoUtils.toMapPoint
import org.odk.collect.geo.R
+import org.odk.collect.geo.support.FakeLocationTracker
import org.odk.collect.geo.support.FakeMapFragment
import org.odk.collect.geo.support.Fixtures
+import org.odk.collect.geo.support.MapFragmentAssertions.hasZoomedToCurrentLocation
+import org.odk.collect.geo.support.MapFragmentAssertions.showsCurrentLocation
import org.odk.collect.geo.support.RobolectricApplication
+import org.odk.collect.location.Location
+import org.odk.collect.location.tracker.LocationTracker
import org.odk.collect.maps.MapFragment
import org.odk.collect.maps.MapFragmentFactory
import org.odk.collect.maps.MapPoint
+import org.odk.collect.maps.addMarker
import org.odk.collect.maps.layers.OfflineMapLayersPickerBottomSheetDialogFragment
import org.odk.collect.maps.layers.ReferenceLayerRepository
+import org.odk.collect.maps.markers.MarkerDescription
import org.odk.collect.maps.markers.MarkerIconDescription
import org.odk.collect.material.BottomSheetBehavior
import org.odk.collect.material.MaterialProgressDialogFragment
import org.odk.collect.permissions.PermissionsChecker
import org.odk.collect.settings.InMemSettingsProvider
import org.odk.collect.settings.SettingsProvider
+import org.odk.collect.strings.R.string
+import org.odk.collect.testshared.EspressoInteractions
import org.odk.collect.testshared.RobolectricHelpers.getFragmentByClass
import org.odk.collect.webpage.WebPageService
@@ -72,6 +83,7 @@ class SelectionMapFragmentTest {
}
private val onBackPressedDispatcher = OnBackPressedDispatcher()
+ private val locationTracker = FakeLocationTracker()
@get:Rule
val launcherRule = FragmentScenarioLauncherRule(
@@ -120,6 +132,10 @@ class SelectionMapFragmentTest {
override fun providesWebPageService(): WebPageService {
return mock()
}
+
+ override fun providesLocationTracker(application: Application): LocationTracker {
+ return locationTracker
+ }
}).build()
BottomSheetBehavior.DRAGGING_ENABLED = false
@@ -155,10 +171,34 @@ class SelectionMapFragmentTest {
launcherRule.launchInContainer(SelectionMapFragment::class.java)
map.ready()
- assertThat(map.getMarkers(), equalTo(itemsLiveData.value?.map { (it as MappableSelectItem.MappableSelectPoint).toMapPoint() }))
+ assertThat(map.getMarkersPoints(), equalTo(itemsLiveData.value?.map { (it as MappableSelectItem.MappableSelectPoint).toMapPoint() }))
+
+ itemsLiveData.value = emptyList()
+ assertThat(map.getMarkersPoints(), equalTo(emptyList()))
+ }
+
+ @Test
+ fun `doesn't clear other markers when items update`() {
+ val items: List = listOf(
+ Fixtures.actionMappableSelectPoint().copy(id = 0, point = MapPoint(40.0, 0.0))
+ )
+ val itemsLiveData = MutableLiveData(items)
+ whenever(data.getMappableItems()).thenReturn(itemsLiveData)
+
+ launcherRule.launchInContainer(SelectionMapFragment::class.java)
+ map.ready()
+ map.addMarker(
+ MarkerDescription(
+ MapPoint(0.0, 0.0),
+ false,
+ iconDescription = MarkerIconDescription.DrawableResource(R.drawable.ic_info)
+ )
+ )
+
+ assertThat(map.getMarkersPoints().size, equalTo(2))
itemsLiveData.value = emptyList()
- assertThat(map.getMarkers(), equalTo(emptyList()))
+ assertThat(map.getMarkersPoints().size, equalTo(1))
}
@Test
@@ -324,7 +364,7 @@ class SelectionMapFragmentTest {
assertThat(map.hasCenter(), equalTo(false))
- map.setLocation(MapPoint(1.0, 2.0))
+ locationTracker.currentLocation = Location(1.0, 2.0)
assertThat(map.getCenter(), equalTo(MapPoint(1.0, 2.0)))
}
@@ -337,7 +377,7 @@ class SelectionMapFragmentTest {
assertThat(map.hasCenter(), equalTo(false))
- map.setLocation(MapPoint(1.0, 2.0))
+ locationTracker.currentLocation = Location(1.0, 2.0)
assertThat(map.getCenter(), equalTo(MapPoint(1.0, 2.0)))
assertThat(map.getZoom(), equalTo(MapFragment.POINT_ZOOM.toDouble()))
}
@@ -352,7 +392,7 @@ class SelectionMapFragmentTest {
assertThat(map.hasCenter(), equalTo(false))
- map.setLocation(MapPoint(1.0, 2.0))
+ locationTracker.currentLocation = Location(1.0, 2.0)
assertThat(map.getCenter(), equalTo(MapPoint(1.0, 2.0)))
assertThat(map.getZoom(), equalTo(10.0))
}
@@ -366,10 +406,10 @@ class SelectionMapFragmentTest {
assertThat(map.hasCenter(), equalTo(false))
- map.setLocation(MapPoint(1.0, 2.0))
+ locationTracker.currentLocation = Location(1.0, 2.0)
assertThat(map.getCenter(), equalTo(MapPoint(1.0, 2.0)))
- map.setLocation(MapPoint(3.0, 4.0))
+ locationTracker.currentLocation = Location(3.0, 4.0)
assertThat(map.getCenter(), equalTo(MapPoint(1.0, 2.0)))
}
@@ -383,7 +423,7 @@ class SelectionMapFragmentTest {
launcherRule.launchInContainer(SelectionMapFragment::class.java)
map.ready()
- map.setLocation(MapPoint(67.0, 48.0))
+ locationTracker.currentLocation = Location(67.0, 48.0)
itemsLiveData.value =
listOf(Fixtures.actionMappableSelectPoint().copy(id = 0, point = MapPoint(52.0, 0.0)))
@@ -397,7 +437,7 @@ class SelectionMapFragmentTest {
launcherRule.launchInContainer(SelectionMapFragment::class.java)
map.ready()
- map.setLocation(MapPoint(40.181389, 44.514444))
+ locationTracker.currentLocation = Location(40.181389, 44.514444)
onView(withId(R.id.zoom_to_location)).perform(click())
assertThat(map.getCenter(), equalTo(MapPoint(40.181389, 44.514444)))
@@ -742,14 +782,15 @@ class SelectionMapFragmentTest {
fun `does not move when location changes when centered on already selected item`() {
val items = listOf(
Fixtures.actionMappableSelectPoint().copy(id = 0, point = MapPoint(40.0, 0.0)),
- Fixtures.actionMappableSelectPoint().copy(id = 1, point = MapPoint(41.0, 0.0), selected = true)
+ Fixtures.actionMappableSelectPoint()
+ .copy(id = 1, point = MapPoint(41.0, 0.0), selected = true)
)
whenever(data.getMappableItems()).thenReturn(MutableLiveData(items))
launcherRule.launchInContainer(SelectionMapFragment::class.java)
map.ready()
- map.setLocation(MapPoint(1.0, 2.0))
+ locationTracker.currentLocation = Location(1.0, 2.0)
assertThat(map.getCenter(), equalTo(items[1].toMapPoint()))
}
@@ -917,6 +958,33 @@ class SelectionMapFragmentTest {
assertThat(actualResult, equalTo(null))
}
+ @Test
+ fun `clicking zoom zooms to the current location`() {
+ launcherRule.launchInContainer(SelectionMapFragment::class.java)
+ map.ready()
+
+ locationTracker.currentLocation = Location(5.0, 5.0)
+ locationTracker.currentLocation = Location(6.0, 6.0)
+
+ EspressoInteractions.clickOn(withContentDescription(string.show_my_location))
+ assertThat(map, hasZoomedToCurrentLocation(MapPoint(6.0, 6.0)))
+ }
+
+ @Test
+ fun `shows current location`() {
+ launcherRule.launchInContainer(SelectionMapFragment::class.java)
+ map.ready()
+
+ val firstLocation = Location(2.0, 2.0, accuracy = 5.2f)
+ locationTracker.currentLocation = firstLocation
+ assertThat(map, showsCurrentLocation(firstLocation.toMapPoint()))
+
+ val secondLocation = Location(3.0, 2.0, accuracy = 2.1f)
+ locationTracker.currentLocation = secondLocation
+ assertThat(map, showsCurrentLocation(secondLocation.toMapPoint()))
+ assertThat(map, not(showsCurrentLocation(firstLocation.toMapPoint())))
+ }
+
private fun MappableSelectItem.MappableSelectPoint.toMapPoint(): MapPoint {
return MapPoint(this.point.latitude, this.point.longitude)
}
diff --git a/geo/src/test/java/org/odk/collect/geo/support/FakeLocationTracker.kt b/geo/src/test/java/org/odk/collect/geo/support/FakeLocationTracker.kt
index 7d444d851c0..b2cbf0bf698 100644
--- a/geo/src/test/java/org/odk/collect/geo/support/FakeLocationTracker.kt
+++ b/geo/src/test/java/org/odk/collect/geo/support/FakeLocationTracker.kt
@@ -9,8 +9,10 @@ class FakeLocationTracker : LocationTracker {
var currentLocation: Location? = null
set(value) {
- _currentLocation.value = value
- field = value
+ if (isStarted) {
+ _currentLocation.value = value
+ field = value
+ }
}
var retainMockAccuracy: Boolean = false
diff --git a/geo/src/test/java/org/odk/collect/geo/support/FakeMapFragment.kt b/geo/src/test/java/org/odk/collect/geo/support/FakeMapFragment.kt
index 64653df53ed..a07f4aac725 100644
--- a/geo/src/test/java/org/odk/collect/geo/support/FakeMapFragment.kt
+++ b/geo/src/test/java/org/odk/collect/geo/support/FakeMapFragment.kt
@@ -14,9 +14,9 @@ import org.odk.collect.maps.traces.PolygonDescription
import kotlin.random.Random
class FakeMapFragment(private val ready: Boolean = false) : Fragment(), MapFragment {
+ private var gpsLocationEnabled: Boolean = false
private var clickListener: PointListener? = null
private var gpsLocationListener: PointListener? = null
- private var locationProvider: String? = null
private var retainMockAccuracy = false
private var center: MapPoint? = null
private var zoom = 0.0
@@ -25,8 +25,7 @@ class FakeMapFragment(private val ready: Boolean = false) : Fragment(), MapFragm
private var gpsLocation: MapPoint? = null
private var featureClickListener: FeatureListener? = null
private var dragListener: FeatureListener? = null
- private val markers = mutableMapOf()
- private val markerIcons = mutableMapOf()
+ private val markers = mutableMapOf()
private val polyLines = mutableMapOf()
private val polygons = mutableMapOf()
@@ -103,22 +102,11 @@ class FakeMapFragment(private val ready: Boolean = false) : Fragment(), MapFragm
}
}
- override fun addMarker(markerDescription: MarkerDescription): Int {
- val featureId = generateFeatureId()
-
- markers[featureId] = markerDescription.point
- markerIcons[featureId] = markerDescription.iconDescription
-
- featureIds.add(featureId)
- return featureId
- }
-
override fun updateMarker(
featureId: Int,
markerDescription: MarkerDescription
) {
- markers[featureId] = markerDescription.point
- markerIcons[featureId] = markerDescription.iconDescription
+ markers[featureId] = markerDescription
}
override fun addMarkers(markers: List): List {
@@ -127,12 +115,20 @@ class FakeMapFragment(private val ready: Boolean = false) : Fragment(), MapFragm
}
}
+ private fun addMarker(markerDescription: MarkerDescription): Int {
+ val featureId = generateFeatureId()
+
+ markers[featureId] = markerDescription
+ featureIds.add(featureId)
+ return featureId
+ }
+
override fun setMarkerIcon(featureId: Int, markerIconDescription: MarkerIconDescription) {
- markerIcons[featureId] = markerIconDescription
+ markers[featureId] = markers[featureId]!!.copy(iconDescription = markerIconDescription)
}
override fun getMarkerPoint(featureId: Int): MapPoint? {
- return markers[featureId]
+ return markers[featureId]?.point
}
override fun addPolyLine(lineDescription: LineDescription): Int {
@@ -187,11 +183,18 @@ class FakeMapFragment(private val ready: Boolean = false) : Fragment(), MapFragm
override fun clearFeatures() {
markers.clear()
- markerIcons.clear()
polyLines.clear()
polygons.clear()
}
+ override fun clearFeatures(ids: List) {
+ listOf(markers, polyLines, polygons).forEach {
+ ids.forEach { id ->
+ it.remove(id)
+ }
+ }
+ }
+
override fun setClickListener(listener: PointListener?) {
this.clickListener = listener
}
@@ -209,38 +212,10 @@ class FakeMapFragment(private val ready: Boolean = false) : Fragment(), MapFragm
dragListener = listener
}
- override fun setGpsLocationEnabled(enabled: Boolean) {}
- override fun getGpsLocation(): MapPoint? {
- return gpsLocation
- }
-
- override fun setGpsLocationListener(listener: PointListener?) {
- gpsLocationListener = listener
-
- gpsLocation?.let {
- listener?.onPoint(it)
- }
- }
-
- override fun setRetainMockAccuracy(retainMockAccuracy: Boolean) {
- this.retainMockAccuracy = retainMockAccuracy
- }
-
override fun hasCenter(): Boolean {
return hasCenter
}
- fun setLocation(mapPoint: MapPoint?) {
- gpsLocation = mapPoint
- if (gpsLocationListener != null) {
- gpsLocationListener!!.onPoint(mapPoint!!)
- }
- }
-
- fun setLocationProvider(locationProvider: String?) {
- this.locationProvider = locationProvider
- }
-
fun isRetainMockAccuracy(): Boolean {
return retainMockAccuracy
}
@@ -253,12 +228,16 @@ class FakeMapFragment(private val ready: Boolean = false) : Fragment(), MapFragm
featureClickListener!!.onFeature(featureId)
}
- fun getMarkers(): List {
+ fun getMarkers(): List {
return markers.values.toList()
}
+ fun getMarkersPoints(): List {
+ return markers.values.map { it.point }
+ }
+
fun getMarkerIcons(): List {
- return markerIcons.values.toList()
+ return markers.values.map { it.iconDescription }
}
fun getZoomBoundingBox(): Pair, Double>? {
@@ -276,7 +255,7 @@ class FakeMapFragment(private val ready: Boolean = false) : Fragment(), MapFragm
fun getFeatureId(points: List): Int {
return if (points.size == 1) {
markers.entries.find {
- it.value == points[0]
+ it.value.point == points[0]
}!!.key
} else {
polyLines.entries.find {
diff --git a/geo/src/test/java/org/odk/collect/geo/support/MapFragmentAssertions.kt b/geo/src/test/java/org/odk/collect/geo/support/MapFragmentAssertions.kt
new file mode 100644
index 00000000000..d2df752ac4e
--- /dev/null
+++ b/geo/src/test/java/org/odk/collect/geo/support/MapFragmentAssertions.kt
@@ -0,0 +1,54 @@
+package org.odk.collect.geo.support
+
+import org.hamcrest.Description
+import org.hamcrest.TypeSafeMatcher
+import org.odk.collect.maps.MapPoint
+import org.odk.collect.maps.circles.CurrentLocationDelegate
+
+object MapFragmentAssertions {
+ fun hasZoomedToCurrentLocation(location: MapPoint): TypeSafeMatcher {
+ return object : TypeSafeMatcher() {
+ override fun matchesSafely(mapFragment: FakeMapFragment): Boolean {
+ return mapFragment.getCenter() == location && mapFragment.getZoom() != 0.0
+ }
+
+ override fun describeTo(description: Description) {
+ description.appendText("is zoomed to $location")
+ }
+
+ override fun describeMismatchSafely(
+ mapFragment: FakeMapFragment,
+ mismatchDescription: Description
+ ) {
+ mismatchDescription.appendText("was ${mapFragment.getCenter()}")
+ }
+ }
+ }
+
+ fun showsCurrentLocation(location: MapPoint): TypeSafeMatcher {
+ return object : TypeSafeMatcher() {
+ override fun matchesSafely(mapFragment: FakeMapFragment): Boolean {
+ val hasLocationMarker = mapFragment.getMarkers().any {
+ it.point == location && it.iconDescription == CurrentLocationDelegate.ICON_DESCRIPTION
+ }
+
+ val hasAccuracyCircle = mapFragment.getCircles().any {
+ it.center == location && it.radius == location.accuracy.toFloat()
+ }
+
+ return hasLocationMarker && hasAccuracyCircle
+ }
+
+ override fun describeTo(description: Description) {
+ description.appendText("shows current location as $location")
+ }
+
+ override fun describeMismatchSafely(
+ mapFragment: FakeMapFragment,
+ mismatchDescription: Description
+ ) {
+ mismatchDescription.appendText("was ${mapFragment.getCenter()}")
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/google-maps/build.gradle.kts b/google-maps/build.gradle.kts
index d4a6ec7df11..6c8239005a7 100644
--- a/google-maps/build.gradle.kts
+++ b/google-maps/build.gradle.kts
@@ -46,7 +46,6 @@ dependencies {
implementation(project(":androidshared"))
implementation(project(":maps"))
implementation(project(":settings"))
- implementation(project(":location"))
implementation(project(":strings"))
implementation(project(":icons"))
diff --git a/google-maps/src/main/java/org/odk/collect/googlemaps/DaggerSetup.kt b/google-maps/src/main/java/org/odk/collect/googlemaps/DaggerSetup.kt
index 94cdd92dae8..6820b6de8bc 100644
--- a/google-maps/src/main/java/org/odk/collect/googlemaps/DaggerSetup.kt
+++ b/google-maps/src/main/java/org/odk/collect/googlemaps/DaggerSetup.kt
@@ -3,7 +3,6 @@ package org.odk.collect.googlemaps
import dagger.Component
import dagger.Module
import dagger.Provides
-import org.odk.collect.location.LocationClient
import org.odk.collect.maps.layers.ReferenceLayerRepository
import org.odk.collect.settings.SettingsProvider
import javax.inject.Singleton
@@ -26,11 +25,6 @@ open class GoogleMapsDependencyModule {
throw UnsupportedOperationException("This should be overridden by dependent application")
}
- @Provides
- open fun providesLocationClient(): LocationClient {
- throw UnsupportedOperationException("This should be overridden by dependent application")
- }
-
@Provides
open fun providesSettingsProvider(): SettingsProvider {
throw UnsupportedOperationException("This should be overridden by dependent application")
diff --git a/google-maps/src/main/java/org/odk/collect/googlemaps/GoogleMapFragment.java b/google-maps/src/main/java/org/odk/collect/googlemaps/GoogleMapFragment.java
index c56006f2c53..b7309ea54e1 100644
--- a/google-maps/src/main/java/org/odk/collect/googlemaps/GoogleMapFragment.java
+++ b/google-maps/src/main/java/org/odk/collect/googlemaps/GoogleMapFragment.java
@@ -31,15 +31,12 @@
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
-import com.google.android.gms.location.LocationListener;
import com.google.android.gms.maps.CameraUpdate;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.BitmapDescriptor;
import com.google.android.gms.maps.model.CameraPosition;
-import com.google.android.gms.maps.model.Circle;
-import com.google.android.gms.maps.model.CircleOptions;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.LatLngBounds;
import com.google.android.gms.maps.model.MapStyleOptions;
@@ -53,12 +50,10 @@
import com.google.android.gms.maps.model.TileOverlayOptions;
import org.jetbrains.annotations.NotNull;
-import org.odk.collect.androidshared.system.ContextExt;
import org.odk.collect.androidshared.ui.ToastUtils;
import org.odk.collect.googlemaps.GoogleMapConfigurator.GoogleMapTypeOption;
import org.odk.collect.googlemaps.circles.CircleFeature;
import org.odk.collect.googlemaps.scaleview.MapScaleView;
-import org.odk.collect.location.LocationClient;
import org.odk.collect.maps.MapConfigurator;
import org.odk.collect.maps.MapFragment;
import org.odk.collect.maps.MapPoint;
@@ -89,7 +84,6 @@
import timber.log.Timber;
public class GoogleMapFragment extends MapViewModelMapFragment implements
- LocationListener, LocationClient.LocationClientListener,
GoogleMap.OnMapClickListener, GoogleMap.OnMapLongClickListener,
GoogleMap.OnMarkerClickListener, GoogleMap.OnMarkerDragListener,
GoogleMap.OnPolylineClickListener, GoogleMap.OnPolygonClickListener {
@@ -100,9 +94,6 @@ public class GoogleMapFragment extends MapViewModelMapFragment implements
@Inject
ReferenceLayerRepository referenceLayerRepository;
- @Inject
- LocationClient locationClient;
-
@Inject
SettingsProvider settingsProvider;
@@ -110,18 +101,11 @@ public class GoogleMapFragment extends MapViewModelMapFragment implements
private MapScaleView scaleView;
private ReadyListener readyListener;
private ErrorListener errorListener;
- private Marker locationCrosshairs;
- private Circle accuracyCircle;
- private final List gpsLocationReadyListeners = new ArrayList<>();
private PointListener clickListener;
private PointListener longPressListener;
- private PointListener gpsLocationListener;
private FeatureListener featureClickListener;
private FeatureListener dragEndListener;
- private boolean clientWantsLocationUpdates;
- private MapPoint lastLocationFix;
-
private int nextFeatureId = 1;
private final Map features = new HashMap<>();
private int mapType;
@@ -250,16 +234,6 @@ public void onZoomToBox(@NonNull Zoom.Box zoom) {
component.inject(this);
}
- @Override public void onResume() {
- super.onResume();
- enableLocationUpdates(clientWantsLocationUpdates);
- }
-
- @Override public void onPause() {
- super.onPause();
- enableLocationUpdates(false);
- }
-
@Override public void onDestroy() {
BitmapDescriptorCache.clearCache();
super.onDestroy();
@@ -280,21 +254,16 @@ public void onZoomToBox(@NonNull Zoom.Box zoom) {
return map.getCameraPosition().zoom;
}
- @Override public int addMarker(MarkerDescription markerDescription) {
- int featureId = nextFeatureId++;
- return addMarker(featureId, markerDescription);
- }
-
- private int addMarker(int featureId, MarkerDescription markerDescription) {
+ private void addMarker(int featureId, MarkerDescription markerDescription) {
features.put(featureId, new MarkerFeature(getActivity(), markerDescription, map));
- return featureId;
}
@Override
public List addMarkers(List markers) {
List featureIds = new ArrayList<>();
for (MarkerDescription markerDescription : markers) {
- int featureId = addMarker(markerDescription);
+ int featureId = nextFeatureId++;
+ addMarker(featureId, markerDescription);
featureIds.add(featureId);
}
@@ -373,6 +342,13 @@ public void updatePolygon(int featureId, @NotNull PolygonDescription polygonDesc
nextFeatureId = 1;
}
+ @Override
+ public void clearFeatures(@NotNull List<@NotNull Integer> ids) {
+ for (Integer id : ids) {
+ features.remove(id).dispose();
+ }
+ }
+
@Override public void setClickListener(@Nullable PointListener listener) {
clickListener = listener;
}
@@ -389,42 +365,6 @@ public void updatePolygon(int featureId, @NotNull PolygonDescription polygonDesc
dragEndListener = listener;
}
- @Override public void setGpsLocationListener(@Nullable PointListener listener) {
- gpsLocationListener = listener;
- }
-
- @Override
- public void setRetainMockAccuracy(boolean retainMockAccuracy) {
- locationClient.setRetainMockAccuracy(retainMockAccuracy);
- }
-
- @Override public void setGpsLocationEnabled(boolean enable) {
- if (enable != clientWantsLocationUpdates) {
- clientWantsLocationUpdates = enable;
- enableLocationUpdates(clientWantsLocationUpdates);
- }
- }
-
- @Override public void onLocationChanged(Location location) {
- Timber.i("onLocationChanged: location = %s", location);
- lastLocationFix = fromLocation(location);
- for (ReadyListener listener : gpsLocationReadyListeners) {
- listener.onReady(this);
- }
- gpsLocationReadyListeners.clear();
- if (gpsLocationListener != null) {
- gpsLocationListener.onPoint(lastLocationFix);
- }
-
- if (getActivity() != null) {
- updateLocationIndicator(toLatLng(lastLocationFix), location.getAccuracy());
- }
- }
-
- @Override public @Nullable MapPoint getGpsLocation() {
- return lastLocationFix;
- }
-
@Override public void onMapClick(LatLng latLng) {
if (clickListener != null) {
clickListener.onPoint(fromLatLng(latLng));
@@ -438,11 +378,6 @@ public void setRetainMockAccuracy(boolean retainMockAccuracy) {
}
@Override public boolean onMarkerClick(Marker marker) {
- // Avoid calling listeners if location crosshair is clicked on.
- if (marker == locationCrosshairs) {
- return true;
- }
-
if (featureClickListener != null) { // FormMapActivity
featureClickListener.onFeature(findFeature(marker));
} else { // GeoWidget
@@ -486,18 +421,6 @@ public void setRetainMockAccuracy(boolean retainMockAccuracy) {
}
}
- @Override public void onClientStart() {
- lastLocationFix = fromLocation(locationClient.getLastLocation());
- Timber.i("Requesting location updates (to %s)", this);
- locationClient.requestLocationUpdates(this);
- }
-
- @Override public void onClientStartFailure() {
- }
-
- @Override public void onClientStop() {
- }
-
private static @NonNull MapPoint fromLatLng(@NonNull LatLng latLng) {
return new MapPoint(latLng.latitude, latLng.longitude);
}
@@ -581,44 +504,6 @@ private void moveOrAnimateCamera(CameraUpdate movement, boolean animate) {
}
}
- private void enableLocationUpdates(boolean enable) {
- if (enable) {
- Timber.i("Starting LocationClient %s (for MapFragment %s)", locationClient, this);
- locationClient.start(this);
- } else {
- Timber.i("Stopping LocationClient %s (for MapFragment %s)", locationClient, this);
- locationClient.stop();
- }
- }
-
- private void updateLocationIndicator(LatLng loc, double radius) {
- if (map == null) {
- return;
- }
- if (locationCrosshairs == null) {
- locationCrosshairs = map.addMarker(new MarkerOptions()
- .position(loc)
- .icon(getBitmapDescriptor(getContext(), new MarkerIconDescription.DrawableResource(org.odk.collect.maps.R.drawable.ic_crosshairs)))
- .anchor(0.5f, 0.5f) // center the crosshairs on the position
- );
- }
- if (accuracyCircle == null) {
- int stroke = ContextExt.getThemeAttributeValue(requireContext(), androidx.appcompat.R.attr.colorPrimary);
- int fill = getResources().getColor(org.odk.collect.androidshared.R.color.color_primary_low_emphasis);
- accuracyCircle = map.addCircle(new CircleOptions()
- .center(loc)
- .radius(radius)
- .strokeWidth(1)
- .strokeColor(stroke)
- .fillColor(fill)
- );
- }
-
- locationCrosshairs.setPosition(loc);
- accuracyCircle.setCenter(loc);
- accuracyCircle.setRadius(radius);
- }
-
/** Finds the feature to which the given marker belongs. */
private int findFeature(Marker marker) {
for (int featureId : features.keySet()) {
@@ -659,6 +544,14 @@ private static Marker createMarker(Context context, MarkerDescription markerDesc
if (map == null || context == null) { // during Robolectric tests, map will be null
return null;
}
+
+ int index;
+ if (markerDescription.getIconDescription().getBackground()) {
+ index = 1;
+ } else {
+ index = 2;
+ }
+
// A Marker's position is a LatLng with just latitude and longitude
// fields. We need to store the point's altitude and standard
// deviation values somewhere, so they go in the marker's snippet.
@@ -668,6 +561,7 @@ private static Marker createMarker(Context context, MarkerDescription markerDesc
.draggable(markerDescription.isDraggable())
.icon(getBitmapDescriptor(context, markerDescription.getIconDescription()))
.anchor(getIconAnchorValueX(markerDescription.getIconAnchor()), getIconAnchorValueY(markerDescription.getIconAnchor())) // center the icon on the position
+ .zIndex(index)
);
}
diff --git a/location/src/main/java/org/odk/collect/location/tracker/ForegroundServiceLocationTracker.kt b/location/src/main/java/org/odk/collect/location/tracker/ForegroundServiceLocationTracker.kt
index 364fad892e1..bd6da8e2cb0 100644
--- a/location/src/main/java/org/odk/collect/location/tracker/ForegroundServiceLocationTracker.kt
+++ b/location/src/main/java/org/odk/collect/location/tracker/ForegroundServiceLocationTracker.kt
@@ -79,9 +79,7 @@ class LocationTrackerService : Service(), LocationClient.LocationClientListener
if (intent?.hasExtra(EXTRA_UPDATE_INTERVAL) == true) {
val interval = intent.getLongExtra(EXTRA_UPDATE_INTERVAL, -1)
- locationClient.setUpdateInterval(
- interval
- )
+ locationClient.setUpdateInterval(interval)
}
locationClient.start(this)
@@ -90,7 +88,7 @@ class LocationTrackerService : Service(), LocationClient.LocationClientListener
override fun onDestroy() {
locationClient.stop()
- application.getState().clear(LOCATION_KEY)
+ application.getState().setFlow(LOCATION_KEY, null)
}
override fun onClientStart() {
diff --git a/location/src/main/java/org/odk/collect/location/tracker/LocationTracker.kt b/location/src/main/java/org/odk/collect/location/tracker/LocationTracker.kt
index 267dbd4877e..5e8fdcb61bb 100644
--- a/location/src/main/java/org/odk/collect/location/tracker/LocationTracker.kt
+++ b/location/src/main/java/org/odk/collect/location/tracker/LocationTracker.kt
@@ -1,5 +1,7 @@
package org.odk.collect.location.tracker
+import androidx.lifecycle.DefaultLifecycleObserver
+import androidx.lifecycle.LifecycleOwner
import kotlinx.coroutines.flow.StateFlow
import org.odk.collect.location.Location
@@ -31,3 +33,15 @@ interface LocationTracker {
fun LocationTracker.getCurrentLocation(): Location? {
return this.getLocation().value
}
+
+fun LocationTracker.bindToLifecycle(lifecycleOwner: LifecycleOwner, retainMockAccuracy: Boolean = false) {
+ lifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver {
+ override fun onResume(owner: LifecycleOwner) {
+ start(retainMockAccuracy)
+ }
+
+ override fun onPause(owner: LifecycleOwner) {
+ stop()
+ }
+ })
+}
diff --git a/location/src/test/java/org/odk/collect/location/tracker/LocationTrackerTest.kt b/location/src/test/java/org/odk/collect/location/tracker/LocationTrackerTest.kt
index 8f117845794..d1db2b669df 100644
--- a/location/src/test/java/org/odk/collect/location/tracker/LocationTrackerTest.kt
+++ b/location/src/test/java/org/odk/collect/location/tracker/LocationTrackerTest.kt
@@ -51,4 +51,25 @@ abstract class LocationTrackerTest {
runBackground()
assertThat(locationTracker.getCurrentLocation(), equalTo(null))
}
+
+ @Test
+ fun canBeRestarted() {
+ val location = locationTracker.getLocation()
+
+ locationTracker.start()
+ runBackground()
+
+ setDeviceLocation(Location(1.0, 1.0))
+ runBackground()
+
+ locationTracker.stop()
+ runBackground()
+
+ locationTracker.start()
+ runBackground()
+
+ setDeviceLocation(Location(2.0, 2.0))
+ runBackground()
+ assertThat(location.value, equalTo(Location(2.0, 2.0)))
+ }
}
diff --git a/mapbox/build.gradle.kts b/mapbox/build.gradle.kts
index 0a2e58b80bd..bf1f505702d 100644
--- a/mapbox/build.gradle.kts
+++ b/mapbox/build.gradle.kts
@@ -34,7 +34,6 @@ dependencies {
implementation(project(":androidshared"))
implementation(project(":icons"))
- implementation(project(":location"))
implementation(project(":maps"))
implementation(project(":settings"))
implementation(project(":shared"))
diff --git a/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapFragment.kt b/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapFragment.kt
index 62ba6a09fde..fea8c9a3939 100644
--- a/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapFragment.kt
+++ b/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapFragment.kt
@@ -1,18 +1,15 @@
package org.odk.collect.mapbox
import android.graphics.Color
-import android.location.Location
import android.os.Bundle
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
-import androidx.appcompat.content.res.AppCompatResources
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.viewmodel.viewModelFactory
import androidx.startup.AppInitializer
-import com.google.android.gms.location.LocationListener
import com.mapbox.android.gestures.MoveGestureDetector
import com.mapbox.android.gestures.StandardScaleGestureDetector
import com.mapbox.geojson.Point
@@ -32,7 +29,6 @@ import com.mapbox.maps.extension.style.sources.generated.RasterSource
import com.mapbox.maps.extension.style.sources.generated.VectorSource
import com.mapbox.maps.extension.style.sources.getSource
import com.mapbox.maps.loader.MapboxMapsInitializer
-import com.mapbox.maps.plugin.LocationPuck2D
import com.mapbox.maps.plugin.animation.MapAnimationOptions.Companion.mapAnimationOptions
import com.mapbox.maps.plugin.animation.flyTo
import com.mapbox.maps.plugin.annotation.annotations
@@ -52,13 +48,9 @@ import com.mapbox.maps.plugin.gestures.addOnMapClickListener
import com.mapbox.maps.plugin.gestures.addOnMapLongClickListener
import com.mapbox.maps.plugin.gestures.addOnMoveListener
import com.mapbox.maps.plugin.gestures.addOnScaleListener
-import com.mapbox.maps.plugin.locationcomponent.location
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.odk.collect.androidshared.utils.ScreenUtils
-import org.odk.collect.location.LocationClient
-import org.odk.collect.location.LocationClient.LocationClientListener
-import org.odk.collect.maps.traces.LineDescription
import org.odk.collect.maps.MapFragment
import org.odk.collect.maps.MapFragment.ErrorListener
import org.odk.collect.maps.MapFragment.FeatureListener
@@ -67,7 +59,6 @@ import org.odk.collect.maps.MapFragment.ReadyListener
import org.odk.collect.maps.MapPoint
import org.odk.collect.maps.MapViewModel
import org.odk.collect.maps.MapViewModelMapFragment
-import org.odk.collect.maps.traces.PolygonDescription
import org.odk.collect.maps.Zoom
import org.odk.collect.maps.ZoomObserver
import org.odk.collect.maps.circles.CircleDescription
@@ -77,6 +68,8 @@ import org.odk.collect.maps.layers.ReferenceLayerRepository
import org.odk.collect.maps.markers.MarkerDescription
import org.odk.collect.maps.markers.MarkerIconCreator
import org.odk.collect.maps.markers.MarkerIconDescription
+import org.odk.collect.maps.traces.LineDescription
+import org.odk.collect.maps.traces.PolygonDescription
import org.odk.collect.settings.SettingsProvider
import org.odk.collect.shared.injection.ObjectProviderHost
import timber.log.Timber
@@ -86,9 +79,7 @@ import java.io.IOException
class MapboxMapFragment :
MapViewModelMapFragment(),
OnMapClickListener,
- OnMapLongClickListener,
- LocationListener,
- LocationClientListener {
+ OnMapLongClickListener {
private lateinit var mapView: MapView
private lateinit var mapboxMap: MapboxMap
@@ -97,23 +88,16 @@ class MapboxMapFragment :
private lateinit var polylineAnnotationManager: PolylineAnnotationManager
private lateinit var polygonAnnotationManager: PolygonAnnotationManager
private var mapReadyListener: ReadyListener? = null
- private val gpsLocationReadyListeners = mutableListOf()
private var nextFeatureId = 1
private val features = mutableMapOf()
-
- private var gpsLocationListener: PointListener? = null
private var clickListener: PointListener? = null
private var longPressListener: PointListener? = null
private var featureClickListener: FeatureListener? = null
private var featureDragEndListener: FeatureListener? = null
-
- private var lastLocationProvider: String? = null
- private var lastLocationFix: MapPoint? = null
private var tileServer: TileHttpServer? = null
private var referenceLayerFile: File? = null
- private var clientWantsLocationUpdates = false
private var topStyleLayerId: String? = null
private val _mapViewModel by viewModels {
@@ -135,10 +119,6 @@ class MapboxMapFragment :
(requireActivity().applicationContext as ObjectProviderHost).getObjectProvider().provide(ReferenceLayerRepository::class.java)
}
- private val locationClient: LocationClient by lazy {
- (requireActivity().applicationContext as ObjectProviderHost).getObjectProvider().provide(LocationClient::class.java)
- }
-
override fun init(readyListener: ReadyListener?, errorListener: ErrorListener?) {
mapReadyListener = readyListener
@@ -209,7 +189,6 @@ class MapboxMapFragment :
.annotations
.createPointAnnotationManager()
- initLocationComponent()
moveOrAnimateCamera(MapFragment.INITIAL_CENTER, false, MapFragment.INITIAL_ZOOM.toDouble())
// If the screen is rotated before the map is ready, this fragment could already be detached,
@@ -257,16 +236,6 @@ class MapboxMapFragment :
return mapView
}
- override fun onResume() {
- super.onResume()
- enableLocationUpdates(clientWantsLocationUpdates)
- }
-
- override fun onPause() {
- super.onPause()
- enableLocationUpdates(false)
- }
-
override fun onDestroy() {
tileServer?.destroy()
MarkerIconCreator.clearCache()
@@ -298,10 +267,6 @@ class MapboxMapFragment :
return _mapViewModel
}
- override fun addMarker(markerDescription: MarkerDescription): Int {
- return addMarkers(listOf(markerDescription)).first()
- }
-
override fun updateMarker(
featureId: Int,
markerDescription: MarkerDescription
@@ -472,6 +437,10 @@ class MapboxMapFragment :
nextFeatureId = 1
}
+ override fun clearFeatures(ids: List) {
+ ids.forEach { features.remove(it)?.dispose() }
+ }
+
override fun setClickListener(listener: PointListener?) {
clickListener = listener
}
@@ -488,25 +457,6 @@ class MapboxMapFragment :
featureDragEndListener = listener
}
- override fun setGpsLocationEnabled(enabled: Boolean) {
- if (enabled != clientWantsLocationUpdates) {
- clientWantsLocationUpdates = enabled
- enableLocationUpdates(clientWantsLocationUpdates)
- }
- }
-
- override fun getGpsLocation(): MapPoint? {
- return lastLocationFix
- }
-
- override fun setGpsLocationListener(listener: PointListener?) {
- gpsLocationListener = listener
- }
-
- override fun setRetainMockAccuracy(retainMockAccuracy: Boolean) {
- locationClient.setRetainMockAccuracy(retainMockAccuracy)
- }
-
override fun onMapClick(point: Point): Boolean {
clickListener?.onPoint(MapPoint(point.latitude(), point.longitude()))
@@ -522,51 +472,6 @@ class MapboxMapFragment :
return true
}
- override fun onLocationChanged(location: Location) {
- lastLocationFix = MapPoint(
- location.latitude,
- location.longitude,
- location.altitude,
- location.accuracy.toDouble()
- )
- lastLocationProvider = location.provider
- Timber.i(
- "Received location update: %s (%s)",
- lastLocationFix,
- lastLocationProvider
- )
- for (listener in gpsLocationReadyListeners) {
- listener.onReady(this)
- }
- gpsLocationReadyListeners.clear()
- gpsLocationListener?.onPoint(lastLocationFix!!)
- }
-
- @SuppressWarnings("MissingPermission") // permission checks for location services are handled in widgets
- private fun enableLocationUpdates(enabled: Boolean) {
- if (enabled) {
- Timber.i("Starting LocationClient %s (for MapFragment %s)", locationClient, this)
- locationClient.start(this)
- } else {
- Timber.i("Stopping LocationClient %s (for MapFragment %s)", locationClient, this)
- locationClient.stop()
- }
-
- mapView.location.enabled = enabled
- }
-
- private fun initLocationComponent() {
- mapView.location.updateSettings {
- this.enabled = true
- this.locationPuck = LocationPuck2D(
- AppCompatResources.getDrawable(
- requireContext(),
- org.odk.collect.maps.R.drawable.ic_crosshairs
- )
- )
- }
- }
-
private fun moveOrAnimateCamera(point: MapPoint, animate: Boolean, zoom: Double? = getZoom()) {
mapboxMap.flyTo(
cameraOptions {
@@ -681,17 +586,6 @@ class MapboxMapFragment :
}
}
- override fun onClientStart() {
- Timber.i("Requesting location updates (to %s)", this)
- locationClient.requestLocationUpdates(this)
- }
-
- override fun onClientStartFailure() {
- }
-
- override fun onClientStop() {
- }
-
companion object {
const val KEY_STYLE_URL = "STYLE_URL"
}
diff --git a/maps/src/main/java/org/odk/collect/maps/MapFragment.kt b/maps/src/main/java/org/odk/collect/maps/MapFragment.kt
index b0111cc1cad..e1b710f6f06 100644
--- a/maps/src/main/java/org/odk/collect/maps/MapFragment.kt
+++ b/maps/src/main/java/org/odk/collect/maps/MapFragment.kt
@@ -1,9 +1,9 @@
package org.odk.collect.maps
import org.odk.collect.maps.circles.CircleDescription
-import org.odk.collect.maps.traces.LineDescription
import org.odk.collect.maps.markers.MarkerDescription
import org.odk.collect.maps.markers.MarkerIconDescription
+import org.odk.collect.maps.traces.LineDescription
import org.odk.collect.maps.traces.PolygonDescription
/**
@@ -79,10 +79,8 @@ interface MapFragment {
* the user will be able to drag the marker to change its location.
* Returns a positive integer, the featureId for the newly added shape.
*/
- fun addMarker(markerDescription: MarkerDescription): Int
- fun updateMarker(featureId: Int, markerDescription: MarkerDescription)
-
fun addMarkers(markers: List): List
+ fun updateMarker(featureId: Int, markerDescription: MarkerDescription)
/** Sets the icon for a marker. */
@Deprecated(message = "Use #updateMarker instead")
@@ -117,6 +115,7 @@ interface MapFragment {
/** Removes all map features from the map. */
fun clearFeatures()
+ fun clearFeatures(ids: List)
/** Sets or clears the callback for a click on the map. */
fun setClickListener(listener: PointListener?)
@@ -130,29 +129,6 @@ interface MapFragment {
/** Sets or clears the callback for when a drag is completed. */
fun setDragEndListener(listener: FeatureListener?)
- /**
- * Enables/disables GPS tracking. While enabled, the GPS location is shown
- * on the map, the first GPS fix will trigger any pending callbacks set by
- * runOnGpsLocationReady(), and every GPS fix will invoke the callback set
- * by setGpsLocationListener().
- */
- @Deprecated(message = "Location should be handled outside of MapFragment")
- fun setGpsLocationEnabled(enabled: Boolean)
-
- /** Gets the last GPS location fix, or null if there hasn't been one. */
- @Deprecated(message = "Location should be handled outside of MapFragment")
- fun getGpsLocation(): MapPoint?
-
- /**
- * Sets or clears the callback for GPS location updates. This callback
- * will only be invoked while GPS is enabled with setGpsLocationEnabled().
- */
- @Deprecated(message = "Location should be handled outside of MapFragment")
- fun setGpsLocationListener(listener: PointListener?)
-
- @Deprecated(message = "Location should be handled outside of MapFragment")
- fun setRetainMockAccuracy(retainMockAccuracy: Boolean)
-
/**
* @return true if the [MapFragment] center has already been set (by [MapFragment.zoomToPoint] for instance).
*/
@@ -184,3 +160,7 @@ interface MapFragment {
enum class IconAnchor { CENTER, BOTTOM }
}
+
+fun MapFragment.addMarker(marker: MarkerDescription): Int {
+ return addMarkers(listOf(marker))[0]
+}
diff --git a/maps/src/main/java/org/odk/collect/maps/MapFragmentFactory.kt b/maps/src/main/java/org/odk/collect/maps/MapFragmentFactory.kt
index ce0c551d953..33b15c7cdc5 100644
--- a/maps/src/main/java/org/odk/collect/maps/MapFragmentFactory.kt
+++ b/maps/src/main/java/org/odk/collect/maps/MapFragmentFactory.kt
@@ -1,5 +1,5 @@
package org.odk.collect.maps
-interface MapFragmentFactory {
+fun interface MapFragmentFactory {
fun createMapFragment(): MapFragment
}
diff --git a/maps/src/main/java/org/odk/collect/maps/circles/CurrentLocationDelegate.kt b/maps/src/main/java/org/odk/collect/maps/circles/CurrentLocationDelegate.kt
index 89a51baba9e..d90a8b0aa46 100644
--- a/maps/src/main/java/org/odk/collect/maps/circles/CurrentLocationDelegate.kt
+++ b/maps/src/main/java/org/odk/collect/maps/circles/CurrentLocationDelegate.kt
@@ -1,8 +1,9 @@
package org.odk.collect.maps.circles
-import org.odk.collect.maps.MapConsts
+import androidx.core.graphics.toColorInt
import org.odk.collect.maps.MapFragment
import org.odk.collect.maps.MapPoint
+import org.odk.collect.maps.addMarker
import org.odk.collect.maps.markers.MarkerDescription
import org.odk.collect.maps.markers.MarkerIconDescription
@@ -16,14 +17,14 @@ class CurrentLocationDelegate {
private var locationMarkerId: Int? = null
private var accuracyHaloId: Int? = null
- fun update(map: MapFragment, location: MapPoint, follow: Boolean) {
+ fun update(map: MapFragment, location: MapPoint, follow: Boolean = false) {
currentLocation = location
val markerDescription = MarkerDescription(
location,
false,
MapFragment.IconAnchor.CENTER,
- MarkerIconDescription.DrawableResource(org.odk.collect.maps.R.drawable.ic_crosshairs)
+ ICON_DESCRIPTION
)
locationMarkerId.let {
@@ -56,4 +57,12 @@ class CurrentLocationDelegate {
fun zoomToCurrentLocation(map: MapFragment) {
map.zoomToCurrentLocation(currentLocation)
}
+
+ companion object {
+ val ICON_DESCRIPTION = MarkerIconDescription.DrawableResource(
+ drawable = org.odk.collect.maps.R.drawable.ic_current_location,
+ color = "#6393F2".toColorInt(),
+ background = true
+ )
+ }
}
\ No newline at end of file
diff --git a/maps/src/main/java/org/odk/collect/maps/markers/MarkerDescription.kt b/maps/src/main/java/org/odk/collect/maps/markers/MarkerDescription.kt
index 77d2b3d63a8..fc75ce4d721 100644
--- a/maps/src/main/java/org/odk/collect/maps/markers/MarkerDescription.kt
+++ b/maps/src/main/java/org/odk/collect/maps/markers/MarkerDescription.kt
@@ -6,6 +6,6 @@ import org.odk.collect.maps.MapPoint
data class MarkerDescription(
val point: MapPoint,
val isDraggable: Boolean,
- val iconAnchor: MapFragment.IconAnchor,
+ val iconAnchor: MapFragment.IconAnchor = MapFragment.IconAnchor.CENTER,
val iconDescription: MarkerIconDescription
)
diff --git a/maps/src/main/java/org/odk/collect/maps/markers/MarkerIconCreator.kt b/maps/src/main/java/org/odk/collect/maps/markers/MarkerIconCreator.kt
index aec3bd88f97..64f15169ea7 100644
--- a/maps/src/main/java/org/odk/collect/maps/markers/MarkerIconCreator.kt
+++ b/maps/src/main/java/org/odk/collect/maps/markers/MarkerIconCreator.kt
@@ -7,6 +7,7 @@ import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Typeface
+import android.graphics.drawable.GradientDrawable
import android.util.LruCache
import androidx.core.content.ContextCompat
import androidx.core.graphics.ColorUtils
@@ -93,7 +94,16 @@ object MarkerIconCreator {
drawable.mutate()
val isBackgroundDark = color?.let {
- drawable.setTint(it)
+ /**
+ * Tint icon for normal Drawables, but only change solid color for
+ * GradientDrawables (shapes).
+ */
+ if (drawable is GradientDrawable) {
+ drawable.setColor(it)
+ } else {
+ drawable.setTint(it)
+ }
+
ColorUtils.calculateLuminance(color) < 0.5
} ?: true
diff --git a/maps/src/main/java/org/odk/collect/maps/markers/MarkerIconDescription.kt b/maps/src/main/java/org/odk/collect/maps/markers/MarkerIconDescription.kt
index c55a09696c2..099a198a26b 100644
--- a/maps/src/main/java/org/odk/collect/maps/markers/MarkerIconDescription.kt
+++ b/maps/src/main/java/org/odk/collect/maps/markers/MarkerIconDescription.kt
@@ -5,12 +5,19 @@ import org.odk.collect.shared.strings.StringUtils
import java.util.Locale
sealed interface MarkerIconDescription {
+
+ val background: Boolean
+
data class DrawableResource @JvmOverloads constructor(
val drawable: Int,
- private val color: String? = null,
- private val symbol: String? = null
+ private val color: Int? = null,
+ private val symbol: String? = null,
+ override val background: Boolean = false
) : MarkerIconDescription {
- fun getColor(): Int? = color?.sanitizeToColorInt()
+
+ constructor(drawable: Int, color: String?, symbol: String?) : this(drawable, color?.sanitizeToColorInt(), symbol)
+
+ fun getColor(): Int? = color
fun getSymbol(): String? = symbol?.let {
if (it.isBlank()) {
@@ -21,5 +28,7 @@ sealed interface MarkerIconDescription {
}
}
- data class TracePoint(val lineSize: Float, val color: Int) : MarkerIconDescription
+ data class TracePoint(val lineSize: Float, val color: Int) : MarkerIconDescription {
+ override val background = true
+ }
}
diff --git a/maps/src/main/res/drawable/ic_crosshairs.xml b/maps/src/main/res/drawable/ic_crosshairs.xml
deleted file mode 100644
index da69a92a862..00000000000
--- a/maps/src/main/res/drawable/ic_crosshairs.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
diff --git a/maps/src/main/res/drawable/ic_current_location.xml b/maps/src/main/res/drawable/ic_current_location.xml
new file mode 100644
index 00000000000..be0deea42eb
--- /dev/null
+++ b/maps/src/main/res/drawable/ic_current_location.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/osmdroid/build.gradle.kts b/osmdroid/build.gradle.kts
index 43f8cd6015b..da1c296553c 100644
--- a/osmdroid/build.gradle.kts
+++ b/osmdroid/build.gradle.kts
@@ -36,7 +36,6 @@ dependencies {
implementation(project(":androidshared"))
implementation(project(":icons"))
implementation(project(":maps"))
- implementation(project(":location"))
implementation(project(":settings"))
implementation(project(":strings"))
diff --git a/osmdroid/src/main/java/org/odk/collect/osmdroid/DaggerSetup.kt b/osmdroid/src/main/java/org/odk/collect/osmdroid/DaggerSetup.kt
index a1b61e44c42..93bd4000511 100644
--- a/osmdroid/src/main/java/org/odk/collect/osmdroid/DaggerSetup.kt
+++ b/osmdroid/src/main/java/org/odk/collect/osmdroid/DaggerSetup.kt
@@ -3,7 +3,6 @@ package org.odk.collect.osmdroid
import dagger.Component
import dagger.Module
import dagger.Provides
-import org.odk.collect.location.LocationClient
import org.odk.collect.maps.MapConfigurator
import org.odk.collect.maps.layers.ReferenceLayerRepository
import org.odk.collect.settings.SettingsProvider
@@ -27,11 +26,6 @@ open class OsmDroidDependencyModule {
throw UnsupportedOperationException("This should be overridden by dependent application")
}
- @Provides
- open fun providesLocationClient(): LocationClient {
- throw UnsupportedOperationException("This should be overridden by dependent application")
- }
-
@Provides
open fun providesMapConfigurator(): MapConfigurator {
throw UnsupportedOperationException("This should be overridden by dependent application")
diff --git a/osmdroid/src/main/java/org/odk/collect/osmdroid/OsmDroidMapFragment.java b/osmdroid/src/main/java/org/odk/collect/osmdroid/OsmDroidMapFragment.java
index ac3347b1300..685d929ec24 100644
--- a/osmdroid/src/main/java/org/odk/collect/osmdroid/OsmDroidMapFragment.java
+++ b/osmdroid/src/main/java/org/odk/collect/osmdroid/OsmDroidMapFragment.java
@@ -15,9 +15,8 @@
package org.odk.collect.osmdroid;
import static androidx.core.graphics.drawable.BitmapDrawableKt.toDrawable;
-import static androidx.core.graphics.drawable.DrawableKt.toBitmap;
-import static org.odk.collect.maps.traces.TraceDescriptionKt.getMarkersForPoints;
import static org.odk.collect.maps.markers.MarkerIconCreator.toBitmap;
+import static org.odk.collect.maps.traces.TraceDescriptionKt.getMarkersForPoints;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -28,7 +27,6 @@
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
-import android.location.Location;
import android.os.Bundle;
import android.os.Handler;
import android.view.LayoutInflater;
@@ -37,30 +35,26 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.core.content.ContextCompat;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
-import com.google.android.gms.location.LocationListener;
-
import org.jetbrains.annotations.NotNull;
import org.odk.collect.androidshared.system.ContextExt;
-import org.odk.collect.location.LocationClient;
-import org.odk.collect.maps.circles.CircleDescription;
-import org.odk.collect.maps.traces.LineDescription;
import org.odk.collect.maps.MapConfigurator;
import org.odk.collect.maps.MapFragment;
import org.odk.collect.maps.MapPoint;
import org.odk.collect.maps.MapViewModel;
import org.odk.collect.maps.MapViewModelMapFragment;
-import org.odk.collect.maps.traces.PolygonDescription;
import org.odk.collect.maps.Zoom;
import org.odk.collect.maps.ZoomObserver;
+import org.odk.collect.maps.circles.CircleDescription;
import org.odk.collect.maps.layers.MapFragmentReferenceLayerUtils;
import org.odk.collect.maps.layers.ReferenceLayerRepository;
import org.odk.collect.maps.markers.MarkerDescription;
import org.odk.collect.maps.markers.MarkerIconCreator;
import org.odk.collect.maps.markers.MarkerIconDescription;
+import org.odk.collect.maps.traces.LineDescription;
+import org.odk.collect.maps.traces.PolygonDescription;
import org.odk.collect.settings.SettingsProvider;
import org.osmdroid.api.IGeoPoint;
import org.osmdroid.events.MapListener;
@@ -78,9 +72,6 @@
import org.osmdroid.views.overlay.Polyline;
import org.osmdroid.views.overlay.ScaleBarOverlay;
import org.osmdroid.views.overlay.TilesOverlay;
-import org.osmdroid.views.overlay.mylocation.IMyLocationConsumer;
-import org.osmdroid.views.overlay.mylocation.IMyLocationProvider;
-import org.osmdroid.views.overlay.mylocation.MyLocationNewOverlay;
import java.io.File;
import java.util.ArrayList;
@@ -97,8 +88,7 @@
/**
* A MapFragment drawn by OSMDroid.
*/
-public class OsmDroidMapFragment extends MapViewModelMapFragment implements
- LocationListener, LocationClient.LocationClientListener {
+public class OsmDroidMapFragment extends MapViewModelMapFragment {
// Bundle keys understood by applyConfig().
public static final String KEY_WEB_MAP_SERVICE = "WEB_MAP_SERVICE";
@@ -106,9 +96,6 @@ public class OsmDroidMapFragment extends MapViewModelMapFragment implements
@Inject
ReferenceLayerRepository referenceLayerRepository;
- @Inject
- LocationClient locationClient;
-
@Inject
MapConfigurator mapConfigurator;
@@ -119,14 +106,10 @@ public class OsmDroidMapFragment extends MapViewModelMapFragment implements
private ReadyListener readyListener;
private PointListener clickListener;
private PointListener longPressListener;
- private PointListener gpsLocationListener;
private FeatureListener featureClickListener;
private FeatureListener dragEndListener;
- private MyLocationNewOverlay myLocationOverlay;
- private OsmLocationClientWrapper osmLocationClientWrapper;
private int nextFeatureId = 1;
private final Map features = new HashMap<>();
- private boolean clientWantsLocationUpdates;
private IGeoPoint lastMapCenter;
private WebMapService webMapService;
private File referenceLayerFile;
@@ -159,18 +142,6 @@ public T create(@NonNull Class modelClass) {
mapViewModel = new ViewModelProvider(this, viewModelFactory).get(MapViewModel.class);
}
- @Override
- public void onResume() {
- super.onResume();
- enableLocationUpdates(clientWantsLocationUpdates);
- }
-
- @Override
- public void onPause() {
- super.onPause();
- enableLocationUpdates(false);
- }
-
@Override
public void onDestroy() {
clearFeatures(); // prevent a memory leak due to refs held by markers
@@ -221,13 +192,6 @@ public boolean onScroll(ScrollEvent event) {
addAttributionAndMapEventsOverlays();
loadReferenceOverlay();
addMapLayoutChangeListener(map);
- osmLocationClientWrapper = new OsmLocationClientWrapper(locationClient);
- myLocationOverlay = new MyLocationNewOverlay(osmLocationClientWrapper, map);
- myLocationOverlay.setDrawAccuracyEnabled(true);
- Drawable drawable = ContextCompat.getDrawable(requireActivity(), org.odk.collect.maps.R.drawable.ic_crosshairs);
- Bitmap crosshairs = toBitmap(drawable, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), null);
- myLocationOverlay.setDirectionArrow(crosshairs, crosshairs);
- myLocationOverlay.setPersonHotspot(crosshairs.getWidth() / 2.0f, crosshairs.getHeight() / 2.0f);
new Handler().postDelayed(() -> {
// If the screen is rotated before the map is ready, this fragment
@@ -301,25 +265,16 @@ public double getZoom() {
return map.getZoomLevel();
}
- @Override
- public int addMarker(MarkerDescription markerDescription) {
- int featureId = nextFeatureId++;
- return addMarker(featureId, markerDescription);
- }
-
- private int addMarker(int featureId, MarkerDescription markerDescription) {
- features.put(featureId, new MarkerFeature(map, markerDescription));
- return featureId;
- }
-
@Override
public List addMarkers(List markers) {
List featureIds = new ArrayList<>();
for (MarkerDescription markerDescription : markers) {
- int featureId = addMarker(markerDescription);
+ int featureId = nextFeatureId++;
+ features.put(featureId, new MarkerFeature(map, markerDescription));
featureIds.add(featureId);
}
+ map.invalidate();
return featureIds;
}
@@ -404,6 +359,14 @@ public void clearFeatures() {
nextFeatureId = 1;
}
+ @Override
+ public void clearFeatures(@NotNull List<@NotNull Integer> ids) {
+ for (Integer id : ids) {
+ features.remove(id).dispose();
+ }
+
+ map.invalidate();
+ }
@Override
public void setClickListener(@Nullable PointListener listener) {
@@ -425,98 +388,7 @@ public void setDragEndListener(@Nullable FeatureListener listener) {
dragEndListener = listener;
}
- @Override
- public void setGpsLocationListener(@Nullable PointListener listener) {
- gpsLocationListener = listener;
- }
-
- @Override
- public void setRetainMockAccuracy(boolean retainMockAccuracy) {
- locationClient.setRetainMockAccuracy(retainMockAccuracy);
- }
-
- @Override
- public void setGpsLocationEnabled(boolean enable) {
- if (enable != clientWantsLocationUpdates) {
- clientWantsLocationUpdates = enable;
- enableLocationUpdates(clientWantsLocationUpdates);
- }
- }
-
- @Override
- public @Nullable
- MapPoint getGpsLocation() {
- return fromLocation(myLocationOverlay);
- }
-
- @Override
- public void onLocationChanged(Location location) {
- Timber.i("onLocationChanged: location = %s", location);
- if (gpsLocationListener != null) {
- MapPoint point = fromLocation(myLocationOverlay);
- if (point != null) {
- gpsLocationListener.onPoint(point);
- }
- }
-
- if (myLocationOverlay != null) {
- myLocationOverlay.onLocationChanged(location, osmLocationClientWrapper);
- }
- }
-
- @Override
- public void onClientStart() {
- map.getOverlays().add(myLocationOverlay);
- myLocationOverlay.setEnabled(true);
- myLocationOverlay.enableMyLocation();
-
- Timber.i("Requesting location updates (to %s)", this);
- locationClient.requestLocationUpdates(this);
- }
-
- @Override
- public void onClientStartFailure() {
- }
-
- @Override
- public void onClientStop() {
- }
-
- private void enableLocationUpdates(boolean enable) {
- if (enable) {
- Timber.i("Starting LocationClient %s (for MapFragment %s)", locationClient, this);
- locationClient.start(this);
- } else {
- Timber.i("Stopping LocationClient %s (for MapFragment %s)", locationClient, this);
- locationClient.stop();
- myLocationOverlay.setEnabled(false);
- safelyDisableOverlayLocationFollowing();
- }
- }
-
- /**
- *
- * https://github.com/osmdroid/osmdroid/issues/1783
- *
- **/
- private void safelyDisableOverlayLocationFollowing() {
- if (map.isAttachedToWindow()) {
- myLocationOverlay.disableFollowLocation();
- myLocationOverlay.disableMyLocation();
- }
- }
- private static @Nullable
- MapPoint fromLocation(@NonNull MyLocationNewOverlay overlay) {
- GeoPoint geoPoint = overlay.getMyLocation();
- if (geoPoint == null) {
- return null;
- }
- return new MapPoint(
- geoPoint.getLatitude(), geoPoint.getLongitude(),
- geoPoint.getAltitude(), overlay.getLastFix().getAccuracy()
- );
- }
private static @NonNull
MapPoint fromGeoPoint(@NonNull IGeoPoint geoPoint) {
@@ -734,7 +606,8 @@ public MapViewModel getMapViewModel() {
@Override
public void updateMarker(int featureId, @NotNull MarkerDescription markerDescription) {
features.get(featureId).dispose();
- addMarker(featureId, markerDescription);
+ features.put(featureId, new MarkerFeature(map, markerDescription));
+ map.invalidate();
}
@Override
@@ -1129,39 +1002,6 @@ public void draw(Canvas canvas, MapView map, boolean shadow) {
}
}
- private static class OsmLocationClientWrapper implements IMyLocationProvider {
- private LocationClient locationClient;
-
- OsmLocationClientWrapper(LocationClient locationClient) {
- this.locationClient = locationClient;
- }
-
- @Override
- public boolean startLocationProvider(IMyLocationConsumer myLocationConsumer) {
- // locationClient.start launches async work and we need to be confident that
- // getLastKnownLocation is never called before onClientStart so we don't let the OSM
- // location overlay start the provider. We also ignore the location consumer passed in
- // and instead explicitly forward location updates to the overlay from onLocationChanged
- return true;
- }
-
- @Override
- public void stopLocationProvider() {
- locationClient.stop();
- }
-
- @Override
- public Location getLastKnownLocation() {
- return locationClient.getLastLocation();
- }
-
- @Override
- public void destroy() {
- locationClient.stop();
- locationClient = null;
- }
- }
-
private static class RegisterReceiver implements IRegisterReceiver {
private final Context context;
diff --git a/strings/src/main/res/values/strings.xml b/strings/src/main/res/values/strings.xml
index 325d07e7822..b536a8fac06 100644
--- a/strings/src/main/res/values/strings.xml
+++ b/strings/src/main/res/values/strings.xml
@@ -587,7 +587,7 @@
Are you sure you want to delete the %1$s offline layer?
- Record a point
+ Add marker
Accuracy: %1$s
⚠️ Accuracy: %1$s (unacceptable)
Sorry, Location providers are disabled!