Feat: Make it smoother to switch between nearby and explore maps (#6164)

* Nearby: Add 'Show in Explore' 3-dots menu item

* MainActivity: Add methods to pass extras between Nearby and Explore

* MainActivity: Extend loadFragment() to support passing fragment arguments

* Nearby: Add ability to navigate to Explore fragment on 'Show in Explore' click

* Explore: Read fragment arguments for Nearby map data and update Explore map if present

* Explore: Add 'Show in Nearby' 3-dots menu item. Only visible when Map tab is selected

* Explore: On 'Show in Nearby' click, navigate to Nearby fragment, passing map data as fragment args

* Nearby: Read fragment arguments for Explore map data and update Nearby map if present

* MainActivity: Fix memory leaks when navigating between bottom nav destinations

* Explore: Fix crashes caused by unattached map fragment

* Refactor code to pass unit tests

* Explore: Format javadocs

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
This commit is contained in:
Ifeoluwa Andrew Omole 2025-01-30 13:58:00 +01:00 committed by GitHub
parent 9dc9a3b8ab
commit 7b291535e0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 510 additions and 152 deletions

1
.gitignore vendored
View file

@ -47,3 +47,4 @@ captures/*
# Test and other output # Test and other output
app/jacoco.exec app/jacoco.exec
app/CommonsContributions app/CommonsContributions
app/.*

View file

@ -207,6 +207,9 @@ public class MainActivity extends BaseActivity
private boolean loadFragment(Fragment fragment, boolean showBottom) { private boolean loadFragment(Fragment fragment, boolean showBottom) {
//showBottom so that we do not show the bottom tray again when constructing //showBottom so that we do not show the bottom tray again when constructing
//from the saved instance state. //from the saved instance state.
freeUpFragments();
if (fragment instanceof ContributionsFragment) { if (fragment instanceof ContributionsFragment) {
if (activeFragment == ActiveFragment.CONTRIBUTIONS) { if (activeFragment == ActiveFragment.CONTRIBUTIONS) {
// scroll to top if already on the Contributions tab // scroll to top if already on the Contributions tab
@ -256,6 +259,31 @@ public class MainActivity extends BaseActivity
return false; return false;
} }
/**
* loadFragment() overload that supports passing extras to fragments
**/
private boolean loadFragment(Fragment fragment, boolean showBottom, Bundle args) {
if (fragment != null && args != null) {
fragment.setArguments(args);
}
return loadFragment(fragment, showBottom);
}
/**
* Old implementation of loadFragment() was causing memory leaks, due to MainActivity holding
* references to cleared fragments. This function frees up all fragment references.
* <p>
* Called in loadFragment() before doing the actual loading.
**/
public void freeUpFragments() {
// free all fragments except contributionsFragment because several tests depend on it.
// hence, contributionsFragment is probably still a leak
nearbyParentFragment = null;
exploreFragment = null;
bookmarkFragment = null;
}
public void hideTabs() { public void hideTabs() {
binding.fragmentMainNavTabLayout.setVisibility(View.GONE); binding.fragmentMainNavTabLayout.setVisibility(View.GONE);
} }
@ -432,6 +460,42 @@ public class MainActivity extends BaseActivity
}); });
} }
/**
* Launch the Explore fragment from Nearby fragment. This method is called when a user clicks
* the 'Show in Explore' option in the 3-dots menu in Nearby.
*
* @param zoom current zoom of Nearby map
* @param latitude current latitude of Nearby map
* @param longitude current longitude of Nearby map
**/
public void loadExploreMapFromNearby(double zoom, double latitude, double longitude) {
Bundle bundle = new Bundle();
bundle.putDouble("prev_zoom", zoom);
bundle.putDouble("prev_latitude", latitude);
bundle.putDouble("prev_longitude", longitude);
loadFragment(ExploreFragment.newInstance(), false, bundle);
setSelectedItemId(NavTab.EXPLORE.code());
}
/**
* Launch the Nearby fragment from Explore fragment. This method is called when a user clicks
* the 'Show in Nearby' option in the 3-dots menu in Explore.
*
* @param zoom current zoom of Explore map
* @param latitude current latitude of Explore map
* @param longitude current longitude of Explore map
**/
public void loadNearbyMapFromExplore(double zoom, double latitude, double longitude) {
Bundle bundle = new Bundle();
bundle.putDouble("prev_zoom", zoom);
bundle.putDouble("prev_latitude", latitude);
bundle.putDouble("prev_longitude", longitude);
loadFragment(NearbyParentFragment.newInstance(), false, bundle);
setSelectedItemId(NavTab.NEARBY.code());
}
@Override @Override
protected void onResume() { protected void onResume() {
super.onResume(); super.onResume();

View file

@ -1,5 +1,7 @@
package fr.free.nrw.commons.explore; package fr.free.nrw.commons.explore;
import static androidx.viewpager.widget.ViewPager.SCROLL_STATE_IDLE;
import android.os.Bundle; import android.os.Bundle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
@ -42,9 +44,13 @@ public class ExploreFragment extends CommonsDaggerSupportFragment {
@Named("default_preferences") @Named("default_preferences")
public JsonKvStore applicationKvStore; public JsonKvStore applicationKvStore;
public void setScroll(boolean canScroll){ // Nearby map state (for if we came from Nearby fragment)
if (binding != null) private double prevZoom;
{ private double prevLatitude;
private double prevLongitude;
public void setScroll(boolean canScroll) {
if (binding != null) {
binding.viewPager.setCanScroll(canScroll); binding.viewPager.setCanScroll(canScroll);
} }
} }
@ -60,6 +66,7 @@ public class ExploreFragment extends CommonsDaggerSupportFragment {
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) { @Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
loadNearbyMapData();
binding = FragmentExploreBinding.inflate(inflater, container, false); binding = FragmentExploreBinding.inflate(inflater, container, false);
viewPagerAdapter = new ViewPagerAdapter(getChildFragmentManager()); viewPagerAdapter = new ViewPagerAdapter(getChildFragmentManager());
@ -89,6 +96,11 @@ public class ExploreFragment extends CommonsDaggerSupportFragment {
}); });
setTabs(); setTabs();
setHasOptionsMenu(true); setHasOptionsMenu(true);
// if we came from 'Show in Explore' in Nearby, jump to Map tab
if (isCameFromNearbyMap()) {
binding.viewPager.setCurrentItem(2);
}
return binding.getRoot(); return binding.getRoot();
} }
@ -108,6 +120,13 @@ public class ExploreFragment extends CommonsDaggerSupportFragment {
Bundle mapArguments = new Bundle(); Bundle mapArguments = new Bundle();
mapArguments.putString("categoryName", EXPLORE_MAP); mapArguments.putString("categoryName", EXPLORE_MAP);
// if we came from 'Show in Explore' in Nearby, pass on zoom and center to Explore map root
if (isCameFromNearbyMap()) {
mapArguments.putDouble("prev_zoom", prevZoom);
mapArguments.putDouble("prev_latitude", prevLatitude);
mapArguments.putDouble("prev_longitude", prevLongitude);
}
featuredRootFragment = new ExploreListRootFragment(featuredArguments); featuredRootFragment = new ExploreListRootFragment(featuredArguments);
mobileRootFragment = new ExploreListRootFragment(mobileArguments); mobileRootFragment = new ExploreListRootFragment(mobileArguments);
mapRootFragment = new ExploreMapRootFragment(mapArguments); mapRootFragment = new ExploreMapRootFragment(mapArguments);
@ -120,13 +139,35 @@ public class ExploreFragment extends CommonsDaggerSupportFragment {
fragmentList.add(mapRootFragment); fragmentList.add(mapRootFragment);
titleList.add(getString(R.string.explore_tab_title_map).toUpperCase(Locale.ROOT)); titleList.add(getString(R.string.explore_tab_title_map).toUpperCase(Locale.ROOT));
((MainActivity)getActivity()).showTabs(); ((MainActivity) getActivity()).showTabs();
((BaseActivity) getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(false); ((BaseActivity) getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(false);
viewPagerAdapter.setTabData(fragmentList, titleList); viewPagerAdapter.setTabData(fragmentList, titleList);
viewPagerAdapter.notifyDataSetChanged(); viewPagerAdapter.notifyDataSetChanged();
} }
/**
* Fetch Nearby map camera data from fragment arguments if any.
*/
public void loadNearbyMapData() {
// get fragment arguments
if (getArguments() != null) {
prevZoom = getArguments().getDouble("prev_zoom");
prevLatitude = getArguments().getDouble("prev_latitude");
prevLongitude = getArguments().getDouble("prev_longitude");
}
}
/**
* Checks if fragment arguments contain data from Nearby map. if present, then the user
* navigated from Nearby using 'Show in Explore'.
*
* @return true if user navigated from Nearby map
**/
public boolean isCameFromNearbyMap() {
return prevZoom != 0.0 || prevLatitude != 0.0 || prevLongitude != 0.0;
}
public boolean onBackPressed() { public boolean onBackPressed() {
if (binding.tabLayout.getSelectedTabPosition() == 0) { if (binding.tabLayout.getSelectedTabPosition() == 0) {
if (featuredRootFragment.backPressed()) { if (featuredRootFragment.backPressed()) {
@ -155,7 +196,38 @@ public class ExploreFragment extends CommonsDaggerSupportFragment {
*/ */
@Override @Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.menu_search, menu); // if logged in 'Show in Nearby' menu item is visible
if (applicationKvStore.getBoolean("login_skipped") == false) {
inflater.inflate(R.menu.explore_fragment_menu, menu);
MenuItem others = menu.findItem(R.id.list_item_show_in_nearby);
if (binding.viewPager.getCurrentItem() == 2) {
others.setVisible(true);
}
// if on Map tab, show all menu options, else only show search
binding.viewPager.addOnPageChangeListener(new OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset,
int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
others.setVisible((position == 2));
}
@Override
public void onPageScrollStateChanged(int state) {
if (state == SCROLL_STATE_IDLE && binding.viewPager.getCurrentItem() == 2) {
onPageSelected(2);
}
}
});
} else {
inflater.inflate(R.menu.menu_search, menu);
}
super.onCreateOptionsMenu(menu, inflater); super.onCreateOptionsMenu(menu, inflater);
} }
@ -171,6 +243,9 @@ public class ExploreFragment extends CommonsDaggerSupportFragment {
case R.id.action_search: case R.id.action_search:
ActivityUtils.startActivityWithFlags(getActivity(), SearchActivity.class); ActivityUtils.startActivityWithFlags(getActivity(), SearchActivity.class);
return true; return true;
case R.id.list_item_show_in_nearby:
mapRootFragment.loadNearbyMapFromExplore();
return true;
default: default:
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);
} }

View file

@ -39,10 +39,22 @@ public class ExploreMapRootFragment extends CommonsDaggerSupportFragment impleme
} }
public ExploreMapRootFragment(Bundle bundle) { public ExploreMapRootFragment(Bundle bundle) {
// get fragment arguments
String title = bundle.getString("categoryName"); String title = bundle.getString("categoryName");
double zoom = bundle.getDouble("prev_zoom");
double latitude = bundle.getDouble("prev_latitude");
double longitude = bundle.getDouble("prev_longitude");
mapFragment = new ExploreMapFragment(); mapFragment = new ExploreMapFragment();
Bundle featuredArguments = new Bundle(); Bundle featuredArguments = new Bundle();
featuredArguments.putString("categoryName", title); featuredArguments.putString("categoryName", title);
// if we came from 'Show in Explore' in Nearby, pass on zoom and center
if (zoom != 0.0 || latitude != 0.0 || longitude != 0.0) {
featuredArguments.putDouble("prev_zoom", zoom);
featuredArguments.putDouble("prev_latitude", latitude);
featuredArguments.putDouble("prev_longitude", longitude);
}
mapFragment.setArguments(featuredArguments); mapFragment.setArguments(featuredArguments);
} }
@ -198,7 +210,8 @@ public class ExploreMapRootFragment extends CommonsDaggerSupportFragment impleme
((MainActivity) getActivity()).showTabs(); ((MainActivity) getActivity()).showTabs();
return true; return true;
} if (mapFragment != null && mapFragment.isVisible()) { }
if (mapFragment != null && mapFragment.isVisible()) {
if (mapFragment.backButtonClicked()) { if (mapFragment.backButtonClicked()) {
// Explore map fragment handled the event no further action required. // Explore map fragment handled the event no further action required.
return true; return true;
@ -213,6 +226,10 @@ public class ExploreMapRootFragment extends CommonsDaggerSupportFragment impleme
return false; return false;
} }
public void loadNearbyMapFromExplore() {
mapFragment.loadNearbyMapFromExplore();
}
@Override @Override
public void onDestroy() { public void onDestroy() {
super.onDestroy(); super.onDestroy();

View file

@ -38,6 +38,7 @@ import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import fr.free.nrw.commons.Utils; import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao; import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao;
import fr.free.nrw.commons.contributions.MainActivity;
import fr.free.nrw.commons.databinding.FragmentExploreMapBinding; import fr.free.nrw.commons.databinding.FragmentExploreMapBinding;
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment; import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
import fr.free.nrw.commons.explore.ExploreMapRootFragment; import fr.free.nrw.commons.explore.ExploreMapRootFragment;
@ -115,6 +116,11 @@ public class ExploreMapFragment extends CommonsDaggerSupportFragment
SystemThemeUtils systemThemeUtils; SystemThemeUtils systemThemeUtils;
LocationPermissionsHelper locationPermissionsHelper; LocationPermissionsHelper locationPermissionsHelper;
// Nearby map state (if we came from Nearby)
private double prevZoom;
private double prevLatitude;
private double prevLongitude;
private ExploreMapPresenter presenter; private ExploreMapPresenter presenter;
public FragmentExploreMapBinding binding; public FragmentExploreMapBinding binding;
@ -160,6 +166,7 @@ public class ExploreMapFragment extends CommonsDaggerSupportFragment
ViewGroup container, ViewGroup container,
Bundle savedInstanceState Bundle savedInstanceState
) { ) {
loadNearbyMapData();
binding = FragmentExploreMapBinding.inflate(getLayoutInflater()); binding = FragmentExploreMapBinding.inflate(getLayoutInflater());
return binding.getRoot(); return binding.getRoot();
} }
@ -169,12 +176,14 @@ public class ExploreMapFragment extends CommonsDaggerSupportFragment
super.onViewCreated(view, savedInstanceState); super.onViewCreated(view, savedInstanceState);
setSearchThisAreaButtonVisibility(false); setSearchThisAreaButtonVisibility(false);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
binding.tvAttribution.setText(Html.fromHtml(getString(R.string.map_attribution), Html.FROM_HTML_MODE_LEGACY)); binding.tvAttribution.setText(
Html.fromHtml(getString(R.string.map_attribution), Html.FROM_HTML_MODE_LEGACY));
} else { } else {
binding.tvAttribution.setText(Html.fromHtml(getString(R.string.map_attribution))); binding.tvAttribution.setText(Html.fromHtml(getString(R.string.map_attribution)));
} }
initNetworkBroadCastReceiver(); initNetworkBroadCastReceiver();
locationPermissionsHelper = new LocationPermissionsHelper(getActivity(),locationManager,this); locationPermissionsHelper = new LocationPermissionsHelper(getActivity(), locationManager,
this);
if (presenter == null) { if (presenter == null) {
presenter = new ExploreMapPresenter(bookmarkLocationDao); presenter = new ExploreMapPresenter(bookmarkLocationDao);
} }
@ -204,9 +213,14 @@ public class ExploreMapFragment extends CommonsDaggerSupportFragment
scaleBarOverlay.setBackgroundPaint(barPaint); scaleBarOverlay.setBackgroundPaint(barPaint);
scaleBarOverlay.enableScaleBar(); scaleBarOverlay.enableScaleBar();
binding.mapView.getOverlays().add(scaleBarOverlay); binding.mapView.getOverlays().add(scaleBarOverlay);
binding.mapView.getZoomController().setVisibility(CustomZoomButtonsController.Visibility.NEVER); binding.mapView.getZoomController()
.setVisibility(CustomZoomButtonsController.Visibility.NEVER);
binding.mapView.setMultiTouchControls(true); binding.mapView.setMultiTouchControls(true);
binding.mapView.getController().setZoom(ZOOM_LEVEL);
if (!isCameFromNearbyMap()) {
binding.mapView.getController().setZoom(ZOOM_LEVEL);
}
performMapReadyActions(); performMapReadyActions();
binding.mapView.getOverlays().add(new MapEventsOverlay(new MapEventsReceiver() { binding.mapView.getOverlays().add(new MapEventsOverlay(new MapEventsReceiver() {
@ -328,11 +342,51 @@ public class ExploreMapFragment extends CommonsDaggerSupportFragment
isPermissionDenied = true; isPermissionDenied = true;
} }
lastKnownLocation = MapUtils.getDefaultLatLng(); lastKnownLocation = MapUtils.getDefaultLatLng();
moveCameraToPosition(
new GeoPoint(lastKnownLocation.getLatitude(), lastKnownLocation.getLongitude())); // if we came from 'Show in Explore' in Nearby, load Nearby map center and zoom
if (isCameFromNearbyMap()) {
moveCameraToPosition(
new GeoPoint(prevLatitude, prevLongitude),
prevZoom,
1L
);
} else {
moveCameraToPosition(
new GeoPoint(lastKnownLocation.getLatitude(), lastKnownLocation.getLongitude()));
}
presenter.onMapReady(exploreMapController); presenter.onMapReady(exploreMapController);
} }
/**
* Fetch Nearby map camera data from fragment arguments if any.
*/
public void loadNearbyMapData() {
// get fragment arguments
if (getArguments() != null) {
prevZoom = getArguments().getDouble("prev_zoom");
prevLatitude = getArguments().getDouble("prev_latitude");
prevLongitude = getArguments().getDouble("prev_longitude");
}
}
/**
* Checks if fragment arguments contain data from Nearby map, indicating that the user navigated
* from Nearby using 'Show in Explore'.
*
* @return true if user navigated from Nearby map
**/
public boolean isCameFromNearbyMap() {
return prevZoom != 0.0 || prevLatitude != 0.0 || prevLongitude != 0.0;
}
public void loadNearbyMapFromExplore() {
((MainActivity) getContext()).loadNearbyMapFromExplore(
binding.mapView.getZoomLevelDouble(),
binding.mapView.getMapCenter().getLatitude(),
binding.mapView.getMapCenter().getLongitude()
);
}
private void initViews() { private void initViews() {
Timber.d("init views called"); Timber.d("init views called");
initBottomSheets(); initBottomSheets();
@ -346,7 +400,8 @@ public class ExploreMapFragment extends CommonsDaggerSupportFragment
*/ */
@SuppressLint("ClickableViewAccessibility") @SuppressLint("ClickableViewAccessibility")
private void initBottomSheets() { private void initBottomSheets() {
bottomSheetDetailsBehavior = BottomSheetBehavior.from(binding.bottomSheetDetailsBinding.getRoot()); bottomSheetDetailsBehavior = BottomSheetBehavior.from(
binding.bottomSheetDetailsBinding.getRoot());
bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_HIDDEN); bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
binding.bottomSheetDetailsBinding.getRoot().setVisibility(View.VISIBLE); binding.bottomSheetDetailsBinding.getRoot().setVisibility(View.VISIBLE);
} }
@ -404,7 +459,8 @@ public class ExploreMapFragment extends CommonsDaggerSupportFragment
if (currentLatLng == null) { if (currentLatLng == null) {
return; return;
} }
if (currentLatLng.equals(getLastMapFocus())) { // Means we are checking around current location if (currentLatLng.equals(
getLastMapFocus())) { // Means we are checking around current location
nearbyPlacesInfoObservable = presenter.loadAttractionsFromLocation(currentLatLng, nearbyPlacesInfoObservable = presenter.loadAttractionsFromLocation(currentLatLng,
getLastMapFocus(), true); getLastMapFocus(), true);
} else { } else {
@ -416,11 +472,12 @@ public class ExploreMapFragment extends CommonsDaggerSupportFragment
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe(explorePlacesInfo -> { .subscribe(explorePlacesInfo -> {
mediaList = explorePlacesInfo.mediaList; mediaList = explorePlacesInfo.mediaList;
if(mediaList == null) { if (mediaList == null) {
showResponseMessage(getString(R.string.no_pictures_in_this_area)); showResponseMessage(getString(R.string.no_pictures_in_this_area));
} }
updateMapMarkers(explorePlacesInfo); updateMapMarkers(explorePlacesInfo);
lastMapFocus = new GeoPoint(currentLatLng.getLatitude(), currentLatLng.getLongitude()); lastMapFocus = new GeoPoint(currentLatLng.getLatitude(),
currentLatLng.getLongitude());
}, },
throwable -> { throwable -> {
Timber.d(throwable); Timber.d(throwable);
@ -474,9 +531,9 @@ public class ExploreMapFragment extends CommonsDaggerSupportFragment
locationManager.requestLocationUpdatesFromProvider(LocationManager.NETWORK_PROVIDER); locationManager.requestLocationUpdatesFromProvider(LocationManager.NETWORK_PROVIDER);
locationManager.requestLocationUpdatesFromProvider(LocationManager.GPS_PROVIDER); locationManager.requestLocationUpdatesFromProvider(LocationManager.GPS_PROVIDER);
setProgressBarVisibility(true); setProgressBarVisibility(true);
} } else {
else { locationPermissionsHelper.showLocationOffDialog(getActivity(),
locationPermissionsHelper.showLocationOffDialog(getActivity(), R.string.ask_to_turn_location_on_text); R.string.ask_to_turn_location_on_text);
} }
presenter.onMapReady(exploreMapController); presenter.onMapReady(exploreMapController);
registerUnregisterLocationListener(false); registerUnregisterLocationListener(false);
@ -508,7 +565,8 @@ public class ExploreMapFragment extends CommonsDaggerSupportFragment
recenterToUserLocation = true; recenterToUserLocation = true;
return; return;
} }
recenterMarkerToPosition(new GeoPoint(currentLatLng.getLatitude(), currentLatLng.getLongitude())); recenterMarkerToPosition(
new GeoPoint(currentLatLng.getLatitude(), currentLatLng.getLongitude()));
binding.mapView.getController() binding.mapView.getController()
.animateTo(new GeoPoint(currentLatLng.getLatitude(), currentLatLng.getLongitude())); .animateTo(new GeoPoint(currentLatLng.getLatitude(), currentLatLng.getLongitude()));
if (lastMapFocus != null) { if (lastMapFocus != null) {
@ -549,7 +607,8 @@ public class ExploreMapFragment extends CommonsDaggerSupportFragment
view -> Utils.handleGeoCoordinates(getActivity(), view -> Utils.handleGeoCoordinates(getActivity(),
place.getLocation(), binding.mapView.getZoomLevelDouble())); place.getLocation(), binding.mapView.getZoomLevelDouble()));
binding.bottomSheetDetailsBinding.commonsButton.setVisibility(place.hasCommonsLink() ? View.VISIBLE : View.GONE); binding.bottomSheetDetailsBinding.commonsButton.setVisibility(
place.hasCommonsLink() ? View.VISIBLE : View.GONE);
binding.bottomSheetDetailsBinding.commonsButton.setOnClickListener( binding.bottomSheetDetailsBinding.commonsButton.setOnClickListener(
view -> Utils.handleWebUrl(getContext(), place.siteLinks.getCommonsLink())); view -> Utils.handleWebUrl(getContext(), place.siteLinks.getCommonsLink()));
@ -563,7 +622,8 @@ public class ExploreMapFragment extends CommonsDaggerSupportFragment
} }
index++; index++;
} }
binding.bottomSheetDetailsBinding.title.setText(place.name.substring(5, place.name.lastIndexOf("."))); binding.bottomSheetDetailsBinding.title.setText(
place.name.substring(5, place.name.lastIndexOf(".")));
binding.bottomSheetDetailsBinding.category.setText(place.distance); binding.bottomSheetDetailsBinding.category.setText(place.distance);
// Remove label since it is double information // Remove label since it is double information
String descriptionText = place.getLongDescription() String descriptionText = place.getLongDescription()
@ -641,40 +701,43 @@ public class ExploreMapFragment extends CommonsDaggerSupportFragment
* @param nearbyBaseMarker The NearbyBaseMarker object representing the marker to be added. * @param nearbyBaseMarker The NearbyBaseMarker object representing the marker to be added.
*/ */
private void addMarkerToMap(BaseMarker nearbyBaseMarker) { private void addMarkerToMap(BaseMarker nearbyBaseMarker) {
ArrayList<OverlayItem> items = new ArrayList<>(); if (isAttachedToActivity()) {
Bitmap icon = nearbyBaseMarker.getIcon(); ArrayList<OverlayItem> items = new ArrayList<>();
Drawable d = new BitmapDrawable(getResources(), icon); Bitmap icon = nearbyBaseMarker.getIcon();
GeoPoint point = new GeoPoint( Drawable d = new BitmapDrawable(getResources(), icon);
nearbyBaseMarker.getPlace().location.getLatitude(), GeoPoint point = new GeoPoint(
nearbyBaseMarker.getPlace().location.getLongitude()); nearbyBaseMarker.getPlace().location.getLatitude(),
OverlayItem item = new OverlayItem(nearbyBaseMarker.getPlace().name, null, nearbyBaseMarker.getPlace().location.getLongitude());
point); OverlayItem item = new OverlayItem(nearbyBaseMarker.getPlace().name, null,
item.setMarker(d); point);
items.add(item); item.setMarker(d);
ItemizedOverlayWithFocus overlay = new ItemizedOverlayWithFocus(items, items.add(item);
new OnItemGestureListener<OverlayItem>() { ItemizedOverlayWithFocus overlay = new ItemizedOverlayWithFocus(items,
@Override new OnItemGestureListener<OverlayItem>() {
public boolean onItemSingleTapUp(int index, OverlayItem item) { @Override
final Place place = nearbyBaseMarker.getPlace(); public boolean onItemSingleTapUp(int index, OverlayItem item) {
if (clickedMarker != null) { final Place place = nearbyBaseMarker.getPlace();
removeMarker(clickedMarker); if (clickedMarker != null) {
addMarkerToMap(clickedMarker); removeMarker(clickedMarker);
bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_HIDDEN); addMarkerToMap(clickedMarker);
bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED); bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
bottomSheetDetailsBehavior.setState(
BottomSheetBehavior.STATE_COLLAPSED);
}
clickedMarker = nearbyBaseMarker;
passInfoToSheet(place);
return true;
} }
clickedMarker = nearbyBaseMarker;
passInfoToSheet(place);
return true;
}
@Override @Override
public boolean onItemLongPress(int index, OverlayItem item) { public boolean onItemLongPress(int index, OverlayItem item) {
return false; return false;
} }
}, getContext()); }, getContext());
overlay.setFocusItemsOnTap(true); overlay.setFocusItemsOnTap(true);
binding.mapView.getOverlays().add(overlay); // Add the overlay to the map binding.mapView.getOverlays().add(overlay); // Add the overlay to the map
}
} }
/** /**
@ -708,68 +771,72 @@ public class ExploreMapFragment extends CommonsDaggerSupportFragment
*/ */
@Override @Override
public void clearAllMarkers() { public void clearAllMarkers() {
binding.mapView.getOverlayManager().clear(); if (isAttachedToActivity()) {
GeoPoint geoPoint = mapCenter; binding.mapView.getOverlayManager().clear();
if (geoPoint != null) { GeoPoint geoPoint = mapCenter;
List<Overlay> overlays = binding.mapView.getOverlays(); if (geoPoint != null) {
ScaleDiskOverlay diskOverlay = List<Overlay> overlays = binding.mapView.getOverlays();
new ScaleDiskOverlay(this.getContext(), ScaleDiskOverlay diskOverlay =
geoPoint, 2000, GeoConstants.UnitOfMeasure.foot); new ScaleDiskOverlay(this.getContext(),
Paint circlePaint = new Paint(); geoPoint, 2000, GeoConstants.UnitOfMeasure.foot);
circlePaint.setColor(Color.rgb(128, 128, 128)); Paint circlePaint = new Paint();
circlePaint.setStyle(Paint.Style.STROKE); circlePaint.setColor(Color.rgb(128, 128, 128));
circlePaint.setStrokeWidth(2f); circlePaint.setStyle(Paint.Style.STROKE);
diskOverlay.setCirclePaint2(circlePaint); circlePaint.setStrokeWidth(2f);
Paint diskPaint = new Paint(); diskOverlay.setCirclePaint2(circlePaint);
diskPaint.setColor(Color.argb(40, 128, 128, 128)); Paint diskPaint = new Paint();
diskPaint.setStyle(Paint.Style.FILL_AND_STROKE); diskPaint.setColor(Color.argb(40, 128, 128, 128));
diskOverlay.setCirclePaint1(diskPaint); diskPaint.setStyle(Paint.Style.FILL_AND_STROKE);
diskOverlay.setDisplaySizeMin(900); diskOverlay.setCirclePaint1(diskPaint);
diskOverlay.setDisplaySizeMax(1700); diskOverlay.setDisplaySizeMin(900);
binding.mapView.getOverlays().add(diskOverlay); diskOverlay.setDisplaySizeMax(1700);
org.osmdroid.views.overlay.Marker startMarker = new org.osmdroid.views.overlay.Marker( binding.mapView.getOverlays().add(diskOverlay);
binding.mapView); org.osmdroid.views.overlay.Marker startMarker = new org.osmdroid.views.overlay.Marker(
startMarker.setPosition(geoPoint); binding.mapView);
startMarker.setAnchor(org.osmdroid.views.overlay.Marker.ANCHOR_CENTER, startMarker.setPosition(geoPoint);
org.osmdroid.views.overlay.Marker.ANCHOR_BOTTOM); startMarker.setAnchor(org.osmdroid.views.overlay.Marker.ANCHOR_CENTER,
startMarker.setIcon( org.osmdroid.views.overlay.Marker.ANCHOR_BOTTOM);
ContextCompat.getDrawable(this.getContext(), R.drawable.current_location_marker)); startMarker.setIcon(
startMarker.setTitle("Your Location"); ContextCompat.getDrawable(this.getContext(),
startMarker.setTextLabelFontSize(24); R.drawable.current_location_marker));
binding.mapView.getOverlays().add(startMarker); startMarker.setTitle("Your Location");
} startMarker.setTextLabelFontSize(24);
ScaleBarOverlay scaleBarOverlay = new ScaleBarOverlay(binding.mapView); binding.mapView.getOverlays().add(startMarker);
scaleBarOverlay.setScaleBarOffset(15, 25);
Paint barPaint = new Paint();
barPaint.setARGB(200, 255, 250, 250);
scaleBarOverlay.setBackgroundPaint(barPaint);
scaleBarOverlay.enableScaleBar();
binding.mapView.getOverlays().add(scaleBarOverlay);
binding.mapView.getOverlays().add(new MapEventsOverlay(new MapEventsReceiver() {
@Override
public boolean singleTapConfirmedHelper(GeoPoint p) {
if (clickedMarker != null) {
removeMarker(clickedMarker);
addMarkerToMap(clickedMarker);
binding.mapView.invalidate();
} else {
Timber.e("CLICKED MARKER IS NULL");
}
if (bottomSheetDetailsBehavior.getState() == BottomSheetBehavior.STATE_EXPANDED) {
// Back should first hide the bottom sheet if it is expanded
bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
} else if (isDetailsBottomSheetVisible()) {
hideBottomDetailsSheet();
}
return true;
} }
ScaleBarOverlay scaleBarOverlay = new ScaleBarOverlay(binding.mapView);
scaleBarOverlay.setScaleBarOffset(15, 25);
Paint barPaint = new Paint();
barPaint.setARGB(200, 255, 250, 250);
scaleBarOverlay.setBackgroundPaint(barPaint);
scaleBarOverlay.enableScaleBar();
binding.mapView.getOverlays().add(scaleBarOverlay);
binding.mapView.getOverlays().add(new MapEventsOverlay(new MapEventsReceiver() {
@Override
public boolean singleTapConfirmedHelper(GeoPoint p) {
if (clickedMarker != null) {
removeMarker(clickedMarker);
addMarkerToMap(clickedMarker);
binding.mapView.invalidate();
} else {
Timber.e("CLICKED MARKER IS NULL");
}
if (bottomSheetDetailsBehavior.getState()
== BottomSheetBehavior.STATE_EXPANDED) {
// Back should first hide the bottom sheet if it is expanded
bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
} else if (isDetailsBottomSheetVisible()) {
hideBottomDetailsSheet();
}
return true;
}
@Override @Override
public boolean longPressHelper(GeoPoint p) { public boolean longPressHelper(GeoPoint p) {
return false; return false;
} }
})); }));
binding.mapView.setMultiTouchControls(true); binding.mapView.setMultiTouchControls(true);
}
} }
/** /**
@ -826,6 +893,18 @@ public class ExploreMapFragment extends CommonsDaggerSupportFragment
binding.mapView.getController().animateTo(geoPoint); binding.mapView.getController().animateTo(geoPoint);
} }
/**
* Moves the camera of the map view to the specified GeoPoint at specified zoom level and speed
* using an animation.
*
* @param geoPoint The GeoPoint representing the new camera position for the map.
* @param zoom Zoom level of the map camera
* @param speed Speed of animation
*/
private void moveCameraToPosition(GeoPoint geoPoint, double zoom, long speed) {
binding.mapView.getController().animateTo(geoPoint, zoom, speed);
}
@Override @Override
public fr.free.nrw.commons.location.LatLng getLastMapFocus() { public fr.free.nrw.commons.location.LatLng getLastMapFocus() {
return lastMapFocus == null ? getMapCenter() : new fr.free.nrw.commons.location.LatLng( return lastMapFocus == null ? getMapCenter() : new fr.free.nrw.commons.location.LatLng(
@ -851,14 +930,17 @@ public class ExploreMapFragment extends CommonsDaggerSupportFragment
-0.07483536015053005, 1f); -0.07483536015053005, 1f);
} }
} }
moveCameraToPosition(new GeoPoint(latLnge.getLatitude(),latLnge.getLongitude())); if (!isCameFromNearbyMap()) {
moveCameraToPosition(new GeoPoint(latLnge.getLatitude(), latLnge.getLongitude()));
}
return latLnge; return latLnge;
} }
@Override @Override
public fr.free.nrw.commons.location.LatLng getMapFocus() { public fr.free.nrw.commons.location.LatLng getMapFocus() {
fr.free.nrw.commons.location.LatLng mapFocusedLatLng = new fr.free.nrw.commons.location.LatLng( fr.free.nrw.commons.location.LatLng mapFocusedLatLng = new fr.free.nrw.commons.location.LatLng(
binding.mapView.getMapCenter().getLatitude(), binding.mapView.getMapCenter().getLongitude(), 100); binding.mapView.getMapCenter().getLatitude(),
binding.mapView.getMapCenter().getLongitude(), 100);
return mapFocusedLatLng; return mapFocusedLatLng;
} }
@ -911,9 +993,19 @@ public class ExploreMapFragment extends CommonsDaggerSupportFragment
}; };
} }
@Override /**
public void onLocationPermissionDenied(String toastMessage) {} * helper function to confirm that this fragment has been attached.
**/
public boolean isAttachedToActivity() {
boolean attached = isVisible() && getActivity() != null;
return attached;
}
@Override @Override
public void onLocationPermissionGranted() {} public void onLocationPermissionDenied(String toastMessage) {
}
@Override
public void onLocationPermissionGranted() {
}
} }

