mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-26 20:33:53 +01:00
Nearby: Fix race condition and lag when loading pin details, faster overlay management (#6047)
* temporary fixes part one * temporary fixes part two * temporary fixes part three * temporary fixes part four * temporary fixes part five * reformatting * remove code no longer in use * Migrate NearbyParentFragmentPresenter to Kotlin * Partially replace temporary experimental fixes * Replace temporary experimental fixes part 2 * Replace temporary experimental fixes part 3 * Replace temporary fixes completely * Fix caching and loading places in Nearby list * Add place bookmarking logic, Remove all old code * Nearby Presenter: Close channel properly * Nearby pins now load starting from the center Fixes #6049 * Add comments and javadoc for Nearby Presenter * Fix warnings, Fix formatting, Add javadoc * Pass unit tests
This commit is contained in:
parent
70b4f78a5d
commit
c891c2b0df
6 changed files with 676 additions and 679 deletions
|
|
@ -20,8 +20,8 @@ import timber.log.Timber;
|
|||
|
||||
public class NearbyController extends MapController {
|
||||
|
||||
private static final int MAX_RESULTS = 1000;
|
||||
private final NearbyPlaces nearbyPlaces;
|
||||
public static final int MAX_RESULTS = 1000;
|
||||
public static double currentLocationSearchRadius = 10.0; //in kilometers
|
||||
public static LatLng currentLocation; // Users latest fetched location
|
||||
public static LatLng latestSearchLocation; // Can be current and camera target on search this area button is used
|
||||
|
|
|
|||
|
|
@ -2,11 +2,13 @@ package fr.free.nrw.commons.nearby.contract;
|
|||
|
||||
import android.content.Context;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.lifecycle.LifecycleCoroutineScope;
|
||||
import fr.free.nrw.commons.BaseMarker;
|
||||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||
import fr.free.nrw.commons.location.LatLng;
|
||||
import fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType;
|
||||
import fr.free.nrw.commons.nearby.Label;
|
||||
import fr.free.nrw.commons.nearby.MarkerPlaceGroup;
|
||||
import fr.free.nrw.commons.nearby.Place;
|
||||
import java.util.List;
|
||||
|
||||
|
|
@ -68,7 +70,7 @@ public interface NearbyParentFragmentContract {
|
|||
|
||||
Context getContext();
|
||||
|
||||
void updateMapMarkers(List<BaseMarker> BaseMarkers);
|
||||
void replaceMarkerOverlays(List<MarkerPlaceGroup> markerPlaceGroups);
|
||||
|
||||
void filterOutAllMarkers();
|
||||
|
||||
|
|
@ -127,5 +129,7 @@ public interface NearbyParentFragmentContract {
|
|||
void setCheckboxUnknown();
|
||||
|
||||
void setAdvancedQuery(String query);
|
||||
|
||||
void toggleBookmarkedStatus(Place place);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,8 +23,6 @@ import android.graphics.drawable.Drawable;
|
|||
import android.location.Location;
|
||||
import android.location.LocationManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Build.VERSION;
|
||||
import android.os.Build.VERSION_CODES;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.os.Handler;
|
||||
|
|
@ -56,6 +54,7 @@ import androidx.appcompat.app.AlertDialog.Builder;
|
|||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.content.FileProvider;
|
||||
import androidx.lifecycle.LifecycleOwnerKt;
|
||||
import androidx.recyclerview.widget.DividerItemDecoration;
|
||||
import androidx.recyclerview.widget.GridLayoutManager;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
|
|
@ -110,16 +109,12 @@ import fr.free.nrw.commons.wikidata.WikidataEditListener;
|
|||
import io.reactivex.Completable;
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
|
@ -155,6 +150,29 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
|
|||
|
||||
FragmentNearbyParentBinding binding;
|
||||
|
||||
public final MapEventsOverlay mapEventsOverlay = new MapEventsOverlay(new MapEventsReceiver() {
|
||||
@Override
|
||||
public boolean singleTapConfirmedHelper(GeoPoint p) {
|
||||
if (clickedMarker != null) {
|
||||
clickedMarker.closeInfoWindow();
|
||||
} else {
|
||||
Timber.e("CLICKED MARKER IS NULL");
|
||||
}
|
||||
if (isListBottomSheetExpanded()) {
|
||||
// Back should first hide the bottom sheet if it is expanded
|
||||
hideBottomSheet();
|
||||
} else if (isDetailsBottomSheetVisible()) {
|
||||
hideBottomDetailsSheet();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean longPressHelper(GeoPoint p) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
@Inject
|
||||
LocationServiceManager locationManager;
|
||||
@Inject
|
||||
|
|
@ -217,7 +235,6 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
|
|||
private Runnable searchRunnable;
|
||||
private static final long SCROLL_DELAY = 800; // Delay for debounce of onscroll, in milliseconds.
|
||||
|
||||
private List<Place> updatedPlacesList;
|
||||
private LatLng updatedLatLng;
|
||||
private boolean searchable;
|
||||
|
||||
|
|
@ -308,10 +325,6 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
|
|||
* WLM URL
|
||||
*/
|
||||
public static final String WLM_URL = "https://commons.wikimedia.org/wiki/Commons:Mobile_app/Contributing_to_WLM_using_the_app";
|
||||
/**
|
||||
* Saves response of list of places for the first time
|
||||
*/
|
||||
private List<Place> places = new ArrayList<>();
|
||||
|
||||
@NonNull
|
||||
public static NearbyParentFragment newInstance() {
|
||||
|
|
@ -327,7 +340,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
|
|||
view = binding.getRoot();
|
||||
|
||||
initNetworkBroadCastReceiver();
|
||||
presenter = new NearbyParentFragmentPresenter(bookmarkLocationDao);
|
||||
presenter = new NearbyParentFragmentPresenter(bookmarkLocationDao, placesRepository, nearbyController);
|
||||
progressDialog = new ProgressDialog(getActivity());
|
||||
progressDialog.setCancelable(false);
|
||||
progressDialog.setMessage("Saving in progress...");
|
||||
|
|
@ -452,28 +465,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
|
|||
binding.map.getOverlays().add(scaleBarOverlay);
|
||||
binding.map.getZoomController().setVisibility(Visibility.NEVER);
|
||||
binding.map.getController().setZoom(ZOOM_LEVEL);
|
||||
binding.map.getOverlays().add(new MapEventsOverlay(new MapEventsReceiver() {
|
||||
@Override
|
||||
public boolean singleTapConfirmedHelper(GeoPoint p) {
|
||||
if (clickedMarker != null) {
|
||||
clickedMarker.closeInfoWindow();
|
||||
} else {
|
||||
Timber.e("CLICKED MARKER IS NULL");
|
||||
}
|
||||
if (isListBottomSheetExpanded()) {
|
||||
// Back should first hide the bottom sheet if it is expanded
|
||||
hideBottomSheet();
|
||||
} else if (isDetailsBottomSheetVisible()) {
|
||||
hideBottomDetailsSheet();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean longPressHelper(GeoPoint p) {
|
||||
return false;
|
||||
}
|
||||
}));
|
||||
binding.map.getOverlays().add(mapEventsOverlay);
|
||||
|
||||
binding.map.addMapListener(new MapListener() {
|
||||
@Override
|
||||
|
|
@ -605,8 +597,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
|
|||
return Unit.INSTANCE;
|
||||
},
|
||||
(place, isBookmarked) -> {
|
||||
updateMarker(isBookmarked, place, null);
|
||||
binding.map.invalidate();
|
||||
presenter.toggleBookmarkedStatus(place);
|
||||
return Unit.INSTANCE;
|
||||
},
|
||||
commonPlaceClickActions,
|
||||
|
|
@ -670,19 +661,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
|
|||
registerNetworkReceiver();
|
||||
if (isResumed() && ((MainActivity) getActivity()).activeFragment == ActiveFragment.NEARBY) {
|
||||
if (locationPermissionsHelper.checkLocationPermission(getActivity())) {
|
||||
if (lastFocusLocation == null && lastKnownLocation == null) {
|
||||
locationPermissionGranted();
|
||||
} else{
|
||||
if (updatedPlacesList != null) {
|
||||
if (!updatedPlacesList.isEmpty()) {
|
||||
loadPlacesDataAsync(updatedPlacesList, updatedLatLng);
|
||||
} else {
|
||||
updateMapMarkers(updatedPlacesList, getLastMapFocus(), false);
|
||||
}
|
||||
}else {
|
||||
locationPermissionGranted();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
startMapWithoutPermission();
|
||||
}
|
||||
|
|
@ -973,7 +952,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
|
|||
|
||||
@Override
|
||||
public void updateListFragment(final List<Place> placeList) {
|
||||
places = placeList;
|
||||
adapter.clear();
|
||||
adapter.setItems(placeList);
|
||||
binding.bottomSheetNearby.noResultsMessage.setVisibility(
|
||||
placeList.isEmpty() ? View.VISIBLE : View.GONE);
|
||||
|
|
@ -1367,13 +1346,8 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
|
|||
? getTextBetweenParentheses(
|
||||
updatedPlace.getLongDescription()) : updatedPlace.getLongDescription());
|
||||
marker.showInfoWindow();
|
||||
for (int i = 0; i < updatedPlacesList.size(); i++) {
|
||||
Place pl = updatedPlacesList.get(i);
|
||||
if (pl.location == updatedPlace.location) {
|
||||
updatedPlacesList.set(i, updatedPlace);
|
||||
presenter.handlePinClicked(updatedPlace);
|
||||
savePlaceToDatabase(place);
|
||||
}
|
||||
}
|
||||
Drawable icon = ContextCompat.getDrawable(getContext(),
|
||||
getIconFor(updatedPlace, isBookMarked));
|
||||
marker.setIcon(icon);
|
||||
|
|
@ -1412,12 +1386,10 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
|
|||
setProgressBarVisibility(false);
|
||||
presenter.lockUnlockNearby(false);
|
||||
} else {
|
||||
updateMapMarkers(nearbyPlacesInfo.placeList, nearbyPlacesInfo.currentLatLng,
|
||||
true);
|
||||
updateMapMarkers(nearbyPlacesInfo.placeList, searchLatLng, true);
|
||||
lastFocusLocation = searchLatLng;
|
||||
lastMapFocus = new GeoPoint(searchLatLng.getLatitude(),
|
||||
searchLatLng.getLongitude());
|
||||
loadPlacesDataAsync(nearbyPlacesInfo.placeList, nearbyPlacesInfo.currentLatLng);
|
||||
}
|
||||
},
|
||||
throwable -> {
|
||||
|
|
@ -1457,12 +1429,10 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
|
|||
|
||||
// curLatLng is used to calculate distance from the current location to the place
|
||||
// and distance is later on populated to the place
|
||||
updateMapMarkers(nearbyPlacesInfo.placeList, nearbyPlacesInfo.currentLatLng,
|
||||
false);
|
||||
updateMapMarkers(nearbyPlacesInfo.placeList, searchLatLng, false);
|
||||
lastMapFocus = new GeoPoint(searchLatLng.getLatitude(),
|
||||
searchLatLng.getLongitude());
|
||||
stopQuery();
|
||||
loadPlacesDataAsync(nearbyPlacesInfo.placeList, nearbyPlacesInfo.currentLatLng);
|
||||
}
|
||||
},
|
||||
throwable -> {
|
||||
|
|
@ -1475,167 +1445,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
|
|||
}));
|
||||
}
|
||||
|
||||
public void loadPlacesDataAsync(List<Place> placeList, LatLng curLatLng) {
|
||||
List<Place> places = new ArrayList<>(placeList);
|
||||
|
||||
// Instead of loading all pins in a single SPARQL query, we query in batches.
|
||||
// This variable controls the number of pins queried per batch.
|
||||
int batchSize = 3;
|
||||
|
||||
updatedLatLng = curLatLng;
|
||||
updatedPlacesList = new ArrayList<>(placeList);
|
||||
|
||||
// Sorts the places by distance to ensure the nearest pins are ready for the user as soon
|
||||
// as possible.
|
||||
if (VERSION.SDK_INT >= VERSION_CODES.N) {
|
||||
Collections.sort(places,
|
||||
Comparator.comparingDouble(place -> place.getDistanceInDouble(getMapFocus())));
|
||||
}
|
||||
stopQuery = false;
|
||||
processBatchesSequentially(places, batchSize, updatedPlacesList, curLatLng, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a list of places in batches sequentially. This method handles the asynchronous
|
||||
* processing of places, updating the map markers and updates the list of updated places accordingly.
|
||||
*
|
||||
* @param places The list of Place objects to be processed.
|
||||
* @param batchSize The size of each batch to be processed.
|
||||
* @param updatedPlaceList The list of Place objects to be updated.
|
||||
* @param curLatLng The current location of the user.
|
||||
* @param startIndex The starting index for the current batch.
|
||||
*/
|
||||
@SuppressLint("CheckResult")
|
||||
private void processBatchesSequentially(List<Place> places, int batchSize,
|
||||
List<Place> updatedPlaceList, LatLng curLatLng, int startIndex) {
|
||||
if (startIndex >= places.size() || stopQuery) {
|
||||
return;
|
||||
}
|
||||
|
||||
int endIndex = Math.min(startIndex + batchSize, places.size());
|
||||
List<Place> batch = places.subList(startIndex, endIndex);
|
||||
for (int i = 0; i < batch.size(); i++) {
|
||||
if (i == batch.size() - 1 && batch.get(i).name != "") {
|
||||
processBatchesSequentially(places, batchSize, updatedPlaceList, curLatLng,
|
||||
endIndex + batchSize);
|
||||
return;
|
||||
}
|
||||
if (batch.get(i).name == "") {
|
||||
if (i == 0) {
|
||||
break;
|
||||
}
|
||||
processBatchesSequentially(places, batchSize, updatedPlaceList, curLatLng,
|
||||
endIndex + i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Disposable disposable = processBatch(batch, updatedPlaceList)
|
||||
.subscribe(p -> {
|
||||
if (stopQuery) {
|
||||
return;
|
||||
}
|
||||
if (!p.isEmpty() && p != updatedPlaceList) {
|
||||
synchronized (updatedPlaceList) {
|
||||
updatedPlaceList.clear();
|
||||
updatedPlaceList.addAll((Collection<? extends Place>) p);
|
||||
}
|
||||
}
|
||||
updateMapMarkers(new ArrayList<>(updatedPlaceList), curLatLng, false);
|
||||
processBatchesSequentially(places, batchSize, updatedPlaceList, curLatLng, endIndex);
|
||||
}, throwable -> {
|
||||
Timber.e(throwable);
|
||||
showErrorMessage(getString(R.string.error_fetching_nearby_places) + throwable.getLocalizedMessage());
|
||||
setFilterState();
|
||||
});
|
||||
|
||||
compositeDisposable.add(disposable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a batch of places, updating the provided place list with fetched or updated data.
|
||||
* This method handles the asynchronous fetching and updating of places from the repository.
|
||||
*
|
||||
* @param batch The batch of Place objects to be processed.
|
||||
* @param placeList The list of Place objects to be updated.
|
||||
* @return An Observable emitting the updated list of Place objects.
|
||||
*/
|
||||
private Observable<List<?>> processBatch(List<Place> batch, List<Place> placeList) {
|
||||
List<Place> toBeProcessed = new ArrayList<>();
|
||||
|
||||
List<Observable<Place>> placeObservables = new ArrayList<>();
|
||||
|
||||
for (Place place : batch) {
|
||||
Observable<Place> placeObservable = Observable
|
||||
.fromCallable(() -> {
|
||||
Place fetchedPlace = placesRepository.fetchPlace(place.entityID);
|
||||
return fetchedPlace != null ? fetchedPlace : place;
|
||||
})
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.doOnNext(placeData -> {
|
||||
if (placeData.equals(place)) {
|
||||
toBeProcessed.add(place);
|
||||
} else {
|
||||
for (int i = 0; i < placeList.size(); i++) {
|
||||
Place pl = placeList.get(i);
|
||||
if (pl.location.equals(place.location)) {
|
||||
placeList.set(i, placeData);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
placeObservables.add(placeObservable);
|
||||
}
|
||||
|
||||
return Observable.zip(placeObservables, objects -> toBeProcessed)
|
||||
.flatMap(processedList -> {
|
||||
if (processedList.isEmpty()) {
|
||||
return Observable.just(placeList);
|
||||
}
|
||||
return Observable.fromCallable(() -> nearbyController.getPlaces(processedList))
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.map(places -> {
|
||||
if (stopQuery) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
if (places == null || places.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
} else {
|
||||
List<Place> updatedPlaceList = new ArrayList<>(placeList);
|
||||
for (Place place : places) {
|
||||
for (Place foundPlace : placeList) {
|
||||
if (place.siteLinks.getWikidataLink()
|
||||
.equals(foundPlace.siteLinks.getWikidataLink())) {
|
||||
place.location = foundPlace.location;
|
||||
place.distance = foundPlace.distance;
|
||||
place.setMonument(foundPlace.isMonument());
|
||||
int index = updatedPlaceList.indexOf(foundPlace);
|
||||
if (index != -1) {
|
||||
updatedPlaceList.set(index, place);
|
||||
savePlaceToDatabase(place);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return updatedPlaceList;
|
||||
}
|
||||
})
|
||||
.onErrorReturn(throwable -> {
|
||||
Timber.e(throwable);
|
||||
showErrorMessage(getString(R.string.error_fetching_nearby_places) + " "
|
||||
+ throwable.getLocalizedMessage());
|
||||
setFilterState();
|
||||
return Collections.emptyList();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void savePlaceToDatabase(Place place) {
|
||||
public void savePlaceToDatabase(Place place) {
|
||||
compositeDisposable.add(placesRepository
|
||||
.save(place)
|
||||
.subscribeOn(Schedulers.io())
|
||||
|
|
@ -1661,8 +1471,8 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
|
|||
*/
|
||||
private void updateMapMarkers(final List<Place> nearbyPlaces, final LatLng curLatLng,
|
||||
final boolean shouldUpdateSelectedMarker) {
|
||||
presenter.updateMapMarkers(nearbyPlaces, curLatLng, shouldUpdateSelectedMarker);
|
||||
setFilterState();
|
||||
presenter.updateMapMarkers(nearbyPlaces, curLatLng,
|
||||
LifecycleOwnerKt.getLifecycleScope(getViewLifecycleOwner()));
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1899,13 +1709,6 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateMapMarkers(final List<BaseMarker> BaseMarkers) {
|
||||
if (binding.map != null) {
|
||||
presenter.updateMapMarkersToController(BaseMarkers);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void filterOutAllMarkers() {
|
||||
clearAllMarkers();
|
||||
|
|
@ -1925,8 +1728,11 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
|
|||
final boolean displayExists = false;
|
||||
final boolean displayNeedsPhoto= false;
|
||||
final boolean displayWlm = false;
|
||||
// Remove the previous markers before updating them
|
||||
clearAllMarkers();
|
||||
if (selectedLabels == null || selectedLabels.size() == 0) {
|
||||
replaceMarkerOverlays(NearbyController.markerLabelList);
|
||||
return;
|
||||
}
|
||||
final ArrayList<MarkerPlaceGroup> placeGroupsToShow = new ArrayList<>();
|
||||
for (final MarkerPlaceGroup markerPlaceGroup : NearbyController.markerLabelList) {
|
||||
final Place place = markerPlaceGroup.getPlace();
|
||||
// When label filter is engaged
|
||||
|
|
@ -1967,19 +1773,12 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
|
|||
}
|
||||
|
||||
if (shouldUpdateMarker) {
|
||||
updateMarker(markerPlaceGroup.getIsBookmarked(), place,
|
||||
NearbyController.currentLocation);
|
||||
placeGroupsToShow.add(
|
||||
new MarkerPlaceGroup(markerPlaceGroup.getIsBookmarked(), place)
|
||||
);
|
||||
}
|
||||
}
|
||||
if (selectedLabels == null || selectedLabels.size() == 0) {
|
||||
ArrayList<BaseMarker> markerArrayList = new ArrayList<>();
|
||||
for (final MarkerPlaceGroup markerPlaceGroup : NearbyController.markerLabelList) {
|
||||
BaseMarker nearbyBaseMarker = new BaseMarker();
|
||||
nearbyBaseMarker.setPlace(markerPlaceGroup.getPlace());
|
||||
markerArrayList.add(nearbyBaseMarker);
|
||||
}
|
||||
addMarkersToMap(markerArrayList);
|
||||
}
|
||||
replaceMarkerOverlays(placeGroupsToShow);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -1987,18 +1786,6 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
|
|||
return binding.map == null ? null : getMapFocus();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets marker icon according to marker status. Sets title and distance.
|
||||
*
|
||||
* @param isBookmarked true if place is bookmarked
|
||||
* @param place
|
||||
* @param currentLatLng current location
|
||||
*/
|
||||
public void updateMarker(final boolean isBookmarked, final Place place,
|
||||
@Nullable final LatLng currentLatLng) {
|
||||
addMarkerToMap(place, isBookmarked);
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlights nearest place when user clicks on home nearby banner
|
||||
*
|
||||
|
|
@ -2052,13 +1839,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a marker representing a place to the map with optional bookmark icon.
|
||||
*
|
||||
* @param place The Place object containing information about the location.
|
||||
* @param isBookMarked A Boolean flag indicating whether the place is bookmarked or not.
|
||||
*/
|
||||
private void addMarkerToMap(Place place, Boolean isBookMarked) {
|
||||
public Marker convertToMarker(Place place, Boolean isBookMarked) {
|
||||
Drawable icon = ContextCompat.getDrawable(getContext(), getIconFor(place, isBookMarked));
|
||||
GeoPoint point = new GeoPoint(place.location.getLatitude(), place.location.getLongitude());
|
||||
Marker marker = new Marker(binding.map);
|
||||
|
|
@ -2094,21 +1875,26 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
|
|||
bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
|
||||
return true;
|
||||
});
|
||||
binding.map.getOverlays().add(marker);
|
||||
return marker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds multiple markers representing places to the map and handles item gestures.
|
||||
*
|
||||
* @param nearbyBaseMarkers The list of Place objects containing information about the
|
||||
* locations.
|
||||
* @param markerPlaceGroups The list of marker place groups containing the places and
|
||||
* their bookmarked status
|
||||
*/
|
||||
private void addMarkersToMap(List<BaseMarker> nearbyBaseMarkers) {
|
||||
@Override
|
||||
public void replaceMarkerOverlays(final List<MarkerPlaceGroup> markerPlaceGroups) {
|
||||
ArrayList<Marker> newMarkers = new ArrayList<>(markerPlaceGroups.size());
|
||||
for (MarkerPlaceGroup markerPlaceGroup : markerPlaceGroups) {
|
||||
newMarkers.add(
|
||||
convertToMarker(markerPlaceGroup.getPlace(), markerPlaceGroup.getIsBookmarked()));
|
||||
}
|
||||
clearAllMarkers();
|
||||
binding.map.getOverlays().addAll(newMarkers);
|
||||
}
|
||||
|
||||
for(int i = 0; i< nearbyBaseMarkers.size(); i++){
|
||||
addMarkerToMap(nearbyBaseMarkers.get(i).getPlace(), false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts text between the first occurrence of '(' and its corresponding ')' in the input
|
||||
|
|
@ -2400,7 +2186,6 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
|
|||
binding.map.invalidate();
|
||||
GeoPoint geoPoint = mapCenter;
|
||||
if (geoPoint != null) {
|
||||
List<Overlay> overlays = binding.map.getOverlays();
|
||||
ScaleDiskOverlay diskOverlay =
|
||||
new ScaleDiskOverlay(this.getContext(),
|
||||
geoPoint, 2000, UnitOfMeasure.foot);
|
||||
|
|
@ -2434,28 +2219,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
|
|||
scaleBarOverlay.setBackgroundPaint(barPaint);
|
||||
scaleBarOverlay.enableScaleBar();
|
||||
binding.map.getOverlays().add(scaleBarOverlay);
|
||||
binding.map.getOverlays().add(new MapEventsOverlay(new MapEventsReceiver() {
|
||||
@Override
|
||||
public boolean singleTapConfirmedHelper(GeoPoint p) {
|
||||
if (clickedMarker != null) {
|
||||
clickedMarker.closeInfoWindow();
|
||||
} else {
|
||||
Timber.e("CLICKED MARKER IS NULL");
|
||||
}
|
||||
if (isListBottomSheetExpanded()) {
|
||||
// Back should first hide the bottom sheet if it is expanded
|
||||
hideBottomSheet();
|
||||
} else if (isDetailsBottomSheetVisible()) {
|
||||
hideBottomDetailsSheet();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean longPressHelper(GeoPoint p) {
|
||||
return false;
|
||||
}
|
||||
}));
|
||||
binding.map.getOverlays().add(mapEventsOverlay);
|
||||
binding.map.setMultiTouchControls(true);
|
||||
}
|
||||
|
||||
|
|
@ -2510,21 +2274,14 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
|
|||
@Override
|
||||
public void onBottomSheetItemClick(@Nullable View view, int position) {
|
||||
BottomSheetItem item = dataList.get(position);
|
||||
boolean isBookmarked = bookmarkLocationDao.findBookmarkLocation(selectedPlace);
|
||||
switch (item.getImageResourceId()) {
|
||||
case R.drawable.ic_round_star_border_24px:
|
||||
bookmarkLocationDao.updateBookmarkLocation(selectedPlace);
|
||||
presenter.toggleBookmarkedStatus(selectedPlace);
|
||||
updateBookmarkButtonImage(selectedPlace);
|
||||
isBookmarked = bookmarkLocationDao.findBookmarkLocation(selectedPlace);
|
||||
updateMarker(isBookmarked, selectedPlace, locationManager.getLastLocation());
|
||||
binding.map.invalidate();
|
||||
break;
|
||||
case R.drawable.ic_round_star_filled_24px:
|
||||
bookmarkLocationDao.updateBookmarkLocation(selectedPlace);
|
||||
presenter.toggleBookmarkedStatus(selectedPlace);
|
||||
updateBookmarkButtonImage(selectedPlace);
|
||||
isBookmarked = bookmarkLocationDao.findBookmarkLocation(selectedPlace);
|
||||
updateMarker(isBookmarked, selectedPlace, locationManager.getLastLocation());
|
||||
binding.map.invalidate();
|
||||
break;
|
||||
case R.drawable.ic_directions_black_24dp:
|
||||
Utils.handleGeoCoordinates(this.getContext(), selectedPlace.getLocation());
|
||||
|
|
|
|||
|
|
@ -1,368 +0,0 @@
|
|||
package fr.free.nrw.commons.nearby.presenter;
|
||||
|
||||
import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.CUSTOM_QUERY;
|
||||
import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED;
|
||||
import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.LOCATION_SLIGHTLY_CHANGED;
|
||||
import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.MAP_UPDATED;
|
||||
import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.SEARCH_CUSTOM_AREA;
|
||||
import static fr.free.nrw.commons.nearby.CheckBoxTriStates.CHECKED;
|
||||
import static fr.free.nrw.commons.nearby.CheckBoxTriStates.UNCHECKED;
|
||||
import static fr.free.nrw.commons.nearby.CheckBoxTriStates.UNKNOWN;
|
||||
import static fr.free.nrw.commons.wikidata.WikidataConstants.PLACE_OBJECT;
|
||||
|
||||
import android.location.Location;
|
||||
import androidx.annotation.MainThread;
|
||||
import androidx.annotation.Nullable;
|
||||
import fr.free.nrw.commons.BaseMarker;
|
||||
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao;
|
||||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||
import fr.free.nrw.commons.location.LatLng;
|
||||
import fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType;
|
||||
import fr.free.nrw.commons.location.LocationUpdateListener;
|
||||
import fr.free.nrw.commons.nearby.CheckBoxTriStates;
|
||||
import fr.free.nrw.commons.nearby.Label;
|
||||
import fr.free.nrw.commons.nearby.MarkerPlaceGroup;
|
||||
import fr.free.nrw.commons.nearby.NearbyController;
|
||||
import fr.free.nrw.commons.nearby.Place;
|
||||
import fr.free.nrw.commons.nearby.contract.NearbyParentFragmentContract;
|
||||
import fr.free.nrw.commons.utils.LocationUtils;
|
||||
import fr.free.nrw.commons.wikidata.WikidataEditListener;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.List;
|
||||
import timber.log.Timber;
|
||||
|
||||
public class NearbyParentFragmentPresenter
|
||||
implements NearbyParentFragmentContract.UserActions,
|
||||
WikidataEditListener.WikidataP18EditListener,
|
||||
LocationUpdateListener {
|
||||
|
||||
private boolean isNearbyLocked;
|
||||
private LatLng currentLatLng;
|
||||
|
||||
private boolean placesLoadedOnce;
|
||||
|
||||
BookmarkLocationsDao bookmarkLocationDao;
|
||||
|
||||
private @Nullable String customQuery;
|
||||
|
||||
private static final NearbyParentFragmentContract.View DUMMY = (NearbyParentFragmentContract.View) Proxy.newProxyInstance(
|
||||
NearbyParentFragmentContract.View.class.getClassLoader(),
|
||||
new Class[]{NearbyParentFragmentContract.View.class}, (proxy, method, args) -> {
|
||||
if (method.getName().equals("onMyEvent")) {
|
||||
return null;
|
||||
} else if (String.class == method.getReturnType()) {
|
||||
return "";
|
||||
} else if (Integer.class == method.getReturnType()) {
|
||||
return Integer.valueOf(0);
|
||||
} else if (int.class == method.getReturnType()) {
|
||||
return 0;
|
||||
} else if (Boolean.class == method.getReturnType()) {
|
||||
return Boolean.FALSE;
|
||||
} else if (boolean.class == method.getReturnType()) {
|
||||
return false;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
);
|
||||
private NearbyParentFragmentContract.View nearbyParentFragmentView = DUMMY;
|
||||
|
||||
|
||||
public NearbyParentFragmentPresenter(BookmarkLocationsDao bookmarkLocationDao) {
|
||||
this.bookmarkLocationDao = bookmarkLocationDao;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attachView(NearbyParentFragmentContract.View view) {
|
||||
this.nearbyParentFragmentView = view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void detachView() {
|
||||
this.nearbyParentFragmentView = DUMMY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeNearbyPreferences(JsonKvStore applicationKvStore) {
|
||||
Timber.d("Remove place objects");
|
||||
applicationKvStore.remove(PLACE_OBJECT);
|
||||
}
|
||||
|
||||
public void initializeMapOperations() {
|
||||
lockUnlockNearby(false);
|
||||
updateMapAndList(LOCATION_SIGNIFICANTLY_CHANGED);
|
||||
nearbyParentFragmentView.setCheckBoxAction();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets click listeners of FABs, and 2 bottom sheets
|
||||
*/
|
||||
@Override
|
||||
public void setActionListeners(JsonKvStore applicationKvStore) {
|
||||
nearbyParentFragmentView.setFABPlusAction(v -> {
|
||||
if (applicationKvStore.getBoolean("login_skipped", false)) {
|
||||
// prompt the user to login
|
||||
nearbyParentFragmentView.displayLoginSkippedWarning();
|
||||
} else {
|
||||
nearbyParentFragmentView.animateFABs();
|
||||
}
|
||||
});
|
||||
|
||||
nearbyParentFragmentView.setFABRecenterAction(v -> {
|
||||
nearbyParentFragmentView.recenterMap(currentLatLng);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean backButtonClicked() {
|
||||
if (nearbyParentFragmentView.isAdvancedQueryFragmentVisible()) {
|
||||
nearbyParentFragmentView.showHideAdvancedQueryFragment(false);
|
||||
return true;
|
||||
} else if (nearbyParentFragmentView.isListBottomSheetExpanded()) {
|
||||
// Back should first hide the bottom sheet if it is expanded
|
||||
nearbyParentFragmentView.listOptionMenuItemClicked();
|
||||
return true;
|
||||
} else if (nearbyParentFragmentView.isDetailsBottomSheetVisible()) {
|
||||
nearbyParentFragmentView.setBottomSheetDetailsSmaller();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void markerUnselected() {
|
||||
nearbyParentFragmentView.hideBottomSheet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Nearby updates takes time, since they are network operations. During update time, we don't
|
||||
* want to get any other calls from user. So locking nearby.
|
||||
*
|
||||
* @param isNearbyLocked true means lock, false means unlock
|
||||
*/
|
||||
@Override
|
||||
public void lockUnlockNearby(boolean isNearbyLocked) {
|
||||
this.isNearbyLocked = isNearbyLocked;
|
||||
if (isNearbyLocked) {
|
||||
nearbyParentFragmentView.disableFABRecenter();
|
||||
} else {
|
||||
nearbyParentFragmentView.enableFABRecenter();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method should be the single point to update Map and List. Triggered by location changes
|
||||
*
|
||||
* @param locationChangeType defines if location changed significantly or slightly
|
||||
*/
|
||||
@Override
|
||||
public void updateMapAndList(LocationChangeType locationChangeType) {
|
||||
Timber.d("Presenter updates map and list");
|
||||
if (isNearbyLocked) {
|
||||
Timber.d("Nearby is locked, so updateMapAndList returns");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!nearbyParentFragmentView.isNetworkConnectionEstablished()) {
|
||||
Timber.d("Network connection is not established");
|
||||
return;
|
||||
}
|
||||
|
||||
LatLng lastLocation = nearbyParentFragmentView.getLastMapFocus();
|
||||
if (nearbyParentFragmentView.getMapCenter() != null) {
|
||||
currentLatLng = nearbyParentFragmentView.getMapCenter();
|
||||
} else {
|
||||
currentLatLng = lastLocation;
|
||||
}
|
||||
if (currentLatLng == null) {
|
||||
Timber.d("Skipping update of nearby places as location is unavailable");
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Significant changed - Markers and current location will be updated together
|
||||
* Slightly changed - Only current position marker will be updated
|
||||
*/
|
||||
if (locationChangeType.equals(CUSTOM_QUERY)) {
|
||||
Timber.d("ADVANCED_QUERY_SEARCH");
|
||||
lockUnlockNearby(true);
|
||||
nearbyParentFragmentView.setProgressBarVisibility(true);
|
||||
LatLng updatedLocationByUser = LocationUtils.deriveUpdatedLocationFromSearchQuery(
|
||||
customQuery);
|
||||
if (updatedLocationByUser == null) {
|
||||
updatedLocationByUser = lastLocation;
|
||||
}
|
||||
nearbyParentFragmentView.populatePlaces(updatedLocationByUser, customQuery);
|
||||
} else if (locationChangeType.equals(LOCATION_SIGNIFICANTLY_CHANGED)
|
||||
|| locationChangeType.equals(MAP_UPDATED)) {
|
||||
lockUnlockNearby(true);
|
||||
nearbyParentFragmentView.setProgressBarVisibility(true);
|
||||
nearbyParentFragmentView.populatePlaces(nearbyParentFragmentView.getMapCenter());
|
||||
} else if (locationChangeType.equals(SEARCH_CUSTOM_AREA)) {
|
||||
Timber.d("SEARCH_CUSTOM_AREA");
|
||||
lockUnlockNearby(true);
|
||||
nearbyParentFragmentView.setProgressBarVisibility(true);
|
||||
nearbyParentFragmentView.populatePlaces(nearbyParentFragmentView.getMapFocus());
|
||||
} else { // Means location changed slightly, ie user is walking or driving.
|
||||
Timber.d("Means location changed slightly");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates places for custom location, should be used for finding nearby places around a
|
||||
* location where you are not at.
|
||||
*
|
||||
* @param nearbyPlaces This variable has placeToCenter list information and distances.
|
||||
*/
|
||||
public void updateMapMarkers(List<Place> nearbyPlaces, LatLng currentLatLng,
|
||||
boolean shouldTrackPosition) {
|
||||
if (null != nearbyParentFragmentView) {
|
||||
nearbyParentFragmentView.clearAllMarkers();
|
||||
List<BaseMarker> baseMarkers = NearbyController
|
||||
.loadAttractionsFromLocationToBaseMarkerOptions(currentLatLng,
|
||||
// Curlatlang will be used to calculate distances
|
||||
nearbyPlaces);
|
||||
nearbyParentFragmentView.updateMapMarkers(baseMarkers);
|
||||
lockUnlockNearby(false); // So that new location updates wont come
|
||||
nearbyParentFragmentView.setProgressBarVisibility(false);
|
||||
nearbyParentFragmentView.updateListFragment(nearbyPlaces);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Some centering task may need to wait for map to be ready, if they are requested before map is
|
||||
* ready. So we will remember it when the map is ready
|
||||
*/
|
||||
private void handleCenteringTaskIfAny() {
|
||||
if (!placesLoadedOnce) {
|
||||
placesLoadedOnce = true;
|
||||
nearbyParentFragmentView.centerMapToPlace(null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWikidataEditSuccessful() {
|
||||
updateMapAndList(MAP_UPDATED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLocationChangedSignificantly(LatLng latLng) {
|
||||
Timber.d("Location significantly changed");
|
||||
updateMapAndList(LOCATION_SIGNIFICANTLY_CHANGED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLocationChangedSlightly(LatLng latLng) {
|
||||
Timber.d("Location significantly changed");
|
||||
updateMapAndList(LOCATION_SLIGHTLY_CHANGED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLocationChangedMedium(LatLng latLng) {
|
||||
Timber.d("Location changed medium");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void filterByMarkerType(List<Label> selectedLabels, int state,
|
||||
boolean filterForPlaceState, boolean filterForAllNoneType) {
|
||||
if (filterForAllNoneType) {// Means we will set labels based on states
|
||||
switch (state) {
|
||||
case UNKNOWN:
|
||||
// Do nothing
|
||||
break;
|
||||
case UNCHECKED:
|
||||
//TODO
|
||||
nearbyParentFragmentView.filterOutAllMarkers();
|
||||
nearbyParentFragmentView.setRecyclerViewAdapterItemsGreyedOut();
|
||||
break;
|
||||
case CHECKED:
|
||||
// Despite showing all labels NearbyFilterState still should be applied
|
||||
nearbyParentFragmentView.filterMarkersByLabels(selectedLabels,
|
||||
filterForPlaceState, false);
|
||||
nearbyParentFragmentView.setRecyclerViewAdapterAllSelected();
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
nearbyParentFragmentView.filterMarkersByLabels(selectedLabels,
|
||||
filterForPlaceState, false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@MainThread
|
||||
public void updateMapMarkersToController(List<BaseMarker> baseMarkers) {
|
||||
NearbyController.markerLabelList.clear();
|
||||
for (int i = 0; i < baseMarkers.size(); i++) {
|
||||
BaseMarker nearbyBaseMarker = baseMarkers.get(i);
|
||||
NearbyController.markerLabelList.add(
|
||||
new MarkerPlaceGroup(
|
||||
bookmarkLocationDao.findBookmarkLocation(nearbyBaseMarker.getPlace()),
|
||||
nearbyBaseMarker.getPlace()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCheckboxUnknown() {
|
||||
nearbyParentFragmentView.setCheckBoxState(CheckBoxTriStates.UNKNOWN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAdvancedQuery(String query) {
|
||||
this.customQuery = query;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void searchViewGainedFocus() {
|
||||
if (nearbyParentFragmentView.isListBottomSheetExpanded()) {
|
||||
// Back should first hide the bottom sheet if it is expanded
|
||||
nearbyParentFragmentView.hideBottomSheet();
|
||||
} else if (nearbyParentFragmentView.isDetailsBottomSheetVisible()) {
|
||||
nearbyParentFragmentView.hideBottomDetailsSheet();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates a search for places within the area. Depending on whether the search
|
||||
* is close to the current location, the map and list are updated
|
||||
* accordingly.
|
||||
*/
|
||||
public void searchInTheArea(){
|
||||
if (searchCloseToCurrentLocation()) {
|
||||
updateMapAndList(LOCATION_SIGNIFICANTLY_CHANGED);
|
||||
} else {
|
||||
updateMapAndList(SEARCH_CUSTOM_AREA);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if search this area button is used around our current location, so that we can
|
||||
* continue following our current location again
|
||||
*
|
||||
* @return Returns true if search this area button is used around our current location
|
||||
*/
|
||||
public boolean searchCloseToCurrentLocation() {
|
||||
if (null == nearbyParentFragmentView.getLastMapFocus()) {
|
||||
return true;
|
||||
}
|
||||
//TODO
|
||||
Location mylocation = new Location("");
|
||||
Location dest_location = new Location("");
|
||||
dest_location.setLatitude(nearbyParentFragmentView.getMapFocus().getLatitude());
|
||||
dest_location.setLongitude(nearbyParentFragmentView.getMapFocus().getLongitude());
|
||||
mylocation.setLatitude(nearbyParentFragmentView.getLastMapFocus().getLatitude());
|
||||
mylocation.setLongitude(nearbyParentFragmentView.getLastMapFocus().getLongitude());
|
||||
Float distance = mylocation.distanceTo(dest_location);
|
||||
|
||||
if (distance > 2000.0 * 3 / 4) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public void onMapReady() {
|
||||
if (null != nearbyParentFragmentView) {
|
||||
initializeMapOperations();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,597 @@
|
|||
package fr.free.nrw.commons.nearby.presenter
|
||||
|
||||
import android.location.Location
|
||||
import android.view.View
|
||||
import androidx.annotation.MainThread
|
||||
import androidx.lifecycle.LifecycleCoroutineScope
|
||||
import fr.free.nrw.commons.BaseMarker
|
||||
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao
|
||||
import fr.free.nrw.commons.kvstore.JsonKvStore
|
||||
import fr.free.nrw.commons.location.LatLng
|
||||
import fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType
|
||||
import fr.free.nrw.commons.location.LocationUpdateListener
|
||||
import fr.free.nrw.commons.nearby.CheckBoxTriStates
|
||||
import fr.free.nrw.commons.nearby.Label
|
||||
import fr.free.nrw.commons.nearby.MarkerPlaceGroup
|
||||
import fr.free.nrw.commons.nearby.NearbyController
|
||||
import fr.free.nrw.commons.nearby.Place
|
||||
import fr.free.nrw.commons.nearby.PlacesRepository
|
||||
import fr.free.nrw.commons.nearby.contract.NearbyParentFragmentContract
|
||||
import fr.free.nrw.commons.utils.LocationUtils
|
||||
import fr.free.nrw.commons.wikidata.WikidataConstants.PLACE_OBJECT
|
||||
import fr.free.nrw.commons.wikidata.WikidataEditListener.WikidataP18EditListener
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.ensureActive
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import java.lang.reflect.InvocationHandler
|
||||
import java.lang.reflect.Method
|
||||
import java.lang.reflect.Proxy
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
|
||||
class NearbyParentFragmentPresenter
|
||||
(
|
||||
val bookmarkLocationDao: BookmarkLocationsDao,
|
||||
val placesRepository: PlacesRepository,
|
||||
val nearbyController: NearbyController
|
||||
) :
|
||||
NearbyParentFragmentContract.UserActions,
|
||||
WikidataP18EditListener, LocationUpdateListener {
|
||||
|
||||
private var isNearbyLocked = false
|
||||
private var currentLatLng: LatLng? = null
|
||||
|
||||
private var placesLoadedOnce = false
|
||||
|
||||
private var customQuery: String? = null
|
||||
|
||||
private var nearbyParentFragmentView: NearbyParentFragmentContract.View = DUMMY
|
||||
|
||||
private val clickedPlaces = CopyOnWriteArrayList<Place>()
|
||||
|
||||
/**
|
||||
* used to tell the asynchronous place detail loading job that a pin was clicked
|
||||
* so as to prevent it from turning grey on the next pin detail update
|
||||
*
|
||||
* @param place the place whose details have already been loaded because clicked pin
|
||||
*/
|
||||
fun handlePinClicked(place: Place) {
|
||||
clickedPlaces.add(place)
|
||||
}
|
||||
|
||||
// the currently running job for async loading of pin details, cancelled when new pins are come
|
||||
private var loadPlacesDataAyncJob: Job? = null
|
||||
|
||||
/**
|
||||
* - **batchSize**: number of places to fetch details of in a single request
|
||||
* - **connnectionCount**: number of parallel requests
|
||||
*/
|
||||
private object LoadPlacesAsyncOptions {
|
||||
const val BATCH_SIZE = 3
|
||||
const val CONNECTION_COUNT = 3
|
||||
}
|
||||
|
||||
private var schedulePlacesUpdateJob: Job? = null
|
||||
|
||||
/**
|
||||
* - **skippedCount**: stores the number of updates skipped
|
||||
* - **skipLimit**: maximum number of consecutive updates that can be skipped
|
||||
* - **skipDelayMs**: The delay (in milliseconds) to wait for a new update.
|
||||
*
|
||||
* @see schedulePlacesUpdate
|
||||
*/
|
||||
private object SchedulePlacesUpdateOptions {
|
||||
var skippedCount = 0
|
||||
const val SKIP_LIMIT = 3
|
||||
const val SKIP_DELAY_MS = 500L
|
||||
}
|
||||
|
||||
// used to tell the asynchronous place detail loading job that the places' bookmarked status
|
||||
// changed so as to prevent inconsistencies
|
||||
private var bookmarkChangedPlaces = CopyOnWriteArrayList<Place>()
|
||||
|
||||
/**
|
||||
* Schedules a UI update for the provided list of `MarkerPlaceGroup` objects. Since, the update
|
||||
* is performed on the main thread, it waits for a `SchedulePlacesUpdateOptions.skipDelayMs`
|
||||
* to see if a new update comes, and if one does, it discards the scheduled UI update.
|
||||
*
|
||||
* @param markerPlaceGroups The new list of `MarkerPlaceGroup` objects. If the list is empty, no
|
||||
* update will be performed.
|
||||
*
|
||||
* @see SchedulePlacesUpdateOptions
|
||||
*/
|
||||
private suspend fun schedulePlacesUpdate(markerPlaceGroups: List<MarkerPlaceGroup>) =
|
||||
withContext(Dispatchers.Main) {
|
||||
if (markerPlaceGroups.isEmpty()) return@withContext
|
||||
schedulePlacesUpdateJob?.cancel()
|
||||
schedulePlacesUpdateJob = launch {
|
||||
if (SchedulePlacesUpdateOptions.skippedCount++
|
||||
< SchedulePlacesUpdateOptions.SKIP_LIMIT
|
||||
) {
|
||||
delay(SchedulePlacesUpdateOptions.SKIP_DELAY_MS)
|
||||
}
|
||||
SchedulePlacesUpdateOptions.skippedCount = 0
|
||||
updatePlaceGroupsToControllerAndRender(markerPlaceGroups)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the user action of toggling the bookmarked status of a given place. Updates the
|
||||
* bookmark status in the database, updates the UI to reflect the new state.
|
||||
*
|
||||
* @param place The place whose bookmarked status is to be toggled. If the place is `null`,
|
||||
* the operation is skipped.
|
||||
*/
|
||||
override fun toggleBookmarkedStatus(place: Place?) {
|
||||
if (place == null) return
|
||||
val nowBookmarked = bookmarkLocationDao.updateBookmarkLocation(place)
|
||||
bookmarkChangedPlaces.add(place)
|
||||
val placeIndex =
|
||||
NearbyController.markerLabelList.indexOfFirst { it.place.location == place.location }
|
||||
NearbyController.markerLabelList[placeIndex] = MarkerPlaceGroup(
|
||||
nowBookmarked,
|
||||
NearbyController.markerLabelList[placeIndex].place
|
||||
)
|
||||
nearbyParentFragmentView.setFilterState()
|
||||
}
|
||||
|
||||
override fun attachView(view: NearbyParentFragmentContract.View) {
|
||||
this.nearbyParentFragmentView = view
|
||||
}
|
||||
|
||||
override fun detachView() {
|
||||
this.nearbyParentFragmentView = DUMMY
|
||||
}
|
||||
|
||||
override fun removeNearbyPreferences(applicationKvStore: JsonKvStore) {
|
||||
Timber.d("Remove place objects")
|
||||
applicationKvStore.remove(PLACE_OBJECT)
|
||||
}
|
||||
|
||||
fun initializeMapOperations() {
|
||||
lockUnlockNearby(false)
|
||||
updateMapAndList(LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED)
|
||||
nearbyParentFragmentView.setCheckBoxAction()
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets click listeners of FABs, and 2 bottom sheets
|
||||
*/
|
||||
override fun setActionListeners(applicationKvStore: JsonKvStore?) {
|
||||
nearbyParentFragmentView.setFABPlusAction(View.OnClickListener { v: View? ->
|
||||
if (applicationKvStore != null && applicationKvStore.getBoolean(
|
||||
"login_skipped", false
|
||||
)
|
||||
) {
|
||||
// prompt the user to login
|
||||
nearbyParentFragmentView.displayLoginSkippedWarning()
|
||||
} else {
|
||||
nearbyParentFragmentView.animateFABs()
|
||||
}
|
||||
})
|
||||
|
||||
nearbyParentFragmentView.setFABRecenterAction(View.OnClickListener { v: View? ->
|
||||
nearbyParentFragmentView.recenterMap(currentLatLng)
|
||||
})
|
||||
}
|
||||
|
||||
override fun backButtonClicked(): Boolean {
|
||||
if (nearbyParentFragmentView.isAdvancedQueryFragmentVisible()) {
|
||||
nearbyParentFragmentView.showHideAdvancedQueryFragment(false)
|
||||
return true
|
||||
} else if (nearbyParentFragmentView.isListBottomSheetExpanded()) {
|
||||
// Back should first hide the bottom sheet if it is expanded
|
||||
nearbyParentFragmentView.listOptionMenuItemClicked()
|
||||
return true
|
||||
} else if (nearbyParentFragmentView.isDetailsBottomSheetVisible()) {
|
||||
nearbyParentFragmentView.setBottomSheetDetailsSmaller()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun markerUnselected() {
|
||||
nearbyParentFragmentView.hideBottomSheet()
|
||||
}
|
||||
|
||||
/**
|
||||
* Nearby updates takes time, since they are network operations. During update time, we don't
|
||||
* want to get any other calls from user. So locking nearby.
|
||||
*
|
||||
* @param isNearbyLocked true means lock, false means unlock
|
||||
*/
|
||||
override fun lockUnlockNearby(isNearbyLocked: Boolean) {
|
||||
this.isNearbyLocked = isNearbyLocked
|
||||
if (isNearbyLocked) {
|
||||
nearbyParentFragmentView.disableFABRecenter()
|
||||
} else {
|
||||
nearbyParentFragmentView.enableFABRecenter()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method should be the single point to update Map and List. Triggered by location changes
|
||||
*
|
||||
* @param locationChangeType defines if location changed significantly or slightly
|
||||
*/
|
||||
override fun updateMapAndList(locationChangeType: LocationChangeType?) {
|
||||
Timber.d("Presenter updates map and list")
|
||||
if (isNearbyLocked) {
|
||||
Timber.d("Nearby is locked, so updateMapAndList returns")
|
||||
return
|
||||
}
|
||||
|
||||
if (!nearbyParentFragmentView.isNetworkConnectionEstablished()) {
|
||||
Timber.d("Network connection is not established")
|
||||
return
|
||||
}
|
||||
|
||||
val lastLocation = nearbyParentFragmentView.getLastMapFocus()
|
||||
currentLatLng = if (nearbyParentFragmentView.getMapCenter() != null) {
|
||||
nearbyParentFragmentView.getMapCenter()
|
||||
} else {
|
||||
lastLocation
|
||||
}
|
||||
if (currentLatLng == null) {
|
||||
Timber.d("Skipping update of nearby places as location is unavailable")
|
||||
return
|
||||
}
|
||||
|
||||
/**
|
||||
* Significant changed - Markers and current location will be updated together
|
||||
* Slightly changed - Only current position marker will be updated
|
||||
*/
|
||||
if (locationChangeType == LocationChangeType.CUSTOM_QUERY) {
|
||||
Timber.d("ADVANCED_QUERY_SEARCH")
|
||||
lockUnlockNearby(true)
|
||||
nearbyParentFragmentView.setProgressBarVisibility(true)
|
||||
var updatedLocationByUser = LocationUtils.deriveUpdatedLocationFromSearchQuery(
|
||||
customQuery!!
|
||||
)
|
||||
if (updatedLocationByUser == null) {
|
||||
updatedLocationByUser = lastLocation
|
||||
}
|
||||
nearbyParentFragmentView.populatePlaces(updatedLocationByUser, customQuery)
|
||||
} else if (locationChangeType == LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED
|
||||
|| locationChangeType == LocationChangeType.MAP_UPDATED
|
||||
) {
|
||||
lockUnlockNearby(true)
|
||||
nearbyParentFragmentView.setProgressBarVisibility(true)
|
||||
nearbyParentFragmentView.populatePlaces(nearbyParentFragmentView.getMapCenter())
|
||||
} else if (locationChangeType == LocationChangeType.SEARCH_CUSTOM_AREA) {
|
||||
Timber.d("SEARCH_CUSTOM_AREA")
|
||||
lockUnlockNearby(true)
|
||||
nearbyParentFragmentView.setProgressBarVisibility(true)
|
||||
nearbyParentFragmentView.populatePlaces(nearbyParentFragmentView.getMapFocus())
|
||||
} else { // Means location changed slightly, ie user is walking or driving.
|
||||
Timber.d("Means location changed slightly")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update places on the map, and asynchronously load their details from cache and Wikidata query
|
||||
*
|
||||
* @param nearbyPlaces This variable has the list of placecs
|
||||
* @param scope the lifecycle scope of `nearbyParentFragment`'s `viewLifecycleOwner`
|
||||
*/
|
||||
fun updateMapMarkers(
|
||||
nearbyPlaces: List<Place>?, currentLatLng: LatLng,
|
||||
scope: LifecycleCoroutineScope?
|
||||
) {
|
||||
val nearbyPlaceGroups = nearbyPlaces?.sortedBy { it.getDistanceInDouble(currentLatLng) }
|
||||
?.take(NearbyController.MAX_RESULTS)
|
||||
?.map {
|
||||
// currently only the place's location is known but bookmarks are stored by name
|
||||
MarkerPlaceGroup(
|
||||
false,
|
||||
it
|
||||
)
|
||||
}
|
||||
?: return
|
||||
|
||||
loadPlacesDataAyncJob?.cancel()
|
||||
lockUnlockNearby(false) // So that new location updates wont come
|
||||
nearbyParentFragmentView.setProgressBarVisibility(false)
|
||||
|
||||
updatePlaceGroupsToControllerAndRender(nearbyPlaceGroups)
|
||||
|
||||
loadPlacesDataAyncJob = scope?.launch(Dispatchers.IO) {
|
||||
// clear past clicks and bookmarkChanged queues
|
||||
clickedPlaces.clear()
|
||||
bookmarkChangedPlaces.clear()
|
||||
var clickedPlacesIndex = 0
|
||||
var bookmarkChangedPlacesIndex = 0
|
||||
|
||||
val updatedGroups = nearbyPlaceGroups.toMutableList()
|
||||
// first load cached places:
|
||||
val indicesToUpdate = mutableListOf<Int>()
|
||||
for (i in 0..updatedGroups.lastIndex) {
|
||||
val repoPlace = placesRepository.fetchPlace(updatedGroups[i].place.entityID)
|
||||
if (repoPlace != null && repoPlace.name != ""){
|
||||
updatedGroups[i] = MarkerPlaceGroup(
|
||||
bookmarkLocationDao.findBookmarkLocation(repoPlace),
|
||||
repoPlace
|
||||
)
|
||||
} else {
|
||||
indicesToUpdate.add(i)
|
||||
}
|
||||
}
|
||||
if (indicesToUpdate.size < updatedGroups.size) {
|
||||
schedulePlacesUpdate(updatedGroups)
|
||||
}
|
||||
// channel for lists of indices of places, each list to be fetched in a single request
|
||||
val fetchPlacesChannel = Channel<List<Int>>(Channel.UNLIMITED)
|
||||
var totalBatches = 0
|
||||
for (i in indicesToUpdate.indices step LoadPlacesAsyncOptions.BATCH_SIZE) {
|
||||
++totalBatches
|
||||
fetchPlacesChannel.send(
|
||||
indicesToUpdate.slice(
|
||||
i until (i + LoadPlacesAsyncOptions.BATCH_SIZE).coerceAtMost(
|
||||
indicesToUpdate.size
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
fetchPlacesChannel.close()
|
||||
val collectResults = Channel<List<Pair<Int, MarkerPlaceGroup>>>(totalBatches)
|
||||
repeat(LoadPlacesAsyncOptions.CONNECTION_COUNT) {
|
||||
launch(Dispatchers.IO) {
|
||||
for (indices in fetchPlacesChannel) {
|
||||
ensureActive()
|
||||
try {
|
||||
val fetchedPlaces =
|
||||
nearbyController.getPlaces(indices.map { updatedGroups[it].place })
|
||||
collectResults.send(
|
||||
fetchedPlaces.mapIndexed { index, place ->
|
||||
Pair(indices[index], MarkerPlaceGroup(
|
||||
bookmarkLocationDao.findBookmarkLocation(place),
|
||||
place
|
||||
))
|
||||
}
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Timber.tag("NearbyPinDetails").e(e)
|
||||
collectResults.send(indices.map { Pair(it, updatedGroups[it]) })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var collectCount = 0
|
||||
for (resultList in collectResults) {
|
||||
for ((index, fetchedPlaceGroup) in resultList) {
|
||||
val existingPlace = updatedGroups[index].place
|
||||
val finalPlaceGroup = MarkerPlaceGroup(
|
||||
fetchedPlaceGroup.isBookmarked,
|
||||
fetchedPlaceGroup.place.apply {
|
||||
location = existingPlace.location
|
||||
distance = existingPlace.distance
|
||||
isMonument = existingPlace.isMonument
|
||||
}
|
||||
)
|
||||
updatedGroups[index] = finalPlaceGroup
|
||||
placesRepository
|
||||
.save(finalPlaceGroup.place)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe()
|
||||
}
|
||||
// handle any places clicked
|
||||
if (clickedPlacesIndex < clickedPlaces.size) {
|
||||
val clickedPlacesBacklog = hashMapOf<LatLng, Place>()
|
||||
while (clickedPlacesIndex < clickedPlaces.size) {
|
||||
clickedPlacesBacklog.put(
|
||||
clickedPlaces[clickedPlacesIndex].location,
|
||||
clickedPlaces[clickedPlacesIndex]
|
||||
)
|
||||
++clickedPlacesIndex
|
||||
}
|
||||
for ((index, group) in updatedGroups.withIndex()) {
|
||||
if (clickedPlacesBacklog.containsKey(group.place.location)) {
|
||||
updatedGroups[index] = MarkerPlaceGroup(
|
||||
updatedGroups[index].isBookmarked,
|
||||
clickedPlacesBacklog[group.place.location]
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
// handle any bookmarks toggled
|
||||
if (bookmarkChangedPlacesIndex < bookmarkChangedPlaces.size) {
|
||||
val bookmarkChangedPlacesBacklog = hashMapOf<LatLng, Place>()
|
||||
while (bookmarkChangedPlacesIndex < bookmarkChangedPlaces.size) {
|
||||
bookmarkChangedPlacesBacklog.put(
|
||||
bookmarkChangedPlaces[bookmarkChangedPlacesIndex].location,
|
||||
bookmarkChangedPlaces[bookmarkChangedPlacesIndex]
|
||||
)
|
||||
++bookmarkChangedPlacesIndex
|
||||
}
|
||||
for ((index, group) in updatedGroups.withIndex()) {
|
||||
if (bookmarkChangedPlacesBacklog.containsKey(group.place.location)) {
|
||||
updatedGroups[index] = MarkerPlaceGroup(
|
||||
bookmarkLocationDao
|
||||
.findBookmarkLocation(updatedGroups[index].place),
|
||||
updatedGroups[index].place
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
schedulePlacesUpdate(updatedGroups)
|
||||
if (++collectCount == totalBatches) {
|
||||
break
|
||||
}
|
||||
}
|
||||
collectResults.close()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Some centering task may need to wait for map to be ready, if they are requested before map is
|
||||
* ready. So we will remember it when the map is ready
|
||||
*/
|
||||
private fun handleCenteringTaskIfAny() {
|
||||
if (!placesLoadedOnce) {
|
||||
placesLoadedOnce = true
|
||||
nearbyParentFragmentView.centerMapToPlace(null)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onWikidataEditSuccessful() {
|
||||
updateMapAndList(LocationChangeType.MAP_UPDATED)
|
||||
}
|
||||
|
||||
override fun onLocationChangedSignificantly(latLng: LatLng) {
|
||||
Timber.d("Location significantly changed")
|
||||
updateMapAndList(LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED)
|
||||
}
|
||||
|
||||
override fun onLocationChangedSlightly(latLng: LatLng) {
|
||||
Timber.d("Location significantly changed")
|
||||
updateMapAndList(LocationChangeType.LOCATION_SLIGHTLY_CHANGED)
|
||||
}
|
||||
|
||||
override fun onLocationChangedMedium(latLng: LatLng) {
|
||||
Timber.d("Location changed medium")
|
||||
}
|
||||
|
||||
override fun filterByMarkerType(
|
||||
selectedLabels: List<Label?>?, state: Int,
|
||||
filterForPlaceState: Boolean, filterForAllNoneType: Boolean
|
||||
) {
|
||||
if (filterForAllNoneType) { // Means we will set labels based on states
|
||||
when (state) {
|
||||
CheckBoxTriStates.UNKNOWN -> {}
|
||||
CheckBoxTriStates.UNCHECKED -> {
|
||||
//TODO
|
||||
nearbyParentFragmentView.filterOutAllMarkers()
|
||||
nearbyParentFragmentView.setRecyclerViewAdapterItemsGreyedOut()
|
||||
}
|
||||
|
||||
CheckBoxTriStates.CHECKED -> {
|
||||
// Despite showing all labels NearbyFilterState still should be applied
|
||||
nearbyParentFragmentView.filterMarkersByLabels(
|
||||
selectedLabels,
|
||||
filterForPlaceState, false
|
||||
)
|
||||
nearbyParentFragmentView.setRecyclerViewAdapterAllSelected()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
nearbyParentFragmentView.filterMarkersByLabels(
|
||||
selectedLabels,
|
||||
filterForPlaceState, false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@MainThread
|
||||
override fun updateMapMarkersToController(baseMarkers: MutableList<BaseMarker>) {
|
||||
NearbyController.markerLabelList.clear()
|
||||
for (i in baseMarkers.indices) {
|
||||
val nearbyBaseMarker = baseMarkers[i]
|
||||
NearbyController.markerLabelList.add(
|
||||
MarkerPlaceGroup(
|
||||
bookmarkLocationDao.findBookmarkLocation(nearbyBaseMarker.place),
|
||||
nearbyBaseMarker.place
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the supplied markerPlaceGroups to `NearbyController` and nearby list fragment,
|
||||
* and tells nearby parent fragment to filter the updated values to be rendered as overlays
|
||||
* on the map
|
||||
*
|
||||
* @param markerPlaceGroups the new/updated list of places along with their bookmarked status
|
||||
*/
|
||||
@MainThread
|
||||
private fun updatePlaceGroupsToControllerAndRender(markerPlaceGroups: List<MarkerPlaceGroup>) {
|
||||
NearbyController.markerLabelList.clear()
|
||||
NearbyController.markerLabelList.addAll(markerPlaceGroups)
|
||||
nearbyParentFragmentView.setFilterState()
|
||||
nearbyParentFragmentView.updateListFragment(markerPlaceGroups.map { it.place })
|
||||
}
|
||||
|
||||
override fun setCheckboxUnknown() {
|
||||
nearbyParentFragmentView.setCheckBoxState(CheckBoxTriStates.UNKNOWN)
|
||||
}
|
||||
|
||||
override fun setAdvancedQuery(query: String) {
|
||||
this.customQuery = query
|
||||
}
|
||||
|
||||
override fun searchViewGainedFocus() {
|
||||
if (nearbyParentFragmentView.isListBottomSheetExpanded()) {
|
||||
// Back should first hide the bottom sheet if it is expanded
|
||||
nearbyParentFragmentView.hideBottomSheet()
|
||||
} else if (nearbyParentFragmentView.isDetailsBottomSheetVisible()) {
|
||||
nearbyParentFragmentView.hideBottomDetailsSheet()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates a search for places within the area. Depending on whether the search
|
||||
* is close to the current location, the map and list are updated
|
||||
* accordingly.
|
||||
*/
|
||||
fun searchInTheArea() {
|
||||
if (searchCloseToCurrentLocation()) {
|
||||
updateMapAndList(LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED)
|
||||
} else {
|
||||
updateMapAndList(LocationChangeType.SEARCH_CUSTOM_AREA)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if search this area button is used around our current location, so that we can
|
||||
* continue following our current location again
|
||||
*
|
||||
* @return Returns true if search this area button is used around our current location
|
||||
*/
|
||||
fun searchCloseToCurrentLocation(): Boolean {
|
||||
if (null == nearbyParentFragmentView.getLastMapFocus()) {
|
||||
return true
|
||||
}
|
||||
//TODO
|
||||
val myLocation = Location("")
|
||||
val destLocation = Location("")
|
||||
destLocation.latitude = nearbyParentFragmentView.getMapFocus().latitude
|
||||
destLocation.longitude = nearbyParentFragmentView.getMapFocus().longitude
|
||||
myLocation.latitude = nearbyParentFragmentView.getLastMapFocus().latitude
|
||||
myLocation.longitude = nearbyParentFragmentView.getLastMapFocus().longitude
|
||||
val distance = myLocation.distanceTo(destLocation)
|
||||
|
||||
return (distance <= 2000.0 * 3 / 4)
|
||||
}
|
||||
|
||||
fun onMapReady() {
|
||||
initializeMapOperations()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val DUMMY = Proxy.newProxyInstance(
|
||||
NearbyParentFragmentContract.View::class.java.getClassLoader(),
|
||||
arrayOf<Class<*>>(NearbyParentFragmentContract.View::class.java),
|
||||
InvocationHandler { proxy: Any?, method: Method?, args: Array<Any?>? ->
|
||||
if (method!!.name == "onMyEvent") {
|
||||
return@InvocationHandler null
|
||||
} else if (String::class.java == method.returnType) {
|
||||
return@InvocationHandler ""
|
||||
} else if (Int::class.java == method.returnType) {
|
||||
return@InvocationHandler 0
|
||||
} else if (Int::class.javaPrimitiveType == method.returnType) {
|
||||
return@InvocationHandler 0
|
||||
} else if (Boolean::class.java == method.returnType) {
|
||||
return@InvocationHandler java.lang.Boolean.FALSE
|
||||
} else if (Boolean::class.javaPrimitiveType == method.returnType) {
|
||||
return@InvocationHandler false
|
||||
} else {
|
||||
return@InvocationHandler null
|
||||
}
|
||||
}
|
||||
) as NearbyParentFragmentContract.View
|
||||
}
|
||||
}
|
||||
|
|
@ -31,6 +31,12 @@ class NearbyParentFragmentPresenterTest {
|
|||
@Mock
|
||||
internal lateinit var bookmarkLocationsDao: BookmarkLocationsDao
|
||||
|
||||
@Mock
|
||||
internal lateinit var placesRepository: PlacesRepository
|
||||
|
||||
@Mock
|
||||
internal lateinit var nearbyController: NearbyController
|
||||
|
||||
@Mock
|
||||
internal lateinit var latestLocation: LatLng
|
||||
|
||||
|
|
@ -52,7 +58,8 @@ class NearbyParentFragmentPresenterTest {
|
|||
@Throws(Exception::class)
|
||||
fun setUp() {
|
||||
MockitoAnnotations.openMocks(this)
|
||||
nearbyPresenter = NearbyParentFragmentPresenter(bookmarkLocationsDao)
|
||||
nearbyPresenter =
|
||||
NearbyParentFragmentPresenter(bookmarkLocationsDao, placesRepository, nearbyController)
|
||||
nearbyPresenter.attachView(nearbyParentFragmentView)
|
||||
}
|
||||
|
||||
|
|
@ -322,7 +329,7 @@ class NearbyParentFragmentPresenterTest {
|
|||
|
||||
@Test
|
||||
fun testSetActionListeners() {
|
||||
nearbyPresenter.setActionListeners(any())
|
||||
nearbyPresenter.setActionListeners(null)
|
||||
verify(nearbyParentFragmentView).setFABPlusAction(any())
|
||||
verify(nearbyParentFragmentView).setFABRecenterAction(any())
|
||||
}
|
||||
|
|
@ -454,11 +461,11 @@ class NearbyParentFragmentPresenterTest {
|
|||
nearbyPlacesInfo.boundaryCoordinates = arrayOf()
|
||||
nearbyPlacesInfo.currentLatLng = latestLocation
|
||||
nearbyPlacesInfo.searchLatLng = latestLocation
|
||||
nearbyPlacesInfo.placeList = null
|
||||
nearbyPlacesInfo.placeList = emptyList<Place>()
|
||||
|
||||
whenever(bookmarkLocationsDao.allBookmarksLocations).thenReturn(Collections.emptyList())
|
||||
nearbyPresenter.updateMapMarkers(nearbyPlacesInfo.placeList, latestLocation, true)
|
||||
Mockito.verify(nearbyParentFragmentView).updateMapMarkers(any())
|
||||
nearbyPresenter.updateMapMarkers(nearbyPlacesInfo.placeList, latestLocation, null)
|
||||
Mockito.verify(nearbyParentFragmentView).setFilterState()
|
||||
Mockito.verify(nearbyParentFragmentView).setProgressBarVisibility(false)
|
||||
Mockito.verify(nearbyParentFragmentView).updateListFragment(nearbyPlacesInfo.placeList)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue