Added advanced query support in Nearby (#4714)

* Added feature for advanced query options in Nearby

* Fix unit-tests coverage

* wip-tests

* Added log to identify if the nearby request is via an advanced query

* Rolledback test-dependency updates

* Fix tests

* build fix

* Added basic tests for relevant method in OkHttpJsonApiClient & NearbyController

* Added NearbyParentFragmentPresenter Tests

* Added AdvancedQueryFragment Unit Tests

* Added more tests for NearbyParentFragmentPresenter

* Reset ContributionsFragment with Upstream

* Overload method loadNearbyPlaces for CustomQuery

* Added more tests

* Added more tests

* Fixed tests for NearbyParentFragmentPresenter
This commit is contained in:
Ashish 2021-12-13 21:11:33 +05:30 committed by GitHub
parent bd00ce2071
commit a0ff15cdc2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 823 additions and 47 deletions

View file

@ -268,6 +268,7 @@ public class LocationServiceManager implements LocationListener {
LOCATION_NOT_CHANGED,
PERMISSION_JUST_GRANTED,
MAP_UPDATED,
SEARCH_CUSTOM_AREA
SEARCH_CUSTOM_AREA,
CUSTOM_QUERY
}
}

View file

@ -7,6 +7,7 @@ import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.gson.Gson;
import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.campaigns.CampaignResponseDTO;
import fr.free.nrw.commons.explore.depictions.DepictsClient;
import fr.free.nrw.commons.location.LatLng;
@ -265,14 +266,27 @@ public class OkHttpJsonApiClient {
});
}
/**
* Make API Call to get Nearby Places
*
* @param cur Search lat long
* @param language Language
* @param radius Search Radius
* @param shouldQueryForMonuments : Should we query for monuments
* @return
* @throws Exception
*/
@Nullable
public List<Place> getNearbyPlaces(final LatLng cur, final String language, final double radius,
final boolean shouldQueryForMonuments)
final boolean shouldQueryForMonuments, final String customQuery)
throws Exception {
Timber.d("Fetching nearby items at radius %s", radius);
Timber.d("CUSTOM_SPARQL%s", String.valueOf(customQuery != null));
final String wikidataQuery;
if (!shouldQueryForMonuments) {
if (customQuery != null) {
wikidataQuery = customQuery;
} else if (!shouldQueryForMonuments) {
wikidataQuery = FileUtils.readFromResource("/queries/nearby_query.rq");
} else {
wikidataQuery = FileUtils.readFromResource("/queries/nearby_query_monuments.rq");
@ -313,6 +327,23 @@ public class OkHttpJsonApiClient {
throw new Exception(response.message());
}
/**
* Make API Call to get Nearby Places Implementation does not expects a custom query
*
* @param cur Search lat long
* @param language Language
* @param radius Search Radius
* @param shouldQueryForMonuments : Should we query for monuments
* @return
* @throws Exception
*/
@Nullable
public List<Place> getNearbyPlaces(final LatLng cur, final String language, final double radius,
final boolean shouldQueryForMonuments)
throws Exception {
return getNearbyPlaces(cur, language, radius, shouldQueryForMonuments, null);
}
/**
* Get the QIDs of all Wikidata items that are subclasses of the given Wikidata item. Example:
* bridge -> suspended bridge, aqueduct, etc

View file

@ -5,6 +5,7 @@ import android.content.res.Resources;
import android.graphics.Bitmap;
import androidx.annotation.MainThread;
import androidx.annotation.Nullable;
import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;
import com.mapbox.mapboxsdk.annotations.IconFactory;
@ -54,12 +55,13 @@ public class NearbyController {
* @param curLatLng current location for user
* @param searchLatLng the location user wants to search around
* @param returnClosestResult if this search is done to find closest result or all results
* @param customQuery if this search is done via an advanced query
* @return NearbyPlacesInfo a variable holds Place list without distance information
* and boundary coordinates of current Place List
*/
public NearbyPlacesInfo loadAttractionsFromLocation(final LatLng curLatLng, final LatLng searchLatLng,
final boolean returnClosestResult, final boolean checkingAroundCurrentLocation,
final boolean shouldQueryForMonuments) throws Exception {
final boolean shouldQueryForMonuments, @Nullable final String customQuery) throws Exception {
Timber.d("Loading attractions near %s", searchLatLng);
NearbyPlacesInfo nearbyPlacesInfo = new NearbyPlacesInfo();
@ -70,7 +72,7 @@ public class NearbyController {
}
List<Place> places = nearbyPlaces
.radiusExpander(searchLatLng, Locale.getDefault().getLanguage(), returnClosestResult,
shouldQueryForMonuments);
shouldQueryForMonuments, customQuery);
if (null != places && places.size() > 0) {
LatLng[] boundaryCoordinates = {places.get(0).location, // south
@ -128,10 +130,27 @@ public class NearbyController {
return nearbyPlacesInfo;
}
else {
return null;
return nearbyPlacesInfo;
}
}
/**
* Prepares Place list to make their distance information update later.
*
* @param curLatLng current location for user
* @param searchLatLng the location user wants to search around
* @param returnClosestResult if this search is done to find closest result or all results
* @return NearbyPlacesInfo a variable holds Place list without distance information and
* boundary coordinates of current Place List
*/
public NearbyPlacesInfo loadAttractionsFromLocation(final LatLng curLatLng,
final LatLng searchLatLng,
final boolean returnClosestResult, final boolean checkingAroundCurrentLocation,
final boolean shouldQueryForMonuments) throws Exception {
return loadAttractionsFromLocation(curLatLng, searchLatLng, returnClosestResult,
checkingAroundCurrentLocation, shouldQueryForMonuments, null);
}
/**
* Loads attractions from location for list view, we need to return Place data type.
*

View file

@ -1,5 +1,6 @@
package fr.free.nrw.commons.nearby;
import androidx.annotation.Nullable;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
@ -38,10 +39,12 @@ public class NearbyPlaces {
* @param curLatLng coordinates of search location
* @param lang user's language
* @param returnClosestResult true if only the nearest point is desired
* @param customQuery
* @return list of places obtained
*/
List<Place> radiusExpander(final LatLng curLatLng, final String lang, final boolean returnClosestResult
, final boolean shouldQueryForMonuments) throws Exception {
List<Place> radiusExpander(final LatLng curLatLng, final String lang,
final boolean returnClosestResult
, final boolean shouldQueryForMonuments, @Nullable final String customQuery) throws Exception {
final int minResults;
final double maxRadius;
@ -62,13 +65,12 @@ public class NearbyPlaces {
// Increase the radius gradually to find a satisfactory number of nearby places
while (radius <= maxRadius) {
places = getFromWikidataQuery(curLatLng, lang, radius, shouldQueryForMonuments);
places = getFromWikidataQuery(curLatLng, lang, radius, shouldQueryForMonuments, customQuery);
Timber.d("%d results at radius: %f", places.size(), radius);
if (places.size() >= minResults) {
break;
} else {
radius *= RADIUS_MULTIPLIER;
}
radius *= RADIUS_MULTIPLIER;
}
// make sure we will be able to send at least one request next time
if (radius > maxRadius) {
@ -83,11 +85,14 @@ public class NearbyPlaces {
* @param lang user's language
* @param radius radius for search, as determined by radiusExpander()
* @param shouldQueryForMonuments should the query include properites for monuments
* @param customQuery
* @return list of places obtained
* @throws IOException if query fails
*/
public List<Place> getFromWikidataQuery(final LatLng cur, final String lang,
final double radius, final boolean shouldQueryForMonuments) throws Exception {
return okHttpJsonApiClient.getNearbyPlaces(cur, lang, radius, shouldQueryForMonuments);
final double radius, final boolean shouldQueryForMonuments,
@Nullable final String customQuery) throws Exception {
return okHttpJsonApiClient
.getNearbyPlaces(cur, lang, radius, shouldQueryForMonuments, customQuery);
}
}

View file

@ -2,13 +2,14 @@ package fr.free.nrw.commons.nearby.contract;
import android.content.Context;
import androidx.annotation.Nullable;
import com.mapbox.mapboxsdk.annotations.Marker;
import fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType;
import java.util.List;
import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.location.LatLng;
import fr.free.nrw.commons.location.LocationServiceManager;
import fr.free.nrw.commons.nearby.Label;
import fr.free.nrw.commons.nearby.NearbyBaseMarker;
import fr.free.nrw.commons.nearby.Place;
@ -19,6 +20,7 @@ public interface NearbyParentFragmentContract {
boolean isNetworkConnectionEstablished();
void listOptionMenuItemClicked();
void populatePlaces(LatLng curlatLng);
void populatePlaces(LatLng curlatLng, String customQuery);
boolean isListBottomSheetExpanded();
void checkPermissionsAndPerformAction();
void displayLoginSkippedWarning();
@ -62,7 +64,7 @@ public interface NearbyParentFragmentContract {
LatLng getCameraTarget();
void centerMapToPlace(Place placeToCenter);
void centerMapToPlace(@Nullable Place placeToCenter);
void updateListFragment(List<Place> placeList);
@ -72,6 +74,12 @@ public interface NearbyParentFragmentContract {
boolean isCurrentLocationMarkerVisible();
void setProjectorLatLngBounds();
boolean isAdvancedQueryFragmentVisible();
void showHideAdvancedQueryFragment(boolean shouldShow);
void centerMapToPosition(@Nullable LatLng searchLatLng);
}
interface NearbyListView {
@ -79,7 +87,7 @@ public interface NearbyParentFragmentContract {
}
interface UserActions {
void updateMapAndList(LocationServiceManager.LocationChangeType locationChangeType);
void updateMapAndList(LocationChangeType locationChangeType);
void lockUnlockNearby(boolean isNearbyLocked);
void attachView(View view);
@ -96,5 +104,7 @@ public interface NearbyParentFragmentContract {
void searchViewGainedFocus();
void setCheckboxUnknown();
void setAdvancedQuery(String query);
}
}

View file

@ -0,0 +1,70 @@
package fr.free.nrw.commons.nearby.fragments
import android.content.Context.INPUT_METHOD_SERVICE
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.InputMethodManager
import androidx.appcompat.widget.AppCompatButton
import androidx.appcompat.widget.AppCompatEditText
import androidx.fragment.app.Fragment
import fr.free.nrw.commons.R
import kotlinx.android.synthetic.main.fragment_advance_query.*
class AdvanceQueryFragment : Fragment() {
lateinit var originalQuery: String
lateinit var callback: Callback
lateinit var etQuery: AppCompatEditText
lateinit var btnApply: AppCompatButton
lateinit var btnReset: AppCompatButton
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_advance_query, container, false)
originalQuery = arguments?.getString("query")!!
setHasOptionsMenu(false)
return view
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
etQuery = view.findViewById(R.id.et_query)
btnApply = view.findViewById(R.id.btn_apply)
btnReset = view.findViewById(R.id.btn_reset)
etQuery.setText(originalQuery)
btnReset.setOnClickListener {
btnReset.post {
etQuery.setText(originalQuery)
etQuery.clearFocus()
hideKeyBoard()
callback.reset()
}
}
btnApply.setOnClickListener {
btnApply.post {
etQuery.clearFocus()
hideKeyBoard()
callback.apply(etQuery.text.toString())
callback.close()
}
}
}
fun hideKeyBoard() {
val inputMethodManager =
context?.getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager?
inputMethodManager?.hideSoftInputFromWindow(view?.windowToken, 0)
}
interface Callback {
fun reset()
fun apply(query: String)
fun close()
}
}

View file

@ -1,5 +1,6 @@
package fr.free.nrw.commons.nearby.fragments;
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;
@ -34,6 +35,7 @@ import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
@ -44,6 +46,7 @@ import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.AppCompatButton;
import androidx.appcompat.widget.AppCompatImageView;
import androidx.appcompat.widget.SearchView;
import androidx.appcompat.widget.AppCompatTextView;
@ -100,7 +103,9 @@ import fr.free.nrw.commons.nearby.NearbyFilterState;
import fr.free.nrw.commons.nearby.NearbyMarker;
import fr.free.nrw.commons.nearby.Place;
import fr.free.nrw.commons.nearby.contract.NearbyParentFragmentContract;
import fr.free.nrw.commons.nearby.fragments.AdvanceQueryFragment.Callback;
import fr.free.nrw.commons.nearby.presenter.NearbyParentFragmentPresenter;
import fr.free.nrw.commons.upload.FileUtils;
import fr.free.nrw.commons.utils.DialogUtil;
import fr.free.nrw.commons.utils.ExecutorUtils;
import fr.free.nrw.commons.utils.LayoutUtils;
@ -114,14 +119,12 @@ import fr.free.nrw.commons.utils.ViewUtil;
import fr.free.nrw.commons.wikidata.WikidataEditListener;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.functions.Function;
import io.reactivex.schedulers.Schedulers;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.inject.Named;
@ -180,6 +183,10 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
AppCompatImageView ivToggleChips;
@BindView(R.id.chip_view)
View llContainerChips;
@BindView(R.id.btn_advanced_options)
AppCompatButton btnAdvancedOptions;
@BindView(R.id.fl_container_nearby_children)
FrameLayout flConainerNearbyChildren;
@Inject LocationServiceManager locationManager;
@Inject NearbyController nearbyController;
@ -233,6 +240,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
private LatLngBounds latLngBounds;
private PlaceAdapter adapter;
private NearbyParentFragmentInstanceReadyCallback nearbyParentFragmentInstanceReadyCallback;
private boolean isAdvancedQueryFragmentVisible = false;
/**
* Holds filtered markers that are to be shown
@ -342,6 +350,42 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
tvAttribution.setText(Html.fromHtml(getString(R.string.map_attribution)));
tvAttribution.setMovementMethod(LinkMovementMethod.getInstance());
btnAdvancedOptions.setOnClickListener(v -> {
searchView.clearFocus();
showHideAdvancedQueryFragment(true);
final AdvanceQueryFragment fragment = new AdvanceQueryFragment();
final Bundle bundle=new Bundle();
try {
bundle.putString("query", FileUtils.readFromResource("/queries/nearby_query.rq"));
} catch (IOException e) {
Timber.e(e);
}
fragment.setArguments(bundle);
fragment.callback = new Callback() {
@Override
public void close() {
showHideAdvancedQueryFragment(false);
}
@Override
public void reset() {
presenter.setAdvancedQuery(null);
presenter.updateMapAndList(LOCATION_SIGNIFICANTLY_CHANGED);
showHideAdvancedQueryFragment(false);
}
@Override
public void apply(@NotNull final String query) {
presenter.setAdvancedQuery(query);
presenter.updateMapAndList(CUSTOM_QUERY);
showHideAdvancedQueryFragment(false);
}
};
getChildFragmentManager().beginTransaction()
.replace(R.id.fl_container_nearby_children, fragment)
.commit();
});
}
/**
@ -600,7 +644,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
});
nearbyFilterList.getLayoutParams().width = (int) LayoutUtils.getScreenWidth(getActivity(), 0.75);
recyclerView.setAdapter(nearbyFilterSearchRecyclerViewAdapter);
LayoutUtils.setLayoutHeightAllignedToWidth(1, nearbyFilterList);
LayoutUtils.setLayoutHeightAllignedToWidth(1.25, nearbyFilterList);
compositeDisposable.add(RxSearchView.queryTextChanges(searchView)
.takeUntil(RxView.detaches(searchView))
@ -821,7 +865,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
* @param place is new center of the map
*/
@Override
public void centerMapToPlace(final Place place) {
public void centerMapToPlace(@Nullable final Place place) {
Timber.d("Map is centered to place");
final double cameraShift;
if(null!=place){
@ -846,6 +890,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
}
}
@Override
public void updateListFragment(final List<Place> placeList) {
places = placeList;
@ -879,6 +924,32 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
latLngBounds = mapBox.getProjection().getVisibleRegion().latLngBounds;
}
@Override
public boolean isAdvancedQueryFragmentVisible() {
return isAdvancedQueryFragmentVisible;
}
@Override
public void showHideAdvancedQueryFragment(final boolean shouldShow) {
setHasOptionsMenu(!shouldShow);
flConainerNearbyChildren.setVisibility(shouldShow ? View.VISIBLE : View.GONE);
isAdvancedQueryFragmentVisible = shouldShow;
}
@Override
public void centerMapToPosition(fr.free.nrw.commons.location.LatLng searchLatLng) {
final CameraPosition cameraPosition = mapBox.getCameraPosition();
if (null != searchLatLng && !(
cameraPosition.target.getLatitude() == searchLatLng.getLatitude()
&& cameraPosition.target.getLongitude() == searchLatLng.getLongitude())) {
final CameraPosition position = new CameraPosition.Builder()
.target(LocationUtils.commonsLatLngToMapBoxLatLng(searchLatLng))
.zoom(ZOOM_LEVEL) // Same zoom level
.build();
mapBox.setCameraPosition(position);
}
}
@Override
public boolean isNetworkConnectionEstablished() {
return NetworkUtils.isInternetConnectionEstablished(getActivity());
@ -933,29 +1004,53 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
@Override
public void populatePlaces(final fr.free.nrw.commons.location.LatLng curlatLng) {
if (curlatLng.equals(lastFocusLocation) || lastFocusLocation == null || recenterToUserLocation) { // Means we are checking around current location
populatePlacesForCurrentLocation(lastKnownLocation, curlatLng);
populatePlacesForCurrentLocation(lastKnownLocation, curlatLng, null);
} else {
populatePlacesForAnotherLocation(lastKnownLocation, curlatLng);
populatePlacesForAnotherLocation(lastKnownLocation, curlatLng, null);
}
if(recenterToUserLocation) {
recenterToUserLocation = false;
}
}
private void populatePlacesForCurrentLocation(final fr.free.nrw.commons.location.LatLng curlatLng,
final fr.free.nrw.commons.location.LatLng searchLatLng){
@Override
public void populatePlaces(final fr.free.nrw.commons.location.LatLng curlatLng,
@Nullable final String customQuery) {
if (customQuery == null || customQuery.isEmpty()) {
populatePlaces(curlatLng);
return;
}
if (curlatLng.equals(lastFocusLocation) || lastFocusLocation == null
|| recenterToUserLocation) { // Means we are checking around current location
populatePlacesForCurrentLocation(lastKnownLocation, curlatLng, customQuery);
} else {
populatePlacesForAnotherLocation(lastKnownLocation, curlatLng, customQuery);
}
if (recenterToUserLocation) {
recenterToUserLocation = false;
}
}
private void populatePlacesForCurrentLocation(
final fr.free.nrw.commons.location.LatLng curlatLng,
final fr.free.nrw.commons.location.LatLng searchLatLng, @Nullable final String customQuery){
final Observable<NearbyPlacesInfo> nearbyPlacesInfoObservable = Observable
.fromCallable(() -> nearbyController
.loadAttractionsFromLocation(curlatLng, searchLatLng,
false, true, Utils.isMonumentsEnabled(new Date())));
false, true, Utils.isMonumentsEnabled(new Date()), customQuery));
compositeDisposable.add(nearbyPlacesInfoObservable
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(nearbyPlacesInfo -> {
updateMapMarkers(nearbyPlacesInfo, true);
lastFocusLocation=searchLatLng;
if (nearbyPlacesInfo.placeList == null || nearbyPlacesInfo.placeList.isEmpty()) {
showErrorMessage(getString(R.string.no_nearby_places_around));
} else {
updateMapMarkers(nearbyPlacesInfo, true);
lastFocusLocation = searchLatLng;
}
},
throwable -> {
Timber.d(throwable);
@ -966,20 +1061,24 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
}));
}
private void populatePlacesForAnotherLocation(final fr.free.nrw.commons.location.LatLng curlatLng,
final fr.free.nrw.commons.location.LatLng searchLatLng){
private void populatePlacesForAnotherLocation(
final fr.free.nrw.commons.location.LatLng curlatLng,
final fr.free.nrw.commons.location.LatLng searchLatLng, @Nullable final String customQuery){
final Observable<NearbyPlacesInfo> nearbyPlacesInfoObservable = Observable
.fromCallable(() -> nearbyController
.loadAttractionsFromLocation(curlatLng, searchLatLng,
false, true, Utils.isMonumentsEnabled(new Date())));
false, true, Utils.isMonumentsEnabled(new Date()), customQuery));
compositeDisposable.add(nearbyPlacesInfoObservable
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(nearbyPlacesInfo -> {
updateMapMarkers(nearbyPlacesInfo, false);
lastFocusLocation=searchLatLng;
if (nearbyPlacesInfo.placeList == null || nearbyPlacesInfo.placeList.isEmpty()) {
showErrorMessage(getString(R.string.no_nearby_places_around));
} else {
updateMapMarkers(nearbyPlacesInfo, false);
lastFocusLocation = searchLatLng;
}
},
throwable -> {
Timber.e(throwable);
@ -1617,10 +1716,6 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
if (!fabPlus.isShown()) {
showFABs();
}
getView().requestFocus();
break;
case (BottomSheetBehavior.STATE_EXPANDED):
getView().requestFocus();
break;
case (BottomSheetBehavior.STATE_HIDDEN):
if (null != mapBox) {
@ -1630,9 +1725,6 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
transparentView.setAlpha(0);
collapseFABs(isFABsExpanded);
hideFABs();
if (getView() != null) {
getView().requestFocus();
}
break;
}
}

View file

@ -3,8 +3,10 @@ package fr.free.nrw.commons.nearby.presenter;
import android.view.View;
import androidx.annotation.MainThread;
import androidx.annotation.Nullable;
import com.mapbox.mapboxsdk.annotations.Marker;
import fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.List;
@ -12,7 +14,6 @@ import java.util.List;
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;
import fr.free.nrw.commons.location.LocationUpdateListener;
import fr.free.nrw.commons.nearby.CheckBoxTriStates;
import fr.free.nrw.commons.nearby.Label;
@ -25,6 +26,7 @@ import fr.free.nrw.commons.utils.LocationUtils;
import fr.free.nrw.commons.wikidata.WikidataEditListener;
import timber.log.Timber;
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;
@ -46,6 +48,8 @@ public class NearbyParentFragmentPresenter
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) -> {
@ -118,7 +122,11 @@ public class NearbyParentFragmentPresenter
@Override
public boolean backButtonClicked() {
if(nearbyParentFragmentView.isListBottomSheetExpanded()) {
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;
@ -160,7 +168,7 @@ public class NearbyParentFragmentPresenter
* @param locationChangeType defines if location changed significantly or slightly
*/
@Override
public void updateMapAndList(LocationServiceManager.LocationChangeType locationChangeType) {
public void updateMapAndList(LocationChangeType locationChangeType) {
Timber.d("Presenter updates map and list");
if (isNearbyLocked) {
Timber.d("Nearby is locked, so updateMapAndList returns");
@ -184,7 +192,17 @@ public class NearbyParentFragmentPresenter
* Significant changed - Markers and current location will be updated together
* Slightly changed - Only current position marker will be updated
*/
if (locationChangeType.equals(LOCATION_SIGNIFICANTLY_CHANGED)
if(locationChangeType.equals(CUSTOM_QUERY)){
Timber.d("ADVANCED_QUERY_SEARCH");
lockUnlockNearby(true);
nearbyParentFragmentView.setProgressBarVisibility(true);
LatLng updatedLocationByUser = deriveUpdatedLocationFromSearchQuery(customQuery);
if (updatedLocationByUser == null) {
updatedLocationByUser = lastLocation;
}
nearbyParentFragmentView.populatePlaces(updatedLocationByUser, customQuery);
}
else if (locationChangeType.equals(LOCATION_SIGNIFICANTLY_CHANGED)
|| locationChangeType.equals(MAP_UPDATED)) {
Timber.d("LOCATION_SIGNIFICANTLY_CHANGED");
lockUnlockNearby(true);
@ -204,6 +222,38 @@ public class NearbyParentFragmentPresenter
}
}
private LatLng deriveUpdatedLocationFromSearchQuery(String customQuery) {
LatLng latLng = null;
final int indexOfPrefix = customQuery.indexOf("Point(");
if (indexOfPrefix == -1) {
Timber.e("Invalid prefix index - Seems like user has entered an invalid query");
return latLng;
}
final int indexOfSuffix = customQuery.indexOf(")\"", indexOfPrefix);
if (indexOfSuffix == -1) {
Timber.e("Invalid suffix index - Seems like user has entered an invalid query");
return latLng;
}
String latLngString = customQuery.substring(indexOfPrefix+"Point(".length(), indexOfSuffix);
if (latLngString.isEmpty()) {
return null;
}
String latLngArray[] = latLngString.split(" ");
if (latLngArray.length != 2) {
return null;
}
try {
latLng = new LatLng(Double.parseDouble(latLngArray[1].trim()),
Double.parseDouble(latLngArray[0].trim()), 1f);
}catch (Exception e){
Timber.e("Error while parsing user entered lat long: %s", e);
}
return latLng;
}
/**
* Populates places for custom location, should be used for finding nearby places around a
* location where you are not at.
@ -225,6 +275,7 @@ public class NearbyParentFragmentPresenter
nearbyParentFragmentView.setProgressBarVisibility(false);
nearbyParentFragmentView.updateListFragment(nearbyPlacesInfo.placeList);
handleCenteringTaskIfAny();
nearbyParentFragmentView.centerMapToPosition(nearbyPlacesInfo.searchLatLng);
}
}
@ -331,6 +382,11 @@ public class NearbyParentFragmentPresenter
nearbyParentFragmentView.setCheckBoxState(CheckBoxTriStates.UNKNOWN);
}
@Override
public void setAdvancedQuery(String query) {
this.customQuery = query;
}
@Override
public void searchViewGainedFocus() {
if(nearbyParentFragmentView.isListBottomSheetExpanded()) {

View file

@ -19,7 +19,6 @@ import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
import io.reactivex.Flowable;
import io.reactivex.Observable;
import io.reactivex.Single;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
@ -288,7 +287,7 @@ public class UploadRepository {
final List<Place> fromWikidataQuery = nearbyPlaces.getFromWikidataQuery(new LatLng(
decLatitude, decLongitude, 0.0f),
Locale.getDefault().getLanguage(),
NEARBY_RADIUS_IN_KILO_METERS, false);
NEARBY_RADIUS_IN_KILO_METERS, false, null);
return (fromWikidataQuery != null && fromWikidataQuery.size() > 0) ? fromWikidataQuery
.get(0) : null;
}catch (final Exception e) {

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<stroke
android:width="2dp"
android:color="@color/divider_grey" />
</shape>

View file

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
android:padding="10dp">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/tv_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/advanced_query_info_text"
android:textColor="@color/secondaryTextColor"
android:textStyle="bold"
app:layout_constrainedHeight="true"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.appcompat.widget.AppCompatEditText
android:id="@+id/et_query"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="@drawable/advanced_query_border"
android:padding="5dp"
android:textColor="@color/secondaryTextColor"
app:layout_constrainedHeight="true"
app:layout_constraintBottom_toTopOf="@id/btn_apply"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_title" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btn_apply"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/apply"
android:textColor="@color/white"
app:layout_constrainedHeight="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btn_reset"
style="@style/Widget.AppCompat.Button.Borderless"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/reset"
android:textColor="@color/mapbox_blue"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toLeftOf="@id/btn_apply" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -6,6 +6,9 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
@ -129,6 +132,11 @@
app:srcCompat="@drawable/ic_my_location_black_24dp"
app:useCompatPadding="true" />
</RelativeLayout>
<FrameLayout
android:id="@+id/fl_container_nearby_children"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</RelativeLayout>
<include layout="@layout/bottom_sheet_nearby" />

View file

@ -4,7 +4,7 @@
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:elevation="@dimen/activity_margin_horizontal"
android:background="@color/white">
@ -26,6 +26,15 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginVertical="@dimen/dimen_10"
android:paddingBottom="48dp"
/>
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btn_advanced_options"
android:layout_width="match_parent"
android:background="@color/status_bar_blue"
android:text="@string/advanced_options"
android:textColor="@color/white"
android:layout_marginTop="-48dp"
android:layout_height="48dp"/>
</LinearLayout>

View file

@ -326,6 +326,7 @@
<string name="share_app_title">Share App</string>
<string name="error_fetching_nearby_places">Error fetching nearby places.</string>
<string name="no_nearby_places_around">No nearby places around</string>
<string name="error_fetching_nearby_monuments">Error fetching nearby monuments.</string>
<string name="no_recent_searches">No recent searches</string>
<string name="delete_recent_searches_dialog">Are you sure you want to clear your search history?</string>
@ -673,4 +674,8 @@ Upload your first media by tapping on the add button.</string>
<string name="contributions_of_user">Contributions of User: %s</string>
<string name="achievements_of_user">Achievements of User: %s</string>
<string name="menu_view_user_page">View user page</string>
<string name="advanced_options">Advanced Options</string>
<string name="advanced_query_info_text">You can customize the Nearby query. If you get errors, reset and apply.</string>
<string name="apply">Apply</string>
<string name="reset">Reset</string>
</resources>