View file

@ -233,6 +233,11 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
private Place nearestPlace; private Place nearestPlace;
private volatile boolean stopQuery; private volatile boolean stopQuery;
// Explore map data (for if we came from Explore)
private double prevZoom;
private double prevLatitude;
private double prevLongitude;
private final Handler searchHandler = new Handler(); private final Handler searchHandler = new Handler();
private Runnable searchRunnable; private Runnable searchRunnable;
@ -247,27 +252,28 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
private final ActivityResultLauncher<Intent> galleryPickLauncherForResult = private final ActivityResultLauncher<Intent> galleryPickLauncherForResult =
registerForActivityResult(new StartActivityForResult(), registerForActivityResult(new StartActivityForResult(),
result -> { result -> {
controller.handleActivityResultWithCallback(requireActivity(), callbacks -> { controller.handleActivityResultWithCallback(requireActivity(), callbacks -> {
controller.onPictureReturnedFromGallery(result, requireActivity(), callbacks); controller.onPictureReturnedFromGallery(result, requireActivity(), callbacks);
});
}); });
});
private final ActivityResultLauncher<Intent> customSelectorLauncherForResult = private final ActivityResultLauncher<Intent> customSelectorLauncherForResult =
registerForActivityResult(new StartActivityForResult(), registerForActivityResult(new StartActivityForResult(),
result -> { result -> {
controller.handleActivityResultWithCallback(requireActivity(), callbacks -> { controller.handleActivityResultWithCallback(requireActivity(), callbacks -> {
controller.onPictureReturnedFromCustomSelector(result, requireActivity(), callbacks); controller.onPictureReturnedFromCustomSelector(result, requireActivity(),
callbacks);
});
}); });
});
private final ActivityResultLauncher<Intent> cameraPickLauncherForResult = private final ActivityResultLauncher<Intent> cameraPickLauncherForResult =
registerForActivityResult(new StartActivityForResult(), registerForActivityResult(new StartActivityForResult(),
result -> { result -> {
controller.handleActivityResultWithCallback(requireActivity(), callbacks -> { controller.handleActivityResultWithCallback(requireActivity(), callbacks -> {
controller.onPictureReturnedFromCamera(result, requireActivity(), callbacks); controller.onPictureReturnedFromCamera(result, requireActivity(), callbacks);
});
}); });
});
private ActivityResultLauncher<String[]> inAppCameraLocationPermissionLauncher = registerForActivityResult( private ActivityResultLauncher<String[]> inAppCameraLocationPermissionLauncher = registerForActivityResult(
new RequestMultiplePermissions(), new RequestMultiplePermissions(),
@ -337,12 +343,15 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
@Override @Override
public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup container, public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup container,
final Bundle savedInstanceState) { final Bundle savedInstanceState) {
loadExploreMapData();
binding = FragmentNearbyParentBinding.inflate(inflater, container, false); binding = FragmentNearbyParentBinding.inflate(inflater, container, false);
view = binding.getRoot(); view = binding.getRoot();
initNetworkBroadCastReceiver(); initNetworkBroadCastReceiver();
scope = LifecycleOwnerKt.getLifecycleScope(getViewLifecycleOwner()); scope = LifecycleOwnerKt.getLifecycleScope(getViewLifecycleOwner());
presenter = new NearbyParentFragmentPresenter(bookmarkLocationDao, placesRepository, nearbyController); presenter = new NearbyParentFragmentPresenter(bookmarkLocationDao, placesRepository,
nearbyController);
progressDialog = new ProgressDialog(getActivity()); progressDialog = new ProgressDialog(getActivity());
progressDialog.setCancelable(false); progressDialog.setCancelable(false);
progressDialog.setMessage("Saving in progress..."); progressDialog.setMessage("Saving in progress...");
@ -359,6 +368,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
inflater.inflate(R.menu.nearby_fragment_menu, menu); inflater.inflate(R.menu.nearby_fragment_menu, menu);
MenuItem refreshButton = menu.findItem(R.id.item_refresh); MenuItem refreshButton = menu.findItem(R.id.item_refresh);
MenuItem listMenu = menu.findItem(R.id.list_sheet); MenuItem listMenu = menu.findItem(R.id.list_sheet);
MenuItem showInExploreButton = menu.findItem(R.id.list_item_show_in_explore);
MenuItem saveAsGPXButton = menu.findItem(R.id.list_item_gpx); MenuItem saveAsGPXButton = menu.findItem(R.id.list_item_gpx);
MenuItem saveAsKMLButton = menu.findItem(R.id.list_item_kml); MenuItem saveAsKMLButton = menu.findItem(R.id.list_item_kml);
refreshButton.setOnMenuItemClickListener(new OnMenuItemClickListener() { refreshButton.setOnMenuItemClickListener(new OnMenuItemClickListener() {
@ -379,6 +389,17 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
return false; return false;
} }
}); });
showInExploreButton.setOnMenuItemClickListener(new OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(@NonNull MenuItem item) {
((MainActivity) getContext()).loadExploreMapFromNearby(
binding.map.getZoomLevelDouble(),
binding.map.getMapCenter().getLatitude(),
binding.map.getMapCenter().getLongitude()
);
return false;
}
});
saveAsGPXButton.setOnMenuItemClickListener(new OnMenuItemClickListener() { saveAsGPXButton.setOnMenuItemClickListener(new OnMenuItemClickListener() {
@Override @Override
@ -467,6 +488,14 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
binding.map.getOverlays().add(scaleBarOverlay); binding.map.getOverlays().add(scaleBarOverlay);
binding.map.getZoomController().setVisibility(Visibility.NEVER); binding.map.getZoomController().setVisibility(Visibility.NEVER);
binding.map.getController().setZoom(ZOOM_LEVEL); binding.map.getController().setZoom(ZOOM_LEVEL);
// if we came from Explore map using 'Show in Nearby', load Explore map camera position
if (isCameFromExploreMap()) {
moveCameraToPosition(
new GeoPoint(prevLatitude, prevLongitude),
prevZoom,
1L
);
}
binding.map.getOverlays().add(mapEventsOverlay); binding.map.getOverlays().add(mapEventsOverlay);
binding.map.addMapListener(new MapListener() { binding.map.addMapListener(new MapListener() {
@ -489,11 +518,14 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
} }
initNearbyFilter(); initNearbyFilter();
addCheckBoxCallback(); addCheckBoxCallback();
moveCameraToPosition(lastMapFocus); if (!isCameFromExploreMap()) {
moveCameraToPosition(lastMapFocus);
}
initRvNearbyList(); initRvNearbyList();
onResume(); onResume();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
binding.tvAttribution.setText(Html.fromHtml(getString(R.string.map_attribution), Html.FROM_HTML_MODE_LEGACY)); binding.tvAttribution.setText(
Html.fromHtml(getString(R.string.map_attribution), Html.FROM_HTML_MODE_LEGACY));
} else { } else {
//noinspection deprecation //noinspection deprecation
binding.tvAttribution.setText(Html.fromHtml(getString(R.string.map_attribution))); binding.tvAttribution.setText(Html.fromHtml(getString(R.string.map_attribution)));
@ -545,6 +577,28 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
} }
} }
/**
* Fetch Explore map camera data from fragment arguments if any.
*/
public void loadExploreMapData() {
// get fragment arguments
if (getArguments() != null) {
prevZoom = getArguments().getDouble("prev_zoom");
prevLatitude = getArguments().getDouble("prev_latitude");
prevLongitude = getArguments().getDouble("prev_longitude");
}
}
/**
* Checks if fragment arguments contain data from Explore map. if present, then the user
* navigated from Explore using 'Show in Nearby'.
*
* @return true if user navigated from Explore map
**/
public boolean isCameFromExploreMap() {
return prevZoom != 0.0 || prevLatitude != 0.0 || prevLongitude != 0.0;
}
/** /**
* Initialise background based on theme, this should be doe ideally via styles, that would need * Initialise background based on theme, this should be doe ideally via styles, that would need
* another refactor * another refactor
@ -625,7 +679,9 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
mapCenter = targetP; mapCenter = targetP;
binding.map.getController().setCenter(targetP); binding.map.getController().setCenter(targetP);
recenterMarkerToPosition(targetP); recenterMarkerToPosition(targetP);
moveCameraToPosition(targetP); if (!isCameFromExploreMap()) {
moveCameraToPosition(targetP);
}
} else if (locationManager.isGPSProviderEnabled() } else if (locationManager.isGPSProviderEnabled()
|| locationManager.isNetworkProviderEnabled()) { || locationManager.isNetworkProviderEnabled()) {
locationManager.requestLocationUpdatesFromProvider(LocationManager.NETWORK_PROVIDER); locationManager.requestLocationUpdatesFromProvider(LocationManager.NETWORK_PROVIDER);
@ -669,7 +725,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
} else { } else {
lastKnownLocation = MapUtils.getDefaultLatLng(); lastKnownLocation = MapUtils.getDefaultLatLng();
} }
if (binding.map != null) { if (binding.map != null && !isCameFromExploreMap()) {
moveCameraToPosition( moveCameraToPosition(
new GeoPoint(lastKnownLocation.getLatitude(), lastKnownLocation.getLongitude())); new GeoPoint(lastKnownLocation.getLatitude(), lastKnownLocation.getLongitude()));
} }
@ -739,8 +795,8 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
} }
/** /**
* Determines the number of spans (columns) in the RecyclerView based on device orientation * Determines the number of spans (columns) in the RecyclerView based on device orientation and
* and adapter item count. * adapter item count.
* *
* @return The number of spans to be used in the RecyclerView. * @return The number of spans to be used in the RecyclerView.
*/ */
@ -1175,7 +1231,6 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
/** /**
* Clears the Nearby local cache and then calls for pin details to be fetched afresh. * Clears the Nearby local cache and then calls for pin details to be fetched afresh.
*
*/ */
private void emptyCache() { private void emptyCache() {
// reload the map once the cache is cleared // reload the map once the cache is cleared
@ -1338,7 +1393,8 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
} }
/** /**
* Fetches and updates the data for a specific place, then updates the corresponding marker on the map. * Fetches and updates the data for a specific place, then updates the corresponding marker on
* the map.
* *
* @param entity The entity ID of the place. * @param entity The entity ID of the place.
* @param place The Place object containing the initial place data. * @param place The Place object containing the initial place data.
@ -1469,9 +1525,8 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
} }
/** /**
* Stops any ongoing queries and clears all disposables. * Stops any ongoing queries and clears all disposables. This method sets the stopQuery flag to
* This method sets the stopQuery flag to true and clears the compositeDisposable * true and clears the compositeDisposable to prevent any further processing.
* to prevent any further processing.
*/ */
@Override @Override
public void stopQuery() { public void stopQuery() {
@ -1624,7 +1679,8 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
new Builder(getContext()) new Builder(getContext())
.setMessage(R.string.login_alert_message) .setMessage(R.string.login_alert_message)
.setCancelable(false) .setCancelable(false)
.setNegativeButton(R.string.cancel, (dialog, which) -> {}) .setNegativeButton(R.string.cancel, (dialog, which) -> {
})
.setPositiveButton(R.string.login, (dialog, which) -> { .setPositiveButton(R.string.login, (dialog, which) -> {
// logout of the app // logout of the app
BaseLogoutListener logoutListener = new BaseLogoutListener(getActivity()); BaseLogoutListener logoutListener = new BaseLogoutListener(getActivity());
@ -1743,7 +1799,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
final boolean filterForPlaceState, final boolean filterForPlaceState,
final boolean filterForAllNoneType) { final boolean filterForAllNoneType) {
final boolean displayExists = false; final boolean displayExists = false;
final boolean displayNeedsPhoto= false; final boolean displayNeedsPhoto = false;
final boolean displayWlm = false; final boolean displayWlm = false;
if (selectedLabels == null || selectedLabels.size() == 0) { if (selectedLabels == null || selectedLabels.size() == 0) {
replaceMarkerOverlays(NearbyController.markerLabelList); replaceMarkerOverlays(NearbyController.markerLabelList);
@ -1903,8 +1959,8 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
/** /**
* Adds multiple markers representing places to the map and handles item gestures. * Adds multiple markers representing places to the map and handles item gestures.
* *
* @param markerPlaceGroups The list of marker place groups containing the places and * @param markerPlaceGroups The list of marker place groups containing the places and their
* their bookmarked status * bookmarked status
*/ */
@Override @Override
public void replaceMarkerOverlays(final List<MarkerPlaceGroup> markerPlaceGroups) { public void replaceMarkerOverlays(final List<MarkerPlaceGroup> markerPlaceGroups) {
@ -1913,7 +1969,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
for (int i = markerPlaceGroups.size() - 1; i >= 0; i--) { for (int i = markerPlaceGroups.size() - 1; i >= 0; i--) {
newMarkers.add( newMarkers.add(
convertToMarker(markerPlaceGroups.get(i).getPlace(), convertToMarker(markerPlaceGroups.get(i).getPlace(),
markerPlaceGroups.get(i).getIsBookmarked()) markerPlaceGroups.get(i).getIsBookmarked())
); );
} }
clearAllMarkers(); clearAllMarkers();
@ -2103,7 +2159,8 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
if (binding.fabCamera.isShown()) { if (binding.fabCamera.isShown()) {
Timber.d("Camera button tapped. Place: %s", selectedPlace.toString()); Timber.d("Camera button tapped. Place: %s", selectedPlace.toString());
storeSharedPrefs(selectedPlace); storeSharedPrefs(selectedPlace);
controller.initiateCameraPick(getActivity(), inAppCameraLocationPermissionLauncher, cameraPickLauncherForResult); controller.initiateCameraPick(getActivity(), inAppCameraLocationPermissionLauncher,
cameraPickLauncherForResult);
} }
}); });
@ -2121,7 +2178,8 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
if (binding.fabCustomGallery.isShown()) { if (binding.fabCustomGallery.isShown()) {
Timber.d("Gallery button tapped. Place: %s", selectedPlace.toString()); Timber.d("Gallery button tapped. Place: %s", selectedPlace.toString());
storeSharedPrefs(selectedPlace); storeSharedPrefs(selectedPlace);
controller.initiateCustomGalleryPickWithPermission(getActivity(), customSelectorLauncherForResult); controller.initiateCustomGalleryPickWithPermission(getActivity(),
customSelectorLauncherForResult);
} }
}); });
} }
@ -2296,6 +2354,18 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
binding.map.getController().animateTo(geoPoint); binding.map.getController().animateTo(geoPoint);
} }
/**
* Moves the camera of the map view to the specified GeoPoint at specified zoom level and speed
* using an animation.
*
* @param geoPoint The GeoPoint representing the new camera position for the map.
* @param zoom Zoom level of the map camera
* @param speed Speed of animation
*/
private void moveCameraToPosition(GeoPoint geoPoint, double zoom, long speed) {
binding.map.getController().animateTo(geoPoint, zoom, speed);
}
@Override @Override
public void onBottomSheetItemClick(@Nullable View view, int position) { public void onBottomSheetItemClick(@Nullable View view, int position) {
BottomSheetItem item = dataList.get(position); BottomSheetItem item = dataList.get(position);

View file

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
>
<item
android:id="@+id/action_search"
android:title="@string/menu_search_button"
android:icon="?attr/search_icon"
android:orderInCategory="1"
app:showAsAction="ifRoom"
/>
<item android:id="@+id/list_item_show_in_nearby"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:title="@string/show_in_nearby"
android:visible="false"
/>
</menu>

View file

@ -12,6 +12,12 @@
android:icon="@drawable/ic_list_white_24dp" android:icon="@drawable/ic_list_white_24dp"
/> />
<item android:id="@+id/list_item_show_in_explore"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:title="@string/show_in_explore"
/>
<item android:id="@+id/list_item_gpx" <item android:id="@+id/list_item_gpx"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"

View file

@ -869,4 +869,6 @@ Upload your first media by tapping on the add button.</string>
<string name="caption_copied_to_clipboard">Caption copied to clipboard</string> <string name="caption_copied_to_clipboard">Caption copied to clipboard</string>
<string name="congratulations_all_pictures_in_this_album_have_been_either_uploaded_or_marked_as_not_for_upload">Congratulations, all pictures in this album have been either uploaded or marked as not for upload.</string> <string name="congratulations_all_pictures_in_this_album_have_been_either_uploaded_or_marked_as_not_for_upload">Congratulations, all pictures in this album have been either uploaded or marked as not for upload.</string>
<string name="show_in_explore">Show in Explore</string>
<string name="show_in_nearby">Show in Nearby</string>
</resources> </resources>

View file

@ -11,16 +11,19 @@ import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentTransaction import androidx.fragment.app.FragmentTransaction
import androidx.test.core.app.ApplicationProvider import androidx.test.core.app.ApplicationProvider
import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayout
import com.nhaarman.mockitokotlin2.eq
import fr.free.nrw.commons.OkHttpConnectionFactory import fr.free.nrw.commons.OkHttpConnectionFactory
import fr.free.nrw.commons.R import fr.free.nrw.commons.R
import fr.free.nrw.commons.TestCommonsApplication import fr.free.nrw.commons.TestCommonsApplication
import fr.free.nrw.commons.contributions.MainActivity import fr.free.nrw.commons.contributions.MainActivity
import fr.free.nrw.commons.createTestClient import fr.free.nrw.commons.createTestClient
import org.junit.Assert import org.junit.Assert
import org.junit.Assert.assertTrue
import org.junit.Before import org.junit.Before
import org.junit.Ignore import org.junit.Ignore
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Mock import org.mockito.Mock
import org.mockito.Mockito.verify import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` import org.mockito.Mockito.`when`
@ -34,6 +37,7 @@ import org.robolectric.annotation.LooperMode
import org.robolectric.fakes.RoboMenu import org.robolectric.fakes.RoboMenu
import org.robolectric.fakes.RoboMenuItem import org.robolectric.fakes.RoboMenuItem
@RunWith(RobolectricTestRunner::class) @RunWith(RobolectricTestRunner::class)
@Config(sdk = [21], application = TestCommonsApplication::class) @Config(sdk = [21], application = TestCommonsApplication::class)
@LooperMode(LooperMode.Mode.PAUSED) @LooperMode(LooperMode.Mode.PAUSED)
@ -151,6 +155,14 @@ class ExploreFragmentUnitTest {
Shadows.shadowOf(getMainLooper()).idle() Shadows.shadowOf(getMainLooper()).idle()
val menu: Menu = RoboMenu(context) val menu: Menu = RoboMenu(context)
fragment.onCreateOptionsMenu(menu, inflater) fragment.onCreateOptionsMenu(menu, inflater)
verify(inflater).inflate(R.menu.menu_search, menu)
val captor = ArgumentCaptor.forClass(
Int::class.java
)
verify(inflater).inflate(captor.capture(), eq(menu))
val capturedLayout = captor.value
assertTrue(capturedLayout == R.menu.menu_search || capturedLayout == R.menu.explore_fragment_menu)
} }
} }