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, LOCATION_NOT_CHANGED,
PERMISSION_JUST_GRANTED, PERMISSION_JUST_GRANTED,
MAP_UPDATED, 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.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.gson.Gson; import com.google.gson.Gson;
import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.campaigns.CampaignResponseDTO; import fr.free.nrw.commons.campaigns.CampaignResponseDTO;
import fr.free.nrw.commons.explore.depictions.DepictsClient; import fr.free.nrw.commons.explore.depictions.DepictsClient;
import fr.free.nrw.commons.location.LatLng; 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 @Nullable
public List<Place> getNearbyPlaces(final LatLng cur, final String language, final double radius, public List<Place> getNearbyPlaces(final LatLng cur, final String language, final double radius,
final boolean shouldQueryForMonuments) final boolean shouldQueryForMonuments, final String customQuery)
throws Exception { throws Exception {
Timber.d("Fetching nearby items at radius %s", radius); Timber.d("Fetching nearby items at radius %s", radius);
Timber.d("CUSTOM_SPARQL%s", String.valueOf(customQuery != null));
final String wikidataQuery; final String wikidataQuery;
if (!shouldQueryForMonuments) { if (customQuery != null) {
wikidataQuery = customQuery;
} else if (!shouldQueryForMonuments) {
wikidataQuery = FileUtils.readFromResource("/queries/nearby_query.rq"); wikidataQuery = FileUtils.readFromResource("/queries/nearby_query.rq");
} else { } else {
wikidataQuery = FileUtils.readFromResource("/queries/nearby_query_monuments.rq"); wikidataQuery = FileUtils.readFromResource("/queries/nearby_query_monuments.rq");
@ -313,6 +327,23 @@ public class OkHttpJsonApiClient {
throw new Exception(response.message()); 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: * Get the QIDs of all Wikidata items that are subclasses of the given Wikidata item. Example:
* bridge -> suspended bridge, aqueduct, etc * bridge -> suspended bridge, aqueduct, etc

View file

@ -5,6 +5,7 @@ import android.content.res.Resources;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import androidx.annotation.MainThread; import androidx.annotation.MainThread;
import androidx.annotation.Nullable;
import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat; import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;
import com.mapbox.mapboxsdk.annotations.IconFactory; import com.mapbox.mapboxsdk.annotations.IconFactory;
@ -54,12 +55,13 @@ public class NearbyController {
* @param curLatLng current location for user * @param curLatLng current location for user
* @param searchLatLng the location user wants to search around * @param searchLatLng the location user wants to search around
* @param returnClosestResult if this search is done to find closest result or all results * @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 * @return NearbyPlacesInfo a variable holds Place list without distance information
* and boundary coordinates of current Place List * and boundary coordinates of current Place List
*/ */
public NearbyPlacesInfo loadAttractionsFromLocation(final LatLng curLatLng, final LatLng searchLatLng, public NearbyPlacesInfo loadAttractionsFromLocation(final LatLng curLatLng, final LatLng searchLatLng,
final boolean returnClosestResult, final boolean checkingAroundCurrentLocation, 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); Timber.d("Loading attractions near %s", searchLatLng);
NearbyPlacesInfo nearbyPlacesInfo = new NearbyPlacesInfo(); NearbyPlacesInfo nearbyPlacesInfo = new NearbyPlacesInfo();
@ -70,7 +72,7 @@ public class NearbyController {
} }
List<Place> places = nearbyPlaces List<Place> places = nearbyPlaces
.radiusExpander(searchLatLng, Locale.getDefault().getLanguage(), returnClosestResult, .radiusExpander(searchLatLng, Locale.getDefault().getLanguage(), returnClosestResult,
shouldQueryForMonuments); shouldQueryForMonuments, customQuery);
if (null != places && places.size() > 0) { if (null != places && places.size() > 0) {
LatLng[] boundaryCoordinates = {places.get(0).location, // south LatLng[] boundaryCoordinates = {places.get(0).location, // south
@ -128,10 +130,27 @@ public class NearbyController {
return nearbyPlacesInfo; return nearbyPlacesInfo;
} }
else { 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. * 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; package fr.free.nrw.commons.nearby;
import androidx.annotation.Nullable;
import java.io.IOException; import java.io.IOException;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -38,10 +39,12 @@ public class NearbyPlaces {
* @param curLatLng coordinates of search location * @param curLatLng coordinates of search location
* @param lang user's language * @param lang user's language
* @param returnClosestResult true if only the nearest point is desired * @param returnClosestResult true if only the nearest point is desired
* @param customQuery
* @return list of places obtained * @return list of places obtained
*/ */
List<Place> radiusExpander(final LatLng curLatLng, final String lang, final boolean returnClosestResult List<Place> radiusExpander(final LatLng curLatLng, final String lang,
, final boolean shouldQueryForMonuments) throws Exception { final boolean returnClosestResult
, final boolean shouldQueryForMonuments, @Nullable final String customQuery) throws Exception {
final int minResults; final int minResults;
final double maxRadius; final double maxRadius;
@ -62,13 +65,12 @@ public class NearbyPlaces {
// Increase the radius gradually to find a satisfactory number of nearby places // Increase the radius gradually to find a satisfactory number of nearby places
while (radius <= maxRadius) { 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); Timber.d("%d results at radius: %f", places.size(), radius);
if (places.size() >= minResults) { if (places.size() >= minResults) {
break; break;
} else {
radius *= RADIUS_MULTIPLIER;
} }
radius *= RADIUS_MULTIPLIER;
} }
// make sure we will be able to send at least one request next time // make sure we will be able to send at least one request next time
if (radius > maxRadius) { if (radius > maxRadius) {
@ -83,11 +85,14 @@ public class NearbyPlaces {
* @param lang user's language * @param lang user's language
* @param radius radius for search, as determined by radiusExpander() * @param radius radius for search, as determined by radiusExpander()
* @param shouldQueryForMonuments should the query include properites for monuments * @param shouldQueryForMonuments should the query include properites for monuments
* @param customQuery
* @return list of places obtained * @return list of places obtained
* @throws IOException if query fails * @throws IOException if query fails
*/ */
public List<Place> getFromWikidataQuery(final LatLng cur, final String lang, public List<Place> getFromWikidataQuery(final LatLng cur, final String lang,
final double radius, final boolean shouldQueryForMonuments) throws Exception { final double radius, final boolean shouldQueryForMonuments,
return okHttpJsonApiClient.getNearbyPlaces(cur, lang, radius, 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 android.content.Context;
import androidx.annotation.Nullable;
import com.mapbox.mapboxsdk.annotations.Marker; import com.mapbox.mapboxsdk.annotations.Marker;
import fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType;
import java.util.List; import java.util.List;
import fr.free.nrw.commons.kvstore.JsonKvStore; import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.location.LatLng; 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.Label;
import fr.free.nrw.commons.nearby.NearbyBaseMarker; import fr.free.nrw.commons.nearby.NearbyBaseMarker;
import fr.free.nrw.commons.nearby.Place; import fr.free.nrw.commons.nearby.Place;
@ -19,6 +20,7 @@ public interface NearbyParentFragmentContract {
boolean isNetworkConnectionEstablished(); boolean isNetworkConnectionEstablished();
void listOptionMenuItemClicked(); void listOptionMenuItemClicked();
void populatePlaces(LatLng curlatLng); void populatePlaces(LatLng curlatLng);
void populatePlaces(LatLng curlatLng, String customQuery);
boolean isListBottomSheetExpanded(); boolean isListBottomSheetExpanded();
void checkPermissionsAndPerformAction(); void checkPermissionsAndPerformAction();
void displayLoginSkippedWarning(); void displayLoginSkippedWarning();
@ -62,7 +64,7 @@ public interface NearbyParentFragmentContract {
LatLng getCameraTarget(); LatLng getCameraTarget();
void centerMapToPlace(Place placeToCenter); void centerMapToPlace(@Nullable Place placeToCenter);
void updateListFragment(List<Place> placeList); void updateListFragment(List<Place> placeList);
@ -72,6 +74,12 @@ public interface NearbyParentFragmentContract {
boolean isCurrentLocationMarkerVisible(); boolean isCurrentLocationMarkerVisible();
void setProjectorLatLngBounds(); void setProjectorLatLngBounds();
boolean isAdvancedQueryFragmentVisible();
void showHideAdvancedQueryFragment(boolean shouldShow);
void centerMapToPosition(@Nullable LatLng searchLatLng);
} }
interface NearbyListView { interface NearbyListView {
@ -79,7 +87,7 @@ public interface NearbyParentFragmentContract {
} }
interface UserActions { interface UserActions {
void updateMapAndList(LocationServiceManager.LocationChangeType locationChangeType); void updateMapAndList(LocationChangeType locationChangeType);
void lockUnlockNearby(boolean isNearbyLocked); void lockUnlockNearby(boolean isNearbyLocked);
void attachView(View view); void attachView(View view);
@ -96,5 +104,7 @@ public interface NearbyParentFragmentContract {
void searchViewGainedFocus(); void searchViewGainedFocus();
void setCheckboxUnknown(); 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; 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_SIGNIFICANTLY_CHANGED;
import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.LOCATION_SLIGHTLY_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.MAP_UPDATED;
@ -34,6 +35,7 @@ import android.view.ViewGroup;
import android.view.animation.Animation; import android.view.animation.Animation;
import android.view.animation.AnimationUtils; import android.view.animation.AnimationUtils;
import android.widget.Button; import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.ProgressBar; import android.widget.ProgressBar;
@ -44,6 +46,7 @@ import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.AppCompatButton;
import androidx.appcompat.widget.AppCompatImageView; import androidx.appcompat.widget.AppCompatImageView;
import androidx.appcompat.widget.SearchView; import androidx.appcompat.widget.SearchView;
import androidx.appcompat.widget.AppCompatTextView; 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.NearbyMarker;
import fr.free.nrw.commons.nearby.Place; import fr.free.nrw.commons.nearby.Place;
import fr.free.nrw.commons.nearby.contract.NearbyParentFragmentContract; 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.nearby.presenter.NearbyParentFragmentPresenter;
import fr.free.nrw.commons.upload.FileUtils;
import fr.free.nrw.commons.utils.DialogUtil; import fr.free.nrw.commons.utils.DialogUtil;
import fr.free.nrw.commons.utils.ExecutorUtils; import fr.free.nrw.commons.utils.ExecutorUtils;
import fr.free.nrw.commons.utils.LayoutUtils; 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 fr.free.nrw.commons.wikidata.WikidataEditListener;
import io.reactivex.Observable; import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.functions.Function;
import io.reactivex.schedulers.Schedulers; import io.reactivex.schedulers.Schedulers;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named; import javax.inject.Named;
@ -180,6 +183,10 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
AppCompatImageView ivToggleChips; AppCompatImageView ivToggleChips;
@BindView(R.id.chip_view) @BindView(R.id.chip_view)
View llContainerChips; View llContainerChips;
@BindView(R.id.btn_advanced_options)
AppCompatButton btnAdvancedOptions;
@BindView(R.id.fl_container_nearby_children)
FrameLayout flConainerNearbyChildren;
@Inject LocationServiceManager locationManager; @Inject LocationServiceManager locationManager;
@Inject NearbyController nearbyController; @Inject NearbyController nearbyController;
@ -233,6 +240,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
private LatLngBounds latLngBounds; private LatLngBounds latLngBounds;
private PlaceAdapter adapter; private PlaceAdapter adapter;
private NearbyParentFragmentInstanceReadyCallback nearbyParentFragmentInstanceReadyCallback; private NearbyParentFragmentInstanceReadyCallback nearbyParentFragmentInstanceReadyCallback;
private boolean isAdvancedQueryFragmentVisible = false;
/** /**
* Holds filtered markers that are to be shown * 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.setText(Html.fromHtml(getString(R.string.map_attribution)));
tvAttribution.setMovementMethod(LinkMovementMethod.getInstance()); 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); nearbyFilterList.getLayoutParams().width = (int) LayoutUtils.getScreenWidth(getActivity(), 0.75);
recyclerView.setAdapter(nearbyFilterSearchRecyclerViewAdapter); recyclerView.setAdapter(nearbyFilterSearchRecyclerViewAdapter);
LayoutUtils.setLayoutHeightAllignedToWidth(1, nearbyFilterList); LayoutUtils.setLayoutHeightAllignedToWidth(1.25, nearbyFilterList);
compositeDisposable.add(RxSearchView.queryTextChanges(searchView) compositeDisposable.add(RxSearchView.queryTextChanges(searchView)
.takeUntil(RxView.detaches(searchView)) .takeUntil(RxView.detaches(searchView))
@ -821,7 +865,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
* @param place is new center of the map * @param place is new center of the map
*/ */
@Override @Override
public void centerMapToPlace(final Place place) { public void centerMapToPlace(@Nullable final Place place) {
Timber.d("Map is centered to place"); Timber.d("Map is centered to place");
final double cameraShift; final double cameraShift;
if(null!=place){ if(null!=place){
@ -846,6 +890,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
} }
} }
@Override @Override
public void updateListFragment(final List<Place> placeList) { public void updateListFragment(final List<Place> placeList) {
places = placeList; places = placeList;
@ -879,6 +924,32 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
latLngBounds = mapBox.getProjection().getVisibleRegion().latLngBounds; 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 @Override
public boolean isNetworkConnectionEstablished() { public boolean isNetworkConnectionEstablished() {
return NetworkUtils.isInternetConnectionEstablished(getActivity()); return NetworkUtils.isInternetConnectionEstablished(getActivity());
@ -933,29 +1004,53 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
@Override @Override
public void populatePlaces(final fr.free.nrw.commons.location.LatLng curlatLng) { 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 if (curlatLng.equals(lastFocusLocation) || lastFocusLocation == null || recenterToUserLocation) { // Means we are checking around current location
populatePlacesForCurrentLocation(lastKnownLocation, curlatLng); populatePlacesForCurrentLocation(lastKnownLocation, curlatLng, null);
} else { } else {
populatePlacesForAnotherLocation(lastKnownLocation, curlatLng); populatePlacesForAnotherLocation(lastKnownLocation, curlatLng, null);
} }
if(recenterToUserLocation) { if(recenterToUserLocation) {
recenterToUserLocation = false; recenterToUserLocation = false;
} }
} }
private void populatePlacesForCurrentLocation(final fr.free.nrw.commons.location.LatLng curlatLng, @Override
final fr.free.nrw.commons.location.LatLng searchLatLng){ 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 final Observable<NearbyPlacesInfo> nearbyPlacesInfoObservable = Observable
.fromCallable(() -> nearbyController .fromCallable(() -> nearbyController
.loadAttractionsFromLocation(curlatLng, searchLatLng, .loadAttractionsFromLocation(curlatLng, searchLatLng,
false, true, Utils.isMonumentsEnabled(new Date()))); false, true, Utils.isMonumentsEnabled(new Date()), customQuery));
compositeDisposable.add(nearbyPlacesInfoObservable compositeDisposable.add(nearbyPlacesInfoObservable
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe(nearbyPlacesInfo -> { .subscribe(nearbyPlacesInfo -> {
if (nearbyPlacesInfo.placeList == null || nearbyPlacesInfo.placeList.isEmpty()) {
showErrorMessage(getString(R.string.no_nearby_places_around));
} else {
updateMapMarkers(nearbyPlacesInfo, true); updateMapMarkers(nearbyPlacesInfo, true);
lastFocusLocation = searchLatLng; lastFocusLocation = searchLatLng;
}
}, },
throwable -> { throwable -> {
Timber.d(throwable); Timber.d(throwable);
@ -966,20 +1061,24 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
})); }));
} }
private void populatePlacesForAnotherLocation(final fr.free.nrw.commons.location.LatLng curlatLng, private void populatePlacesForAnotherLocation(
final fr.free.nrw.commons.location.LatLng searchLatLng){ 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 final Observable<NearbyPlacesInfo> nearbyPlacesInfoObservable = Observable
.fromCallable(() -> nearbyController .fromCallable(() -> nearbyController
.loadAttractionsFromLocation(curlatLng, searchLatLng, .loadAttractionsFromLocation(curlatLng, searchLatLng,
false, true, Utils.isMonumentsEnabled(new Date()))); false, true, Utils.isMonumentsEnabled(new Date()), customQuery));
compositeDisposable.add(nearbyPlacesInfoObservable compositeDisposable.add(nearbyPlacesInfoObservable
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe(nearbyPlacesInfo -> { .subscribe(nearbyPlacesInfo -> {
if (nearbyPlacesInfo.placeList == null || nearbyPlacesInfo.placeList.isEmpty()) {
showErrorMessage(getString(R.string.no_nearby_places_around));
} else {
updateMapMarkers(nearbyPlacesInfo, false); updateMapMarkers(nearbyPlacesInfo, false);
lastFocusLocation = searchLatLng; lastFocusLocation = searchLatLng;
}
}, },
throwable -> { throwable -> {
Timber.e(throwable); Timber.e(throwable);
@ -1617,10 +1716,6 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
if (!fabPlus.isShown()) { if (!fabPlus.isShown()) {
showFABs(); showFABs();
} }
getView().requestFocus();
break;
case (BottomSheetBehavior.STATE_EXPANDED):
getView().requestFocus();
break; break;
case (BottomSheetBehavior.STATE_HIDDEN): case (BottomSheetBehavior.STATE_HIDDEN):
if (null != mapBox) { if (null != mapBox) {
@ -1630,9 +1725,6 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
transparentView.setAlpha(0); transparentView.setAlpha(0);
collapseFABs(isFABsExpanded); collapseFABs(isFABsExpanded);
hideFABs(); hideFABs();
if (getView() != null) {
getView().requestFocus();
}
break; break;
} }
} }

View file

@ -3,8 +3,10 @@ package fr.free.nrw.commons.nearby.presenter;
import android.view.View; import android.view.View;
import androidx.annotation.MainThread; import androidx.annotation.MainThread;
import androidx.annotation.Nullable;
import com.mapbox.mapboxsdk.annotations.Marker; import com.mapbox.mapboxsdk.annotations.Marker;
import fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType;
import java.lang.reflect.Proxy; import java.lang.reflect.Proxy;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; 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.bookmarks.locations.BookmarkLocationsDao;
import fr.free.nrw.commons.kvstore.JsonKvStore; import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.location.LatLng; 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.location.LocationUpdateListener;
import fr.free.nrw.commons.nearby.CheckBoxTriStates; import fr.free.nrw.commons.nearby.CheckBoxTriStates;
import fr.free.nrw.commons.nearby.Label; 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 fr.free.nrw.commons.wikidata.WikidataEditListener;
import timber.log.Timber; 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_SIGNIFICANTLY_CHANGED;
import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.LOCATION_SLIGHTLY_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.MAP_UPDATED;
@ -46,6 +48,8 @@ public class NearbyParentFragmentPresenter
BookmarkLocationsDao bookmarkLocationDao; BookmarkLocationsDao bookmarkLocationDao;
private @Nullable String customQuery;
private static final NearbyParentFragmentContract.View DUMMY = (NearbyParentFragmentContract.View) Proxy.newProxyInstance( private static final NearbyParentFragmentContract.View DUMMY = (NearbyParentFragmentContract.View) Proxy.newProxyInstance(
NearbyParentFragmentContract.View.class.getClassLoader(), NearbyParentFragmentContract.View.class.getClassLoader(),
new Class[]{NearbyParentFragmentContract.View.class}, (proxy, method, args) -> { new Class[]{NearbyParentFragmentContract.View.class}, (proxy, method, args) -> {
@ -118,7 +122,11 @@ public class NearbyParentFragmentPresenter
@Override @Override
public boolean backButtonClicked() { 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 // Back should first hide the bottom sheet if it is expanded
nearbyParentFragmentView.listOptionMenuItemClicked(); nearbyParentFragmentView.listOptionMenuItemClicked();
return true; return true;
@ -160,7 +168,7 @@ public class NearbyParentFragmentPresenter
* @param locationChangeType defines if location changed significantly or slightly * @param locationChangeType defines if location changed significantly or slightly
*/ */
@Override @Override
public void updateMapAndList(LocationServiceManager.LocationChangeType locationChangeType) { public void updateMapAndList(LocationChangeType locationChangeType) {
Timber.d("Presenter updates map and list"); Timber.d("Presenter updates map and list");
if (isNearbyLocked) { if (isNearbyLocked) {
Timber.d("Nearby is locked, so updateMapAndList returns"); 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 * Significant changed - Markers and current location will be updated together
* Slightly changed - Only current position marker will be updated * 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)) { || locationChangeType.equals(MAP_UPDATED)) {
Timber.d("LOCATION_SIGNIFICANTLY_CHANGED"); Timber.d("LOCATION_SIGNIFICANTLY_CHANGED");
lockUnlockNearby(true); 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 * Populates places for custom location, should be used for finding nearby places around a
* location where you are not at. * location where you are not at.
@ -225,6 +275,7 @@ public class NearbyParentFragmentPresenter
nearbyParentFragmentView.setProgressBarVisibility(false); nearbyParentFragmentView.setProgressBarVisibility(false);
nearbyParentFragmentView.updateListFragment(nearbyPlacesInfo.placeList); nearbyParentFragmentView.updateListFragment(nearbyPlacesInfo.placeList);
handleCenteringTaskIfAny(); handleCenteringTaskIfAny();
nearbyParentFragmentView.centerMapToPosition(nearbyPlacesInfo.searchLatLng);
} }
} }
@ -331,6 +382,11 @@ public class NearbyParentFragmentPresenter
nearbyParentFragmentView.setCheckBoxState(CheckBoxTriStates.UNKNOWN); nearbyParentFragmentView.setCheckBoxState(CheckBoxTriStates.UNKNOWN);
} }
@Override
public void setAdvancedQuery(String query) {
this.customQuery = query;
}
@Override @Override
public void searchViewGainedFocus() { public void searchViewGainedFocus() {
if(nearbyParentFragmentView.isListBottomSheetExpanded()) { 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.Flowable;
import io.reactivex.Observable; import io.reactivex.Observable;
import io.reactivex.Single; import io.reactivex.Single;
import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
@ -288,7 +287,7 @@ public class UploadRepository {
final List<Place> fromWikidataQuery = nearbyPlaces.getFromWikidataQuery(new LatLng( final List<Place> fromWikidataQuery = nearbyPlaces.getFromWikidataQuery(new LatLng(
decLatitude, decLongitude, 0.0f), decLatitude, decLongitude, 0.0f),
Locale.getDefault().getLanguage(), Locale.getDefault().getLanguage(),
NEARBY_RADIUS_IN_KILO_METERS, false); NEARBY_RADIUS_IN_KILO_METERS, false, null);
return (fromWikidataQuery != null && fromWikidataQuery.size() > 0) ? fromWikidataQuery return (fromWikidataQuery != null && fromWikidataQuery.size() > 0) ? fromWikidataQuery
.get(0) : null; .get(0) : null;
}catch (final Exception e) { }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_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout <RelativeLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
@ -129,6 +132,11 @@
app:srcCompat="@drawable/ic_my_location_black_24dp" app:srcCompat="@drawable/ic_my_location_black_24dp"
app:useCompatPadding="true" /> app:useCompatPadding="true" />
</RelativeLayout>
<FrameLayout
android:id="@+id/fl_container_nearby_children"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</RelativeLayout> </RelativeLayout>
<include layout="@layout/bottom_sheet_nearby" /> <include layout="@layout/bottom_sheet_nearby" />

View file

@ -4,7 +4,7 @@
android:orientation="vertical" android:orientation="vertical"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentRight="true" android:layout_alignParentEnd="true"
android:elevation="@dimen/activity_margin_horizontal" android:elevation="@dimen/activity_margin_horizontal"
android:background="@color/white"> android:background="@color/white">
@ -26,6 +26,15 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginVertical="@dimen/dimen_10" 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> </LinearLayout>

View file

@ -326,6 +326,7 @@
<string name="share_app_title">Share App</string> <string name="share_app_title">Share App</string>
<string name="error_fetching_nearby_places">Error fetching nearby places.</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="error_fetching_nearby_monuments">Error fetching nearby monuments.</string>
<string name="no_recent_searches">No recent searches</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> <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="contributions_of_user">Contributions of User: %s</string>
<string name="achievements_of_user">Achievements 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="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> </resources>

View file

@ -0,0 +1,89 @@
package fr.free.nrw.commons
import com.google.gson.Gson
import com.nhaarman.mockitokotlin2.any
import com.nhaarman.mockitokotlin2.verify
import fr.free.nrw.commons.explore.depictions.DepictsClient
import fr.free.nrw.commons.location.LatLng
import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient
import okhttp3.Call
import okhttp3.HttpUrl
import okhttp3.OkHttpClient
import okhttp3.Response
import org.junit.Before
import org.junit.Test
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.MockitoAnnotations
import java.lang.Exception
class OkHttpJsonApiClientTests {
@Mock
lateinit var okhttpClient: OkHttpClient
@Mock
lateinit var depictsClient: DepictsClient
@Mock
lateinit var wikiMediaToolforgeUrl: HttpUrl
@Mock
lateinit var wikiMediaTestToolforgeUrl: HttpUrl
var sparqlQueryUrl: String = "https://www.testqparql.com"
var campaignsUrl: String = "https://www.testcampaignsurl.com"
@Mock
lateinit var gson: Gson
@Mock
lateinit var latLng: LatLng
private lateinit var okHttpJsonApiClient: OkHttpJsonApiClient
@Mock
lateinit var call: Call
@Mock
lateinit var response: Response
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
okHttpJsonApiClient = OkHttpJsonApiClient(
okhttpClient,
depictsClient,
wikiMediaToolforgeUrl,
wikiMediaTestToolforgeUrl,
sparqlQueryUrl,
campaignsUrl,
gson
)
Mockito.`when`(okhttpClient.newCall(any())).thenReturn(call)
Mockito.`when`(call.execute()).thenReturn(response)
}
@Test
fun testGetNearbyPlacesCustomQuery() {
Mockito.`when`(response.message()).thenReturn("test")
try {
okHttpJsonApiClient.getNearbyPlaces(latLng, "test", 10.0, true, "test")
} catch (e: Exception) {
assert(e.message.equals("test"))
}
verify(okhttpClient).newCall(any())
verify(call).execute()
}
@Test
fun testGetNearbyPlaces() {
Mockito.`when`(response.message()).thenReturn("test")
try {
okHttpJsonApiClient.getNearbyPlaces(latLng, "test", 10.0, true)
} catch (e: Exception) {
assert(e.message.equals("test"))
}
verify(okhttpClient).newCall(any())
verify(call).execute()
}
}

View file

@ -0,0 +1,129 @@
package fr.free.nrw.commons.nearby
import android.content.Context
import android.os.Bundle
import android.os.Looper
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.appcompat.widget.AppCompatButton
import androidx.appcompat.widget.AppCompatEditText
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentTransaction
import com.nhaarman.mockitokotlin2.any
import com.nhaarman.mockitokotlin2.verify
import fr.free.nrw.commons.R
import fr.free.nrw.commons.TestAppAdapter
import fr.free.nrw.commons.TestCommonsApplication
import fr.free.nrw.commons.contributions.MainActivity
import fr.free.nrw.commons.nearby.fragments.AdvanceQueryFragment
import org.junit.Assert
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.MockitoAnnotations
import org.robolectric.Robolectric
import org.robolectric.RobolectricTestRunner
import org.robolectric.RuntimeEnvironment
import org.robolectric.Shadows
import org.robolectric.annotation.Config
import org.robolectric.annotation.LooperMode
import org.wikipedia.AppAdapter
@RunWith(RobolectricTestRunner::class)
@Config(sdk = [21], application = TestCommonsApplication::class)
@LooperMode(LooperMode.Mode.PAUSED)
class AdvanceQueryFragmentUnitTests {
private lateinit var context: Context
private lateinit var activity: MainActivity
private lateinit var fragment: AdvanceQueryFragment
private lateinit var viewGroup: ViewGroup
@Mock
private lateinit var layoutInflater: LayoutInflater
@Mock
private lateinit var callback: AdvanceQueryFragment.Callback
@Mock
private lateinit var view: View
@Mock
private lateinit var etQuery: AppCompatEditText
@Mock
private lateinit var btnReset: AppCompatButton
@Mock
private lateinit var btnApply: AppCompatButton
@Mock
private lateinit var bundle: Bundle
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
AppAdapter.set(TestAppAdapter())
context = RuntimeEnvironment.application.applicationContext
activity = Robolectric.buildActivity(MainActivity::class.java).create().get()
viewGroup = FrameLayout(context)
Mockito.`when`(bundle.getString("query")).thenReturn("test")
fragment = AdvanceQueryFragment()
fragment.callback = callback
fragment.arguments = bundle
val fragmentManager: FragmentManager = activity.supportFragmentManager
val fragmentTransaction: FragmentTransaction = fragmentManager.beginTransaction()
fragmentTransaction.add(fragment, null)
fragmentTransaction.commit()
Mockito.`when`(layoutInflater.inflate(R.layout.fragment_advance_query, viewGroup, false))
.thenReturn(view)
Mockito.`when`(view.findViewById<AppCompatEditText>(R.id.et_query)).thenReturn(etQuery)
Mockito.`when`(view.findViewById<AppCompatButton>(R.id.btn_apply)).thenReturn(btnApply)
Mockito.`when`(view.findViewById<AppCompatButton>(R.id.btn_reset)).thenReturn(btnReset)
}
@Test
@Throws(Exception::class)
fun checkFragmentNotNull() {
Shadows.shadowOf(Looper.getMainLooper()).idle()
Assert.assertNotNull(fragment)
}
@Test
fun testOnCreateView() {
Shadows.shadowOf(Looper.getMainLooper()).idle()
fragment.onCreateView(layoutInflater, viewGroup, bundle)
verify(layoutInflater).inflate(R.layout.fragment_advance_query, viewGroup, false)
}
@Test
fun testOnViewCreated() {
fragment.onCreateView(layoutInflater, viewGroup, bundle)
fragment.onViewCreated(view, bundle)
verify(etQuery).setText("test")
Mockito.`when`(btnReset.post(any())).thenAnswer {
it.getArgument(0, Runnable::class.java).run()
}
Mockito.`when`(btnApply.post(any())).thenAnswer {
it.getArgument(0, Runnable::class.java).run()
}
btnReset.performClick()
btnApply.performClick()
}
@Test
fun testHideKeyboard() {
fragment.hideKeyBoard()
}
}

View file

@ -0,0 +1,74 @@
package fr.free.nrw.commons.nearby
import com.nhaarman.mockitokotlin2.any
import com.nhaarman.mockitokotlin2.anyOrNull
import com.nhaarman.mockitokotlin2.eq
import fr.free.nrw.commons.location.LatLng
import org.junit.Before
import org.junit.Test
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.MockitoAnnotations
import java.util.*
class NearbyControllerTest {
@Mock
private lateinit var nearbyPlaces: NearbyPlaces
@Mock
lateinit var searchLatLong: LatLng
@Mock
lateinit var currentLatLng: LatLng
var customQuery: String = "test"
private lateinit var nearbyController: NearbyController
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
nearbyController = NearbyController(nearbyPlaces)
}
@Test
fun testLoadAttractionsForLocationTest() {
Mockito.`when`(nearbyPlaces.radiusExpander(any(), any(), any(), any(), any()))
.thenReturn(Collections.emptyList())
nearbyController.loadAttractionsFromLocation(
searchLatLong,
currentLatLng,
false,
false,
true,
customQuery
)
Mockito.verify(nearbyPlaces).radiusExpander(
eq(currentLatLng),
any(String::class.java),
eq(false),
eq(true),
eq(customQuery)
)
}
@Test
fun testLoadAttractionsForLocationTestNoQuery() {
Mockito.`when`(nearbyPlaces.radiusExpander(any(), any(), any(), any(), anyOrNull()))
.thenReturn(Collections.emptyList())
nearbyController.loadAttractionsFromLocation(
searchLatLong,
currentLatLng,
false,
false,
true
)
Mockito.verify(nearbyPlaces).radiusExpander(
eq(currentLatLng),
any(String::class.java),
eq(false),
eq(true),
eq(null)
)
}
fun <T> any(type: Class<T>): T = Mockito.any<T>(type)
}

View file

@ -15,6 +15,7 @@ import org.mockito.ArgumentMatchers
import org.mockito.Mock import org.mockito.Mock
import org.mockito.Mockito import org.mockito.Mockito
import org.mockito.MockitoAnnotations import org.mockito.MockitoAnnotations
import java.util.*
/** /**
* The unit test class for NearbyParentFragmentPresenter * The unit test class for NearbyParentFragmentPresenter
@ -32,6 +33,8 @@ class NearbyParentFragmentPresenterTest {
internal lateinit var selectedLabels: List<Label> internal lateinit var selectedLabels: List<Label>
@Mock @Mock
internal lateinit var marker: Marker internal lateinit var marker: Marker
@Mock
internal lateinit var nearbyPlaces: NearbyPlaces
private lateinit var nearbyPresenter: NearbyParentFragmentPresenter private lateinit var nearbyPresenter: NearbyParentFragmentPresenter
private lateinit var mapboxCameraTarget: com.mapbox.mapboxsdk.geometry.LatLng private lateinit var mapboxCameraTarget: com.mapbox.mapboxsdk.geometry.LatLng
@ -392,6 +395,14 @@ class NearbyParentFragmentPresenterTest {
assertFalse(hasNearbyHandledBackPress) assertFalse(hasNearbyHandledBackPress)
} }
@Test
fun testBackButtonClickedWhenAdvancedFragmentIsVisible() {
whenever(nearbyParentFragmentView.isAdvancedQueryFragmentVisible()).thenReturn(true)
val hasNearbyHandledBackPress = nearbyPresenter.backButtonClicked()
verify(nearbyParentFragmentView).showHideAdvancedQueryFragment(false)
assertTrue(hasNearbyHandledBackPress)
}
@Test @Test
fun testMarkerUnselected() { fun testMarkerUnselected() {
nearbyPresenter.markerUnselected() nearbyPresenter.markerUnselected()
@ -429,6 +440,50 @@ class NearbyParentFragmentPresenterTest {
verify(nearbyParentFragmentView).recenterMap(latestLocation) verify(nearbyParentFragmentView).recenterMap(latestLocation)
} }
@Test
fun testOnLocationChangeTypeCustomQuery() {
nearbyPresenter.setAdvancedQuery("Point(17.865 82.343)\"")
whenever(nearbyParentFragmentView.isNetworkConnectionEstablished()).thenReturn(true)
whenever(nearbyParentFragmentView.getLastLocation()).thenReturn(latestLocation)
nearbyPresenter.updateMapAndList(LocationChangeType.CUSTOM_QUERY)
expectMapAndListUpdate()
verify(nearbyParentFragmentView).setProgressBarVisibility(true)
verify(nearbyParentFragmentView).populatePlaces(any(), any())
}
@Test
fun testOnLocationChangeTypeCustomQueryInvalidQuery() {
whenever(nearbyParentFragmentView.isNetworkConnectionEstablished).thenReturn(true)
whenever(nearbyParentFragmentView.lastLocation).thenReturn(latestLocation)
nearbyPresenter.setAdvancedQuery("")
nearbyPresenter.updateMapAndList(LocationChangeType.CUSTOM_QUERY)
expectMapAndListUpdate()
nearbyPresenter.setAdvancedQuery("Point(")
nearbyPresenter.updateMapAndList(LocationChangeType.CUSTOM_QUERY)
expectMapAndListUpdate()
}
@Test
fun testOnLocationChangeTypeCustomQueryUnParsableQuery() {
whenever(nearbyParentFragmentView.isNetworkConnectionEstablished).thenReturn(true)
whenever(nearbyParentFragmentView.lastLocation).thenReturn(latestLocation)
nearbyPresenter.setAdvancedQuery("Point()\"")
nearbyPresenter.updateMapAndList(LocationChangeType.CUSTOM_QUERY)
expectMapAndListUpdate()
whenever(nearbyParentFragmentView.isNetworkConnectionEstablished).thenReturn(true)
whenever(nearbyParentFragmentView.lastLocation).thenReturn(latestLocation)
nearbyPresenter.setAdvancedQuery("Point(ab)\"")
nearbyPresenter.updateMapAndList(LocationChangeType.CUSTOM_QUERY)
expectMapAndListUpdate()
whenever(nearbyParentFragmentView.isNetworkConnectionEstablished).thenReturn(true)
whenever(nearbyParentFragmentView.lastLocation).thenReturn(latestLocation)
nearbyPresenter.setAdvancedQuery("Point(ab ab)\"")
nearbyPresenter.updateMapAndList(LocationChangeType.CUSTOM_QUERY)
expectMapAndListUpdate()
}
@Test @Test
fun testOnCameraMoveWhenSearchLocationNull() { fun testOnCameraMoveWhenSearchLocationNull() {
NearbyController.latestSearchLocation = null NearbyController.latestSearchLocation = null
@ -456,4 +511,27 @@ class NearbyParentFragmentPresenterTest {
verify(nearbyParentFragmentView).isNetworkConnectionEstablished() verify(nearbyParentFragmentView).isNetworkConnectionEstablished()
verifyZeroInteractions(nearbyParentFragmentView) verifyZeroInteractions(nearbyParentFragmentView)
} }
@Test
fun testSetAdvancedQuery(){
nearbyPresenter.setAdvancedQuery("test")
}
@Test
fun testUpdateMapMarkers(){
var nearbyPlacesInfo = NearbyController(nearbyPlaces).NearbyPlacesInfo()
nearbyPlacesInfo.boundaryCoordinates= arrayOf()
nearbyPlacesInfo.curLatLng=latestLocation
nearbyPlacesInfo.searchLatLng=latestLocation
nearbyPlacesInfo.placeList = null
whenever(bookmarkLocationsDao.allBookmarksLocations).thenReturn(Collections.emptyList())
nearbyPresenter.updateMapMarkers(nearbyPlacesInfo, marker, true)
Mockito.verify(nearbyParentFragmentView).updateMapMarkers(any(), eq(marker))
Mockito.verify(nearbyParentFragmentView).addCurrentLocationMarker(latestLocation)
Mockito.verify(nearbyParentFragmentView).updateMapToTrackPosition(latestLocation)
Mockito.verify(nearbyParentFragmentView).setProgressBarVisibility(false)
Mockito.verify(nearbyParentFragmentView).centerMapToPosition(latestLocation)
}
} }

View file

@ -0,0 +1,40 @@
package fr.free.nrw.commons.nearby
import com.nhaarman.mockitokotlin2.any
import com.nhaarman.mockitokotlin2.eq
import com.nhaarman.mockitokotlin2.times
import com.nhaarman.mockitokotlin2.verify
import fr.free.nrw.commons.location.LatLng
import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient
import org.junit.Before
import org.junit.Test
import org.mockito.Mock
import org.mockito.MockitoAnnotations
class NearbyPlacesTest {
@Mock
private lateinit var okHttpJsonApiClient: OkHttpJsonApiClient
@Mock
private lateinit var currentLatLong: LatLng
private lateinit var nearbyPlaces: NearbyPlaces
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
nearbyPlaces = NearbyPlaces(okHttpJsonApiClient)
}
@Test
fun testRadiusExpander() {
nearbyPlaces.radiusExpander(currentLatLong, "test", true, true, "test")
verify(okHttpJsonApiClient, times(5)).getNearbyPlaces(
eq(currentLatLong),
eq("test"),
any(),
eq(true),
eq("test")
)
}
}