mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-27 12:53:55 +01:00
Explore nearby pictures (#4910)
* Add map fragment for explore and search * Create structure of explore map by defining view and user action interfaces to presenter * Add methods to map start, bottom sheet and permission * Make the simple map visible * Imitate methods from nearby map for permission needed and non needed map initialisation operations, however, needs to be tested and reactor * a level of abstraction * update media params to include coordinates * Implement pageable presenter to explore * Create Root fragment for map and media * Iplement two presenter one for map operations, the other for pageable operations * Construct general structure for both explore with search query and just by location * fix injection issue * Make default explore work with zoom level * increase offscreen page limit with newly added fragment * Make two distinct api calls for search with and without query * Before trying to use same presenter for both search with and without query * Add notes for Madhur * Add Madhur's fixes for binding * Call serch with and without query from the same spot * partially solve zoom issue * Make tab view unswipble while map is being used on search activity * make viewpager unswipable while map is being used * Code cleanup and reverting unnecessry edits * Add search this area methods * Implement search this area button functionality * Fix search this area button, current location FAB and bottom attribution UI elements * Add marker click action * Solve bookmarkdao injection issue * Make display bottom sheet details on marker click * remove label and set bottom sheet behavior * Remove irrelevan buttons like wikidata and article buttons * Cancel bookmark feature for commons images for know, needs to be thought * Add search this area button * Add location off dialog * Implement back button for explore map fragment while not on search activity * Make thumbnails visible, they need some styling though * Make gridle views even more beautiful * Remove classes added to support query * Remove query related code from Reach Activity * Solve two progressbar issue * Remove query related ekstra codes * Remove not needed anymore callback * Make medai details work * Remove all old removed code dependencies * Solve initial load takes too long issue * Solve current position track * Add placeholder for possible load issues * Add red stroke to bitmap * Add borders to rectangles * Change media details text to details * Fix file name extension anf File: prefix * Fix some code style issues * Fix some style issues * Fix style issues * Fix build issue * Fix test about etMediaListFromSearch * Fix test issue with Seacrh Activity * Fix conflict mark
This commit is contained in:
parent
7655562272
commit
ee1bf4b5b6
28 changed files with 2172 additions and 59 deletions
30
app/src/main/java/fr/free/nrw/commons/MapController.java
Normal file
30
app/src/main/java/fr/free/nrw/commons/MapController.java
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
package fr.free.nrw.commons;
|
||||
|
||||
import fr.free.nrw.commons.location.LatLng;
|
||||
import fr.free.nrw.commons.nearby.Place;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class MapController {
|
||||
|
||||
/**
|
||||
* We pass this variable as a group of placeList and boundaryCoordinates
|
||||
*/
|
||||
public class NearbyPlacesInfo {
|
||||
public List<Place> placeList; // List of nearby places
|
||||
public LatLng[] boundaryCoordinates; // Corners of nearby area
|
||||
public LatLng curLatLng; // Current location when this places are populated
|
||||
public LatLng searchLatLng; // Search location for finding this places
|
||||
public List<Media> mediaList; // Search location for finding this places
|
||||
}
|
||||
|
||||
/**
|
||||
* We pass this variable as a group of placeList and boundaryCoordinates
|
||||
*/
|
||||
public class ExplorePlacesInfo {
|
||||
public List<Place> explorePlaceList; // List of nearby places
|
||||
public LatLng[] boundaryCoordinates; // Corners of nearby area
|
||||
public LatLng curLatLng; // Current location when this places are populated
|
||||
public LatLng searchLatLng; // Search location for finding this places
|
||||
public List<Media> mediaList; // Search location for finding this places
|
||||
}
|
||||
}
|
||||
|
|
@ -175,8 +175,8 @@ public class BookmarkLocationsDao {
|
|||
cv.put(BookmarkLocationsDao.Table.COLUMN_LANGUAGE, bookmarkLocation.getLanguage());
|
||||
cv.put(BookmarkLocationsDao.Table.COLUMN_DESCRIPTION, bookmarkLocation.getLongDescription());
|
||||
cv.put(BookmarkLocationsDao.Table.COLUMN_CATEGORY, bookmarkLocation.getCategory());
|
||||
cv.put(BookmarkLocationsDao.Table.COLUMN_LABEL_TEXT, bookmarkLocation.getLabel().getText());
|
||||
cv.put(BookmarkLocationsDao.Table.COLUMN_LABEL_ICON, bookmarkLocation.getLabel().getIcon());
|
||||
cv.put(BookmarkLocationsDao.Table.COLUMN_LABEL_TEXT, bookmarkLocation.getLabel()!=null ? bookmarkLocation.getLabel().getText() : "");
|
||||
cv.put(BookmarkLocationsDao.Table.COLUMN_LABEL_ICON, bookmarkLocation.getLabel()!=null ? bookmarkLocation.getLabel().getIcon() : null);
|
||||
cv.put(BookmarkLocationsDao.Table.COLUMN_WIKIPEDIA_LINK, bookmarkLocation.siteLinks.getWikipediaLink().toString());
|
||||
cv.put(BookmarkLocationsDao.Table.COLUMN_WIKIDATA_LINK, bookmarkLocation.siteLinks.getWikidataLink().toString());
|
||||
cv.put(BookmarkLocationsDao.Table.COLUMN_COMMONS_LINK, bookmarkLocation.siteLinks.getCommonsLink().toString());
|
||||
|
|
|
|||
|
|
@ -318,6 +318,8 @@ public class MainActivity extends BaseActivity
|
|||
if (!exploreFragment.onBackPressed()) {
|
||||
if (applicationKvStore.getBoolean("login_skipped")) {
|
||||
super.onBackPressed();
|
||||
} else {
|
||||
setSelectedItemId(NavTab.CONTRIBUTIONS.code());
|
||||
}
|
||||
}
|
||||
} else if (bookmarkFragment != null && activeFragment == ActiveFragment.BOOKMARK) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
package fr.free.nrw.commons.di
|
||||
|
||||
import android.app.Activity
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import fr.free.nrw.commons.explore.map.ExploreMapFragment
|
||||
|
||||
@Module
|
||||
class ExploreMapFragmentModule{
|
||||
|
||||
@Provides
|
||||
fun ExploreMapFragment.providesActivity(): Activity = activity!!
|
||||
}
|
||||
|
|
@ -13,6 +13,8 @@ import fr.free.nrw.commons.customselector.ui.selector.FolderFragment;
|
|||
import fr.free.nrw.commons.customselector.ui.selector.ImageFragment;
|
||||
import fr.free.nrw.commons.explore.ExploreFragment;
|
||||
import fr.free.nrw.commons.explore.ExploreListRootFragment;
|
||||
import fr.free.nrw.commons.explore.ExploreMapRootFragment;
|
||||
import fr.free.nrw.commons.explore.map.ExploreMapFragment;
|
||||
import fr.free.nrw.commons.explore.categories.media.CategoriesMediaFragment;
|
||||
import fr.free.nrw.commons.explore.categories.parent.ParentCategoriesFragment;
|
||||
import fr.free.nrw.commons.explore.categories.search.SearchCategoryFragment;
|
||||
|
|
@ -130,6 +132,12 @@ public abstract class FragmentBuilderModule {
|
|||
@ContributesAndroidInjector
|
||||
abstract ExploreListRootFragment bindExploreFeaturedRootFragment();
|
||||
|
||||
@ContributesAndroidInjector(modules = ExploreMapFragmentModule.class)
|
||||
abstract ExploreMapFragment bindExploreNearbyUploadsFragment();
|
||||
|
||||
@ContributesAndroidInjector
|
||||
abstract ExploreMapRootFragment bindExploreNearbyUploadsRootFragment();
|
||||
|
||||
@ContributesAndroidInjector
|
||||
abstract BookmarkListRootFragment bindBookmarkListRootFragment();
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import android.view.ViewGroup;
|
|||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.viewpager.widget.ViewPager.OnPageChangeListener;
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
|
|
@ -29,6 +30,7 @@ public class ExploreFragment extends CommonsDaggerSupportFragment {
|
|||
|
||||
private static final String FEATURED_IMAGES_CATEGORY = "Featured_pictures_on_Wikimedia_Commons";
|
||||
private static final String MOBILE_UPLOADS_CATEGORY = "Uploaded_with_Mobile/Android";
|
||||
private static final String EXPLORE_MAP = "Map";
|
||||
private static final String MEDIA_DETAILS_FRAGMENT_TAG = "MediaDetailsFragment";
|
||||
|
||||
@BindView(R.id.tab_layout)
|
||||
|
|
@ -38,6 +40,7 @@ public class ExploreFragment extends CommonsDaggerSupportFragment {
|
|||
ViewPagerAdapter viewPagerAdapter;
|
||||
private ExploreListRootFragment featuredRootFragment;
|
||||
private ExploreListRootFragment mobileRootFragment;
|
||||
private ExploreMapRootFragment mapRootFragment;
|
||||
@Inject
|
||||
@Named("default_preferences")
|
||||
public JsonKvStore applicationKvStore;
|
||||
|
|
@ -68,6 +71,27 @@ public class ExploreFragment extends CommonsDaggerSupportFragment {
|
|||
viewPager.setAdapter(viewPagerAdapter);
|
||||
viewPager.setId(R.id.viewPager);
|
||||
tabLayout.setupWithViewPager(viewPager);
|
||||
viewPager.addOnPageChangeListener(new OnPageChangeListener() {
|
||||
@Override
|
||||
public void onPageScrolled(int position, float positionOffset,
|
||||
int positionOffsetPixels) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageSelected(int position) {
|
||||
if (position == 2) {
|
||||
viewPager.setCanScroll(false);
|
||||
} else {
|
||||
viewPager.setCanScroll(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageScrollStateChanged(int state) {
|
||||
|
||||
}
|
||||
});
|
||||
setTabs();
|
||||
setHasOptionsMenu(true);
|
||||
return view;
|
||||
|
|
@ -86,14 +110,21 @@ public class ExploreFragment extends CommonsDaggerSupportFragment {
|
|||
Bundle mobileArguments = new Bundle();
|
||||
mobileArguments.putString("categoryName", MOBILE_UPLOADS_CATEGORY);
|
||||
|
||||
Bundle mapArguments = new Bundle();
|
||||
mapArguments.putString("categoryName", EXPLORE_MAP);
|
||||
|
||||
featuredRootFragment = new ExploreListRootFragment(featuredArguments);
|
||||
mobileRootFragment = new ExploreListRootFragment(mobileArguments);
|
||||
mapRootFragment = new ExploreMapRootFragment(mapArguments);
|
||||
fragmentList.add(featuredRootFragment);
|
||||
titleList.add(getString(R.string.explore_tab_title_featured).toUpperCase());
|
||||
|
||||
fragmentList.add(mobileRootFragment);
|
||||
titleList.add(getString(R.string.explore_tab_title_mobile).toUpperCase());
|
||||
|
||||
fragmentList.add(mapRootFragment);
|
||||
titleList.add(getString(R.string.explore_tab_title_map).toUpperCase());
|
||||
|
||||
((MainActivity)getActivity()).showTabs();
|
||||
((BaseActivity) getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(false);
|
||||
|
||||
|
|
@ -108,12 +139,18 @@ public class ExploreFragment extends CommonsDaggerSupportFragment {
|
|||
.setDisplayHomeAsUpEnabled(false);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
} else if (tabLayout.getSelectedTabPosition() == 1) { //Mobile root fragment
|
||||
if (mobileRootFragment.backPressed()) {
|
||||
((BaseActivity) getActivity()).getSupportActionBar()
|
||||
.setDisplayHomeAsUpEnabled(false);
|
||||
return true;
|
||||
}
|
||||
} else { //explore map fragment
|
||||
if (mapRootFragment.backPressed()) {
|
||||
((BaseActivity) getActivity()).getSupportActionBar()
|
||||
.setDisplayHomeAsUpEnabled(false);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,216 @@
|
|||
package fr.free.nrw.commons.explore;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import fr.free.nrw.commons.Media;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.category.CategoryImagesCallback;
|
||||
import fr.free.nrw.commons.contributions.MainActivity;
|
||||
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
|
||||
import fr.free.nrw.commons.explore.map.ExploreMapFragment;
|
||||
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
|
||||
import fr.free.nrw.commons.navtab.NavTab;
|
||||
|
||||
public class ExploreMapRootFragment extends CommonsDaggerSupportFragment implements
|
||||
MediaDetailPagerFragment.MediaDetailProvider, CategoryImagesCallback {
|
||||
|
||||
private MediaDetailPagerFragment mediaDetails;
|
||||
private ExploreMapFragment mapFragment;
|
||||
|
||||
@BindView(R.id.explore_container)
|
||||
FrameLayout container;
|
||||
|
||||
public ExploreMapRootFragment() {
|
||||
//empty constructor necessary otherwise crashes on recreate
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static ExploreMapRootFragment newInstance() {
|
||||
ExploreMapRootFragment fragment = new ExploreMapRootFragment();
|
||||
fragment.setRetainInstance(true);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
public ExploreMapRootFragment(Bundle bundle) {
|
||||
String title = bundle.getString("categoryName");
|
||||
mapFragment = new ExploreMapFragment();
|
||||
Bundle featuredArguments = new Bundle();
|
||||
featuredArguments.putString("categoryName", title);
|
||||
mapFragment.setArguments(featuredArguments);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull final LayoutInflater inflater,
|
||||
@Nullable final ViewGroup container,
|
||||
@Nullable final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
View view = inflater.inflate(R.layout.fragment_featured_root, container, false);
|
||||
ButterKnife.bind(this, view);
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
if (savedInstanceState == null) {
|
||||
setFragment(mapFragment, mediaDetails);
|
||||
}
|
||||
}
|
||||
|
||||
public void setFragment(Fragment fragment, Fragment otherFragment) {
|
||||
if (fragment.isAdded() && otherFragment != null) {
|
||||
getChildFragmentManager()
|
||||
.beginTransaction()
|
||||
.hide(otherFragment)
|
||||
.show(fragment)
|
||||
.addToBackStack("CONTRIBUTION_LIST_FRAGMENT_TAG")
|
||||
.commit();
|
||||
getChildFragmentManager().executePendingTransactions();
|
||||
} else if (fragment.isAdded() && otherFragment == null) {
|
||||
getChildFragmentManager()
|
||||
.beginTransaction()
|
||||
.show(fragment)
|
||||
.addToBackStack("CONTRIBUTION_LIST_FRAGMENT_TAG")
|
||||
.commit();
|
||||
getChildFragmentManager().executePendingTransactions();
|
||||
} else if (!fragment.isAdded() && otherFragment != null) {
|
||||
getChildFragmentManager()
|
||||
.beginTransaction()
|
||||
.hide(otherFragment)
|
||||
.add(R.id.explore_container, fragment)
|
||||
.addToBackStack("CONTRIBUTION_LIST_FRAGMENT_TAG")
|
||||
.commit();
|
||||
getChildFragmentManager().executePendingTransactions();
|
||||
} else if (!fragment.isAdded()) {
|
||||
getChildFragmentManager()
|
||||
.beginTransaction()
|
||||
.replace(R.id.explore_container, fragment)
|
||||
.addToBackStack("CONTRIBUTION_LIST_FRAGMENT_TAG")
|
||||
.commit();
|
||||
getChildFragmentManager().executePendingTransactions();
|
||||
}
|
||||
}
|
||||
|
||||
public void removeFragment(Fragment fragment) {
|
||||
getChildFragmentManager()
|
||||
.beginTransaction()
|
||||
.remove(fragment)
|
||||
.commit();
|
||||
getChildFragmentManager().executePendingTransactions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(final Context context) {
|
||||
super.onAttach(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMediaClicked(int position) {
|
||||
container.setVisibility(View.VISIBLE);
|
||||
((ExploreFragment) getParentFragment()).tabLayout.setVisibility(View.GONE);
|
||||
mediaDetails = new MediaDetailPagerFragment(false, true);
|
||||
((ExploreFragment) getParentFragment()).setScroll(false);
|
||||
setFragment(mediaDetails, mapFragment);
|
||||
mediaDetails.showImage(position);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called mediaDetailPagerFragment. It returns the Media Object at that Index
|
||||
*
|
||||
* @param i It is the index of which media object is to be returned which is same as current
|
||||
* index of viewPager.
|
||||
* @return Media Object
|
||||
*/
|
||||
@Override
|
||||
public Media getMediaAtPosition(int i) {
|
||||
if (mapFragment != null && mapFragment.mediaList != null) {
|
||||
return mapFragment.mediaList.get(i);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called on from getCount of MediaDetailPagerFragment The viewpager will contain
|
||||
* same number of media items as that of media elements in adapter.
|
||||
*
|
||||
* @return Total Media count in the adapter
|
||||
*/
|
||||
@Override
|
||||
public int getTotalMediaCount() {
|
||||
if (mapFragment != null && mapFragment.mediaList != null) {
|
||||
return mapFragment.mediaList.size();
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getContributionStateAt(int position) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reload media detail fragment once media is nominated
|
||||
*
|
||||
* @param index item position that has been nominated
|
||||
*/
|
||||
@Override
|
||||
public void refreshNominatedMedia(int index) {
|
||||
if (mediaDetails != null && !mapFragment.isVisible()) {
|
||||
removeFragment(mediaDetails);
|
||||
onMediaClicked(index);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called on success of API call for featured images or mobile uploads. The
|
||||
* viewpager will notified that number of items have changed.
|
||||
*/
|
||||
@Override
|
||||
public void viewPagerNotifyDataSetChanged() {
|
||||
if (mediaDetails != null) {
|
||||
mediaDetails.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs back pressed action on the fragment. Return true if the event was handled by the
|
||||
* mediaDetails otherwise returns false.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean backPressed() {
|
||||
if (null != mediaDetails && mediaDetails.isVisible()) {
|
||||
((ExploreFragment) getParentFragment()).tabLayout.setVisibility(View.VISIBLE);
|
||||
removeFragment(mediaDetails);
|
||||
((ExploreFragment) getParentFragment()).setScroll(true);
|
||||
setFragment(mapFragment, mediaDetails);
|
||||
((MainActivity) getActivity()).showTabs();
|
||||
return true;
|
||||
|
||||
} if (mapFragment != null && mapFragment.isVisible()) {
|
||||
if (mapFragment.backButtonClicked()) {
|
||||
// Explore map fragment handled the event no further action required.
|
||||
return true;
|
||||
} else {
|
||||
((MainActivity) getActivity()).showTabs();
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
((MainActivity) getActivity()).setSelectedItemId(NavTab.CONTRIBUTIONS.code());
|
||||
}
|
||||
((MainActivity) getActivity()).showTabs();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -140,7 +140,8 @@ public class SearchActivity extends BaseActivity
|
|||
searchCategoryFragment.onQueryUpdated(query.toString());
|
||||
}
|
||||
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
//Open RecentSearchesFragment
|
||||
recentSearchesFragment.updateRecentSearches();
|
||||
viewPager.setVisibility(View.GONE);
|
||||
|
|
@ -155,8 +156,7 @@ public class SearchActivity extends BaseActivity
|
|||
// Newly searched query...
|
||||
if (recentSearch == null) {
|
||||
recentSearchesDao.save(new RecentSearch(null, query, new Date()));
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
recentSearch.setLastSearched(new Date());
|
||||
recentSearchesDao.save(recentSearch);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
package fr.free.nrw.commons.explore.map;
|
||||
|
||||
import fr.free.nrw.commons.Media;
|
||||
import fr.free.nrw.commons.location.LatLng;
|
||||
import fr.free.nrw.commons.media.MediaClient;
|
||||
import java.util.List;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
@Singleton
|
||||
public class ExploreMapCalls {
|
||||
|
||||
@Inject
|
||||
MediaClient mediaClient;
|
||||
|
||||
@Inject
|
||||
public ExploreMapCalls() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls method to query Commons for uploads around a location
|
||||
*
|
||||
* @param curLatLng coordinates of search location
|
||||
* @return list of places obtained
|
||||
*/
|
||||
List<Media> callCommonsQuery(final LatLng curLatLng) {
|
||||
String coordinates = curLatLng.getLatitude() + "|" + curLatLng.getLongitude();
|
||||
return mediaClient.getMediaListFromGeoSearch(coordinates).blockingGet();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
package fr.free.nrw.commons.explore.map;
|
||||
|
||||
import android.content.Context;
|
||||
import com.mapbox.mapboxsdk.annotations.Marker;
|
||||
import com.mapbox.mapboxsdk.camera.CameraUpdate;
|
||||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||
import fr.free.nrw.commons.location.LatLng;
|
||||
import fr.free.nrw.commons.location.LocationServiceManager;
|
||||
import fr.free.nrw.commons.nearby.NearbyBaseMarker;
|
||||
import fr.free.nrw.commons.nearby.Place;
|
||||
import java.util.List;
|
||||
|
||||
public class ExploreMapContract {
|
||||
|
||||
interface View {
|
||||
boolean isNetworkConnectionEstablished();
|
||||
void populatePlaces(LatLng curlatLng,LatLng searchLatLng);
|
||||
void checkPermissionsAndPerformAction();
|
||||
void recenterMap(LatLng curLatLng);
|
||||
void showLocationOffDialog();
|
||||
void openLocationSettings();
|
||||
void hideBottomDetailsSheet();
|
||||
void displayBottomSheetWithInfo(Marker marker);
|
||||
void addOnCameraMoveListener();
|
||||
void addSearchThisAreaButtonAction();
|
||||
void setSearchThisAreaButtonVisibility(boolean isVisible);
|
||||
void setProgressBarVisibility(boolean isVisible);
|
||||
boolean isDetailsBottomSheetVisible();
|
||||
boolean isSearchThisAreaButtonVisible();
|
||||
void addCurrentLocationMarker(LatLng curLatLng);
|
||||
void updateMapToTrackPosition(LatLng curLatLng);
|
||||
Context getContext();
|
||||
LatLng getCameraTarget();
|
||||
void centerMapToPlace(Place placeToCenter);
|
||||
LatLng getLastLocation();
|
||||
com.mapbox.mapboxsdk.geometry.LatLng getLastFocusLocation();
|
||||
boolean isCurrentLocationMarkerVisible();
|
||||
void setProjectorLatLngBounds();
|
||||
void disableFABRecenter();
|
||||
void enableFABRecenter();
|
||||
void addNearbyMarkersToMapBoxMap(final List<NearbyBaseMarker> nearbyBaseMarkers, final Marker selectedMarker);
|
||||
void setMapBoundaries(CameraUpdate cameaUpdate);
|
||||
void setFABRecenterAction(android.view.View.OnClickListener onClickListener);
|
||||
boolean backButtonClicked();
|
||||
}
|
||||
|
||||
interface UserActions {
|
||||
void updateMap(LocationServiceManager.LocationChangeType locationChangeType);
|
||||
void lockUnlockNearby(boolean isNearbyLocked);
|
||||
void attachView(View view);
|
||||
void detachView();
|
||||
void setActionListeners(JsonKvStore applicationKvStore);
|
||||
boolean backButtonClicked();
|
||||
void onCameraMove(com.mapbox.mapboxsdk.geometry.LatLng latLng);
|
||||
void markerUnselected();
|
||||
void markerSelected(Marker marker);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,196 @@
|
|||
package fr.free.nrw.commons.explore.map;
|
||||
|
||||
import static fr.free.nrw.commons.utils.LengthUtils.computeDistanceBetween;
|
||||
import static fr.free.nrw.commons.utils.LengthUtils.formatDistanceBetween;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.request.RequestOptions;
|
||||
import com.bumptech.glide.request.target.CustomTarget;
|
||||
import com.bumptech.glide.request.transition.Transition;
|
||||
import com.mapbox.mapboxsdk.annotations.IconFactory;
|
||||
import com.mapbox.mapboxsdk.annotations.Marker;
|
||||
import fr.free.nrw.commons.MapController;
|
||||
import fr.free.nrw.commons.Media;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.location.LatLng;
|
||||
import fr.free.nrw.commons.nearby.NearbyBaseMarker;
|
||||
import fr.free.nrw.commons.nearby.Place;
|
||||
import fr.free.nrw.commons.utils.ImageUtils;
|
||||
import fr.free.nrw.commons.utils.LocationUtils;
|
||||
import fr.free.nrw.commons.utils.PlaceUtils;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.inject.Inject;
|
||||
import timber.log.Timber;
|
||||
|
||||
public class ExploreMapController extends MapController {
|
||||
private final ExploreMapCalls exploreMapCalls;
|
||||
public LatLng latestSearchLocation; // Can be current and camera target on search this area button is used
|
||||
public LatLng currentLocation; // current location of user
|
||||
public double latestSearchRadius = 0; // Any last search radius
|
||||
public double currentLocationSearchRadius = 0; // Search radius of only searches around current location
|
||||
|
||||
|
||||
@Inject
|
||||
public ExploreMapController(ExploreMapCalls explorePlaces) {
|
||||
this.exploreMapCalls = explorePlaces;
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes location as parameter and returns ExplorePlaces info that holds curLatLng, mediaList, explorePlaceList and boundaryCoordinates
|
||||
* @param curLatLng is current geolocation
|
||||
* @param searchLatLng is the location that we want to search around
|
||||
* @param checkingAroundCurrentLocation is a boolean flag. True if we want to check around current location, false if another location
|
||||
* @return explorePlacesInfo info that holds curLatLng, mediaList, explorePlaceList and boundaryCoordinates
|
||||
*/
|
||||
public ExplorePlacesInfo loadAttractionsFromLocation(LatLng curLatLng, LatLng searchLatLng, boolean checkingAroundCurrentLocation) {
|
||||
|
||||
if (searchLatLng == null) {
|
||||
Timber.d("Loading attractions explore map, but search is null");
|
||||
return null;
|
||||
}
|
||||
|
||||
ExplorePlacesInfo explorePlacesInfo = new ExplorePlacesInfo();
|
||||
try {
|
||||
explorePlacesInfo.curLatLng = curLatLng;
|
||||
latestSearchLocation = searchLatLng;
|
||||
|
||||
List<Media> mediaList = exploreMapCalls.callCommonsQuery(searchLatLng);
|
||||
LatLng[] boundaryCoordinates = {mediaList.get(0).getCoordinates(), // south
|
||||
mediaList.get(0).getCoordinates(), // north
|
||||
mediaList.get(0).getCoordinates(), // west
|
||||
mediaList.get(0).getCoordinates()};// east, init with a random location
|
||||
|
||||
if (searchLatLng != null) {
|
||||
Timber.d("Sorting places by distance...");
|
||||
final Map<Media, Double> distances = new HashMap<>();
|
||||
for (Media media : mediaList) {
|
||||
distances.put(media, computeDistanceBetween(media.getCoordinates(), searchLatLng));
|
||||
// Find boundaries with basic find max approach
|
||||
if (media.getCoordinates().getLatitude() < boundaryCoordinates[0].getLatitude()) {
|
||||
boundaryCoordinates[0] = media.getCoordinates();
|
||||
}
|
||||
if (media.getCoordinates().getLatitude() > boundaryCoordinates[1].getLatitude()) {
|
||||
boundaryCoordinates[1] = media.getCoordinates();
|
||||
}
|
||||
if (media.getCoordinates().getLongitude() < boundaryCoordinates[2].getLongitude()) {
|
||||
boundaryCoordinates[2] = media.getCoordinates();
|
||||
}
|
||||
if (media.getCoordinates().getLongitude() > boundaryCoordinates[3].getLongitude()) {
|
||||
boundaryCoordinates[3] = media.getCoordinates();
|
||||
}
|
||||
}
|
||||
}
|
||||
explorePlacesInfo.mediaList = mediaList;
|
||||
explorePlacesInfo.explorePlaceList = PlaceUtils.mediaToExplorePlace(mediaList);
|
||||
explorePlacesInfo.boundaryCoordinates = boundaryCoordinates;
|
||||
|
||||
// Sets latestSearchRadius to maximum distance among boundaries and search location
|
||||
for (LatLng bound : boundaryCoordinates) {
|
||||
double distance = LocationUtils.commonsLatLngToMapBoxLatLng(bound).distanceTo(LocationUtils.commonsLatLngToMapBoxLatLng(latestSearchLocation));
|
||||
if (distance > latestSearchRadius) {
|
||||
latestSearchRadius = distance;
|
||||
}
|
||||
}
|
||||
|
||||
// Our radius searched around us, will be used to understand when user search their own location, we will follow them
|
||||
if (checkingAroundCurrentLocation) {
|
||||
currentLocationSearchRadius = latestSearchRadius;
|
||||
currentLocation = curLatLng;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return explorePlacesInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads attractions from location for map view, we need to return places in Place data type
|
||||
* @return baseMarkerOptions list that holds nearby places with their icons
|
||||
*/
|
||||
public static List<NearbyBaseMarker> loadAttractionsFromLocationToBaseMarkerOptions(
|
||||
LatLng curLatLng,
|
||||
final List<Place> placeList,
|
||||
Context context,
|
||||
NearbyBaseMarkerThumbCallback callback,
|
||||
Marker selectedMarker,
|
||||
boolean shouldTrackPosition,
|
||||
ExplorePlacesInfo explorePlacesInfo) {
|
||||
List<NearbyBaseMarker> baseMarkerOptions = new ArrayList<>();
|
||||
|
||||
if (placeList == null) {
|
||||
return baseMarkerOptions;
|
||||
}
|
||||
|
||||
VectorDrawableCompat vectorDrawable = null;
|
||||
try {
|
||||
vectorDrawable = VectorDrawableCompat.create(
|
||||
context.getResources(), R.drawable.ic_custom_map_marker, context.getTheme());
|
||||
|
||||
} catch (Resources.NotFoundException e) {
|
||||
// ignore when running tests.
|
||||
}
|
||||
if (vectorDrawable != null) {
|
||||
for (Place explorePlace : placeList) {
|
||||
final NearbyBaseMarker nearbyBaseMarker = new NearbyBaseMarker();
|
||||
String distance = formatDistanceBetween(curLatLng, explorePlace.location);
|
||||
explorePlace.setDistance(distance);
|
||||
|
||||
nearbyBaseMarker.title(explorePlace.name.substring(5, explorePlace.name.lastIndexOf(".")));
|
||||
nearbyBaseMarker.position(
|
||||
new com.mapbox.mapboxsdk.geometry.LatLng(
|
||||
explorePlace.location.getLatitude(),
|
||||
explorePlace.location.getLongitude()));
|
||||
nearbyBaseMarker.place(explorePlace);
|
||||
|
||||
Glide.with(context)
|
||||
.asBitmap()
|
||||
.load(explorePlace.getThumb())
|
||||
.placeholder(R.drawable.image_placeholder_96)
|
||||
.apply(new RequestOptions().override(96, 96).centerCrop())
|
||||
.into(new CustomTarget<Bitmap>() {
|
||||
// We add icons to markers when bitmaps are ready
|
||||
@Override
|
||||
public void onResourceReady(@NonNull Bitmap resource, @Nullable Transition<? super Bitmap> transition) {
|
||||
nearbyBaseMarker.setIcon(IconFactory.getInstance(context).fromBitmap(
|
||||
ImageUtils.addRedBorder(resource, 6, context)));
|
||||
baseMarkerOptions.add(nearbyBaseMarker);
|
||||
if (baseMarkerOptions.size() == placeList.size()) { // if true, we added all markers to list and can trigger thumbs ready callback
|
||||
callback.onNearbyBaseMarkerThumbsReady(baseMarkerOptions, explorePlacesInfo, selectedMarker, shouldTrackPosition);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadCleared(@Nullable Drawable placeholder) {
|
||||
}
|
||||
|
||||
// We add thumbnail icon for images that couldn't be loaded
|
||||
@Override
|
||||
public void onLoadFailed(@Nullable final Drawable errorDrawable) {
|
||||
super.onLoadFailed(errorDrawable);
|
||||
nearbyBaseMarker.setIcon(IconFactory.getInstance(context).fromResource(R.drawable.image_placeholder_96));
|
||||
baseMarkerOptions.add(nearbyBaseMarker);
|
||||
if (baseMarkerOptions.size() == placeList.size()) { // if true, we added all markers to list and can trigger thumbs ready callback
|
||||
callback.onNearbyBaseMarkerThumbsReady(baseMarkerOptions, explorePlacesInfo, selectedMarker, shouldTrackPosition);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
return baseMarkerOptions;
|
||||
}
|
||||
|
||||
interface NearbyBaseMarkerThumbCallback {
|
||||
// Callback to notify thumbnails of explore markers are added as icons and ready
|
||||
void onNearbyBaseMarkerThumbsReady(List<NearbyBaseMarker> baseMarkers, ExplorePlacesInfo explorePlacesInfo, Marker selectedMarker, boolean shouldTrackPosition);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,807 @@
|
|||
package fr.free.nrw.commons.explore.map;
|
||||
|
||||
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.utils.MapUtils.CAMERA_TARGET_SHIFT_FACTOR_LANDSCAPE;
|
||||
import static fr.free.nrw.commons.utils.MapUtils.CAMERA_TARGET_SHIFT_FACTOR_PORTRAIT;
|
||||
import static fr.free.nrw.commons.utils.MapUtils.ZOOM_LEVEL;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Configuration;
|
||||
import android.location.LocationManager;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
import android.text.Html;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.widget.AppCompatTextView;
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior;
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import com.mapbox.mapboxsdk.annotations.Icon;
|
||||
import com.mapbox.mapboxsdk.annotations.IconFactory;
|
||||
import com.mapbox.mapboxsdk.annotations.Marker;
|
||||
import com.mapbox.mapboxsdk.annotations.MarkerOptions;
|
||||
import com.mapbox.mapboxsdk.annotations.Polygon;
|
||||
import com.mapbox.mapboxsdk.annotations.PolygonOptions;
|
||||
import com.mapbox.mapboxsdk.camera.CameraPosition;
|
||||
import com.mapbox.mapboxsdk.camera.CameraUpdate;
|
||||
import com.mapbox.mapboxsdk.camera.CameraUpdateFactory;
|
||||
import com.mapbox.mapboxsdk.geometry.LatLngBounds;
|
||||
import com.mapbox.mapboxsdk.maps.MapView;
|
||||
import com.mapbox.mapboxsdk.maps.MapboxMap;
|
||||
import com.mapbox.mapboxsdk.maps.Style;
|
||||
import com.mapbox.mapboxsdk.maps.UiSettings;
|
||||
import com.mapbox.pluginscalebar.ScaleBarOptions;
|
||||
import com.mapbox.pluginscalebar.ScaleBarPlugin;
|
||||
import fr.free.nrw.commons.MapController;
|
||||
import fr.free.nrw.commons.Media;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.Utils;
|
||||
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao;
|
||||
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
|
||||
import fr.free.nrw.commons.explore.ExploreMapRootFragment;
|
||||
import fr.free.nrw.commons.explore.paging.LiveDataConverter;
|
||||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||
import fr.free.nrw.commons.location.LatLng;
|
||||
import fr.free.nrw.commons.location.LocationServiceManager;
|
||||
import fr.free.nrw.commons.location.LocationUpdateListener;
|
||||
import fr.free.nrw.commons.media.MediaClient;
|
||||
import fr.free.nrw.commons.nearby.NearbyBaseMarker;
|
||||
import fr.free.nrw.commons.nearby.NearbyMarker;
|
||||
import fr.free.nrw.commons.nearby.Place;
|
||||
import fr.free.nrw.commons.utils.DialogUtil;
|
||||
import fr.free.nrw.commons.utils.ExecutorUtils;
|
||||
import fr.free.nrw.commons.utils.LocationUtils;
|
||||
import fr.free.nrw.commons.utils.MapUtils;
|
||||
import fr.free.nrw.commons.utils.NetworkUtils;
|
||||
import fr.free.nrw.commons.utils.PermissionUtils;
|
||||
import fr.free.nrw.commons.utils.SystemThemeUtils;
|
||||
import fr.free.nrw.commons.utils.UiUtils;
|
||||
import fr.free.nrw.commons.utils.ViewUtil;
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import java.util.List;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import timber.log.Timber;
|
||||
|
||||
public class ExploreMapFragment extends CommonsDaggerSupportFragment
|
||||
implements ExploreMapContract.View, LocationUpdateListener {
|
||||
|
||||
private BottomSheetBehavior bottomSheetDetailsBehavior;
|
||||
private BroadcastReceiver broadcastReceiver;
|
||||
private boolean isNetworkErrorOccurred;
|
||||
private Snackbar snackbar;
|
||||
private boolean isDarkTheme;
|
||||
private boolean isPermissionDenied;
|
||||
private fr.free.nrw.commons.location.LatLng lastKnownLocation; // lask location of user
|
||||
private fr.free.nrw.commons.location.LatLng lastFocusLocation; // last location that map is focused
|
||||
public List<Media> mediaList;
|
||||
private boolean recenterToUserLocation; // true is recenter is needed (ie. when current location is in visible map boundaries)
|
||||
|
||||
|
||||
private MapboxMap.OnCameraMoveListener cameraMoveListener;
|
||||
private MapboxMap mapBox;
|
||||
private Place lastPlaceToCenter; // the last place that we centered the map
|
||||
private boolean isMapBoxReady;
|
||||
private Marker selectedMarker; // the marker that user selected
|
||||
private LatLngBounds projectorLatLngBounds; // current windows borders
|
||||
private Marker currentLocationMarker;
|
||||
private Polygon currentLocationPolygon;
|
||||
IntentFilter intentFilter = new IntentFilter(MapUtils.NETWORK_INTENT_ACTION);
|
||||
|
||||
@Inject
|
||||
LiveDataConverter liveDataConverter;
|
||||
@Inject
|
||||
MediaClient mediaClient;
|
||||
@Inject
|
||||
LocationServiceManager locationManager;
|
||||
@Inject
|
||||
ExploreMapController exploreMapController;
|
||||
@Inject @Named("default_preferences")
|
||||
JsonKvStore applicationKvStore;
|
||||
@Inject
|
||||
BookmarkLocationsDao bookmarkLocationDao; // May be needed in future if we want to integrate bookmarking explore places
|
||||
@Inject
|
||||
SystemThemeUtils systemThemeUtils;
|
||||
|
||||
private ExploreMapPresenter presenter;
|
||||
|
||||
@BindView(R.id.map_view) MapView mapView;
|
||||
@BindView(R.id.bottom_sheet_details) View bottomSheetDetails;
|
||||
@BindView(R.id.map_progress_bar) ProgressBar progressBar;
|
||||
@BindView(R.id.fab_recenter) FloatingActionButton fabRecenter;
|
||||
@BindView(R.id.search_this_area_button) Button searchThisAreaButton;
|
||||
@BindView(R.id.tv_attribution) AppCompatTextView tvAttribution;
|
||||
|
||||
@BindView(R.id.directionsButton) LinearLayout directionsButton;
|
||||
@BindView(R.id.commonsButton) LinearLayout commonsButton;
|
||||
@BindView(R.id.mediaDetailsButton) LinearLayout mediaDetailsButton;
|
||||
@BindView(R.id.description) TextView description;
|
||||
@BindView(R.id.title) TextView title;
|
||||
@BindView(R.id.category) TextView distance;
|
||||
|
||||
|
||||
@NonNull
|
||||
public static ExploreMapFragment newInstance() {
|
||||
ExploreMapFragment fragment = new ExploreMapFragment();
|
||||
fragment.setRetainInstance(true);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(
|
||||
@NonNull LayoutInflater inflater,
|
||||
ViewGroup container,
|
||||
Bundle savedInstanceState
|
||||
) {
|
||||
View v = inflater.inflate(R.layout.fragment_explore_map, container, false);
|
||||
ButterKnife.bind(this, v);
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
mapView.onStart();
|
||||
setSearchThisAreaButtonVisibility(false);
|
||||
tvAttribution.setText(Html.fromHtml(getString(R.string.map_attribution)));
|
||||
initNetworkBroadCastReceiver();
|
||||
|
||||
if (presenter == null) {
|
||||
presenter = new ExploreMapPresenter(bookmarkLocationDao);
|
||||
}
|
||||
setHasOptionsMenu(true);
|
||||
|
||||
isDarkTheme = systemThemeUtils.isDeviceInNightMode();
|
||||
isPermissionDenied = false;
|
||||
cameraMoveListener= () -> presenter.onCameraMove(mapBox.getCameraPosition().target);
|
||||
presenter.attachView(this);
|
||||
recenterToUserLocation = false;
|
||||
mapView.onCreate(savedInstanceState);
|
||||
mapView.getMapAsync(mapBoxMap -> {
|
||||
mapBox = mapBoxMap;
|
||||
initViews();
|
||||
presenter.setActionListeners(applicationKvStore);
|
||||
mapBoxMap.setStyle(isDarkTheme? Style.DARK:Style.OUTDOORS, style -> {
|
||||
final UiSettings uiSettings = mapBoxMap.getUiSettings();
|
||||
uiSettings.setCompassGravity(Gravity.BOTTOM | Gravity.LEFT);
|
||||
uiSettings.setCompassMargins(12, 0, 0, 24);
|
||||
uiSettings.setLogoEnabled(false);
|
||||
uiSettings.setAttributionEnabled(false);
|
||||
uiSettings.setRotateGesturesEnabled(false);
|
||||
isMapBoxReady = true;
|
||||
performMapReadyActions();
|
||||
final CameraPosition cameraPosition = new CameraPosition.Builder()
|
||||
.target(new com.mapbox.mapboxsdk.geometry.LatLng(51.50550, -0.07520))
|
||||
.zoom(MapUtils.ZOOM_OUT)
|
||||
.build();
|
||||
mapBoxMap.setCameraPosition(cameraPosition);
|
||||
|
||||
final ScaleBarPlugin scaleBarPlugin = new ScaleBarPlugin(mapView, mapBoxMap);
|
||||
final int color = isDarkTheme ? R.color.bottom_bar_light : R.color.bottom_bar_dark;
|
||||
final ScaleBarOptions scaleBarOptions = new ScaleBarOptions(getContext())
|
||||
.setTextColor(color)
|
||||
.setTextSize(R.dimen.description_text_size)
|
||||
.setBarHeight(R.dimen.tiny_gap)
|
||||
.setBorderWidth(R.dimen.miniscule_margin)
|
||||
.setMarginTop(R.dimen.tiny_padding)
|
||||
.setMarginLeft(R.dimen.tiny_padding)
|
||||
.setTextBarMargin(R.dimen.tiny_padding);
|
||||
scaleBarPlugin.create(scaleBarOptions);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
mapView.onResume();
|
||||
presenter.attachView(this);
|
||||
registerNetworkReceiver();
|
||||
if (isResumed()) {
|
||||
if (!isPermissionDenied && !applicationKvStore
|
||||
.getBoolean("doNotAskForLocationPermission", false)) {
|
||||
startTheMap();
|
||||
} else {
|
||||
startMapWithoutPermission();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void startTheMap() {
|
||||
mapView.onStart();
|
||||
performMapReadyActions();
|
||||
}
|
||||
|
||||
private void startMapWithoutPermission() {
|
||||
mapView.onStart();
|
||||
applicationKvStore.putBoolean("doNotAskForLocationPermission", true);
|
||||
lastKnownLocation = MapUtils.defaultLatLng;
|
||||
MapUtils.centerMapToDefaultLatLng(mapBox);
|
||||
if (mapBox != null) {
|
||||
addOnCameraMoveListener();
|
||||
}
|
||||
presenter.onMapReady(exploreMapController);
|
||||
}
|
||||
|
||||
private void registerNetworkReceiver() {
|
||||
if (getActivity() != null) {
|
||||
getActivity().registerReceiver(broadcastReceiver, intentFilter);
|
||||
}
|
||||
}
|
||||
|
||||
private void performMapReadyActions() {
|
||||
if (isMapBoxReady) {
|
||||
if(!applicationKvStore.getBoolean("doNotAskForLocationPermission", false) ||
|
||||
PermissionUtils.hasPermission(getActivity(), Manifest.permission.ACCESS_FINE_LOCATION)){
|
||||
checkPermissionsAndPerformAction();
|
||||
}else{
|
||||
isPermissionDenied = true;
|
||||
addOnCameraMoveListener();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void initViews() {
|
||||
Timber.d("init views called");
|
||||
initBottomSheets();
|
||||
setBottomSheetCallbacks();
|
||||
}
|
||||
|
||||
/**
|
||||
* a) Creates bottom sheet behaviours from bottom sheet, sets initial states and visibility
|
||||
* b) Gets the touch event on the map to perform following actions:
|
||||
* if bottom sheet details are expanded or collapsed hide the bottom sheet details.
|
||||
*/
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
private void initBottomSheets() {
|
||||
bottomSheetDetailsBehavior = BottomSheetBehavior.from(bottomSheetDetails);
|
||||
bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
|
||||
bottomSheetDetails.setVisibility(View.VISIBLE);
|
||||
|
||||
mapView.setOnTouchListener((v, event) -> {
|
||||
|
||||
// Motion event is triggered two times on a touch event, one as ACTION_UP
|
||||
// and other as ACTION_DOWN, we only want one trigger per touch event.
|
||||
|
||||
if(event.getAction() == MotionEvent.ACTION_DOWN) {
|
||||
if (bottomSheetDetailsBehavior.getState()
|
||||
== BottomSheetBehavior.STATE_EXPANDED) {
|
||||
bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
|
||||
} else if (bottomSheetDetailsBehavior.getState()
|
||||
== BottomSheetBehavior.STATE_COLLAPSED) {
|
||||
bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines how bottom sheets will act on click
|
||||
*/
|
||||
private void setBottomSheetCallbacks() {
|
||||
bottomSheetDetails.setOnClickListener(v -> {
|
||||
if (bottomSheetDetailsBehavior.getState() == BottomSheetBehavior.STATE_COLLAPSED) {
|
||||
bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
|
||||
} else if (bottomSheetDetailsBehavior.getState() == BottomSheetBehavior.STATE_EXPANDED) {
|
||||
bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLocationChangedSignificantly(LatLng latLng) {
|
||||
Timber.d("Location significantly changed");
|
||||
if (isMapBoxReady && latLng != null &&!isUserBrowsing()) {
|
||||
handleLocationUpdate(latLng,LOCATION_SIGNIFICANTLY_CHANGED);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isUserBrowsing() {
|
||||
final boolean isUserBrowsing = lastKnownLocation!=null && !presenter.areLocationsClose(getCameraTarget(), lastKnownLocation);
|
||||
return isUserBrowsing;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLocationChangedSlightly(LatLng latLng) {
|
||||
Timber.d("Location slightly changed");
|
||||
if (isMapBoxReady && latLng != null &&!isUserBrowsing()) {//If the map has never ever shown the current location, lets do it know
|
||||
handleLocationUpdate(latLng,LOCATION_SLIGHTLY_CHANGED);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleLocationUpdate(final fr.free.nrw.commons.location.LatLng latLng, final LocationServiceManager.LocationChangeType locationChangeType){
|
||||
lastKnownLocation = latLng;
|
||||
exploreMapController.currentLocation = lastKnownLocation;
|
||||
presenter.updateMap(locationChangeType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLocationChangedMedium(LatLng latLng) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNetworkConnectionEstablished() {
|
||||
return NetworkUtils.isInternetConnectionEstablished(getActivity());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void populatePlaces(LatLng curLatLng, LatLng searchLatLng) {
|
||||
final Observable<MapController.ExplorePlacesInfo> nearbyPlacesInfoObservable;
|
||||
if (curLatLng == null) {
|
||||
checkPermissionsAndPerformAction();
|
||||
return;
|
||||
}
|
||||
if (searchLatLng.equals(lastFocusLocation) || lastFocusLocation == null || recenterToUserLocation) { // Means we are checking around current location
|
||||
nearbyPlacesInfoObservable = presenter.loadAttractionsFromLocation(curLatLng, searchLatLng, true);
|
||||
} else {
|
||||
nearbyPlacesInfoObservable = presenter.loadAttractionsFromLocation(curLatLng, searchLatLng, false);
|
||||
}
|
||||
compositeDisposable.add(nearbyPlacesInfoObservable
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(explorePlacesInfo -> {
|
||||
updateMapMarkers(explorePlacesInfo, isCurrentLocationMarkerVisible());
|
||||
mediaList = explorePlacesInfo.mediaList;
|
||||
lastFocusLocation = searchLatLng;
|
||||
},
|
||||
throwable -> {
|
||||
Timber.d(throwable);
|
||||
showErrorMessage(getString(R.string.error_fetching_nearby_places)+throwable.getLocalizedMessage());
|
||||
setProgressBarVisibility(false);
|
||||
presenter.lockUnlockNearby(false);
|
||||
}));
|
||||
if(recenterToUserLocation) {
|
||||
recenterToUserLocation = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates map markers according to latest situation
|
||||
* @param explorePlacesInfo holds several information as current location, marker list etc.
|
||||
*/
|
||||
private void updateMapMarkers(final MapController.ExplorePlacesInfo explorePlacesInfo, final boolean shouldTrackPosition) {
|
||||
presenter.updateMapMarkers(explorePlacesInfo, selectedMarker,shouldTrackPosition);
|
||||
}
|
||||
|
||||
private void showErrorMessage(final String message) {
|
||||
ViewUtil.showLongToast(getActivity(), message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkPermissionsAndPerformAction() {
|
||||
Timber.d("Checking permission and perfoming action");
|
||||
PermissionUtils.checkPermissionsAndPerformAction(getActivity(),
|
||||
Manifest.permission.ACCESS_FINE_LOCATION,
|
||||
() -> locationPermissionGranted(),
|
||||
() -> isPermissionDenied = true,
|
||||
R.string.location_permission_title,
|
||||
R.string.location_permission_rationale_nearby);
|
||||
}
|
||||
|
||||
private void locationPermissionGranted() {
|
||||
isPermissionDenied = false;
|
||||
applicationKvStore.putBoolean("doNotAskForLocationPermission", false);
|
||||
lastKnownLocation = locationManager.getLastLocation();
|
||||
fr.free.nrw.commons.location.LatLng target=lastFocusLocation;
|
||||
if(null == lastFocusLocation){
|
||||
target = lastKnownLocation;
|
||||
}
|
||||
if (lastKnownLocation != null) {
|
||||
final CameraPosition position = new CameraPosition.Builder()
|
||||
.target(LocationUtils.commonsLatLngToMapBoxLatLng(target)) // Sets the new camera position
|
||||
.zoom(ZOOM_LEVEL) // Same zoom level
|
||||
.build();
|
||||
mapBox.moveCamera(CameraUpdateFactory.newCameraPosition(position));
|
||||
}
|
||||
else if(locationManager.isGPSProviderEnabled() || locationManager.isNetworkProviderEnabled()){
|
||||
locationManager.requestLocationUpdatesFromProvider(LocationManager.NETWORK_PROVIDER);
|
||||
locationManager.requestLocationUpdatesFromProvider(LocationManager.GPS_PROVIDER);
|
||||
setProgressBarVisibility(true);
|
||||
}
|
||||
else {
|
||||
Toast.makeText(getContext(), getString(R.string.nearby_location_not_available), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
presenter.onMapReady(exploreMapController);
|
||||
registerUnregisterLocationListener(false);
|
||||
addOnCameraMoveListener();
|
||||
}
|
||||
|
||||
public void registerUnregisterLocationListener(final boolean removeLocationListener) {
|
||||
MapUtils.registerUnregisterLocationListener(removeLocationListener, locationManager, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recenterMap(LatLng curLatLng) {
|
||||
if (isPermissionDenied || curLatLng == null) {
|
||||
recenterToUserLocation = true;
|
||||
checkPermissionsAndPerformAction();
|
||||
if (!isPermissionDenied && !(locationManager.isNetworkProviderEnabled() || locationManager.isGPSProviderEnabled())) {
|
||||
showLocationOffDialog();
|
||||
}
|
||||
return;
|
||||
}
|
||||
addCurrentLocationMarker(curLatLng);
|
||||
final CameraPosition position;
|
||||
position = new CameraPosition.Builder()
|
||||
.target(new com.mapbox.mapboxsdk.geometry.LatLng(curLatLng.getLatitude(), curLatLng.getLongitude(), 0)) // Sets the new camera position
|
||||
.zoom(mapBox.getCameraPosition().zoom) // Same zoom level
|
||||
.build();
|
||||
|
||||
mapBox.animateCamera(CameraUpdateFactory.newCameraPosition(position), 1000);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showLocationOffDialog() {
|
||||
// This creates a dialog box that prompts the user to enable location
|
||||
DialogUtil
|
||||
.showAlertDialog(getActivity(), getString(R.string.ask_to_turn_location_on), getString(R.string.nearby_needs_location),
|
||||
getString(R.string.yes), getString(R.string.no), this::openLocationSettings, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void openLocationSettings() {
|
||||
// This method opens the location settings of the device along with a followup toast.
|
||||
final Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
|
||||
final PackageManager packageManager = getActivity().getPackageManager();
|
||||
|
||||
if (intent.resolveActivity(packageManager)!= null) {
|
||||
startActivity(intent);
|
||||
Toast.makeText(getContext(), R.string.recommend_high_accuracy_mode, Toast.LENGTH_LONG).show();
|
||||
} else {
|
||||
Toast.makeText(getContext(), R.string.cannot_open_location_settings, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hideBottomDetailsSheet() {
|
||||
bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void displayBottomSheetWithInfo(final Marker marker) {
|
||||
selectedMarker = marker;
|
||||
final NearbyMarker nearbyMarker = (NearbyMarker) marker;
|
||||
final Place place = nearbyMarker.getNearbyBaseMarker().getPlace();
|
||||
passInfoToSheet(place);
|
||||
bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Same bottom sheet carries information for all nearby places, so we need to pass information
|
||||
* (title, description, distance and links) to view on nearby marker click
|
||||
* @param place Place of clicked nearby marker
|
||||
*/
|
||||
private void passInfoToSheet(final Place place) {
|
||||
directionsButton.setOnClickListener(view -> Utils.handleGeoCoordinates(getActivity(),
|
||||
place.getLocation()));
|
||||
|
||||
commonsButton.setVisibility(place.hasCommonsLink()?View.VISIBLE:View.GONE);
|
||||
commonsButton.setOnClickListener(view -> Utils.handleWebUrl(getContext(), place.siteLinks.getCommonsLink()));
|
||||
|
||||
int index = 0;
|
||||
for (Media media : mediaList) {
|
||||
if (media.getFilename().equals(place.name)) {
|
||||
int finalIndex = index;
|
||||
mediaDetailsButton.setOnClickListener(view -> {
|
||||
((ExploreMapRootFragment) getParentFragment()).onMediaClicked(finalIndex);
|
||||
});
|
||||
}
|
||||
index ++;
|
||||
}
|
||||
title.setText(place.name.substring(5, place.name.lastIndexOf(".")));
|
||||
distance.setText(place.distance);
|
||||
// Remove label since it is double information
|
||||
String descriptionText = place.getLongDescription()
|
||||
.replace(place.getName() + " (","");
|
||||
descriptionText = (descriptionText.equals(place.getLongDescription()) ? descriptionText : descriptionText.replaceFirst(".$",""));
|
||||
// Set the short description after we remove place name from long description
|
||||
description.setText(descriptionText);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void addOnCameraMoveListener() {
|
||||
mapBox.addOnCameraMoveListener(cameraMoveListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addSearchThisAreaButtonAction() {
|
||||
searchThisAreaButton.setOnClickListener(presenter.onSearchThisAreaClicked());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSearchThisAreaButtonVisibility(boolean isVisible) {
|
||||
if (isVisible) {
|
||||
searchThisAreaButton.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
searchThisAreaButton.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProgressBarVisibility(boolean isVisible) {
|
||||
if (isVisible) {
|
||||
progressBar.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
progressBar.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDetailsBottomSheetVisible() {
|
||||
if (bottomSheetDetails.getVisibility() == View.VISIBLE) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSearchThisAreaButtonVisible() {
|
||||
if (searchThisAreaButton.getVisibility() == View.VISIBLE) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes old current location marker and adds a new one to display current location
|
||||
* @param curLatLng current location of user
|
||||
*/
|
||||
@Override
|
||||
public void addCurrentLocationMarker(LatLng curLatLng) {
|
||||
if (null != curLatLng && !isPermissionDenied) {
|
||||
ExecutorUtils.get().submit(() -> {
|
||||
mapView.post(() -> removeCurrentLocationMarker());
|
||||
Timber.d("Adds current location marker");
|
||||
|
||||
final Icon icon = IconFactory.getInstance(getContext())
|
||||
.fromResource(R.drawable.current_location_marker);
|
||||
|
||||
final MarkerOptions currentLocationMarkerOptions = new MarkerOptions()
|
||||
.position(new com.mapbox.mapboxsdk.geometry.LatLng(curLatLng.getLatitude(),
|
||||
curLatLng.getLongitude()));
|
||||
currentLocationMarkerOptions.setIcon(icon); // Set custom icon
|
||||
mapView.post(
|
||||
() -> currentLocationMarker = mapBox.addMarker(currentLocationMarkerOptions));
|
||||
|
||||
final List<com.mapbox.mapboxsdk.geometry.LatLng> circle = UiUtils
|
||||
.createCircleArray(curLatLng.getLatitude(), curLatLng.getLongitude(),
|
||||
curLatLng.getAccuracy() * 2, 100);
|
||||
|
||||
final PolygonOptions currentLocationPolygonOptions = new PolygonOptions()
|
||||
.addAll(circle)
|
||||
.strokeColor(getResources().getColor(R.color.current_marker_stroke))
|
||||
.fillColor(getResources().getColor(R.color.current_marker_fill));
|
||||
mapView.post(
|
||||
() -> currentLocationPolygon = mapBox
|
||||
.addPolygon(currentLocationPolygonOptions));
|
||||
});
|
||||
} else {
|
||||
Timber.d("not adding current location marker..current location is null");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCurrentLocationMarkerVisible() {
|
||||
if (projectorLatLngBounds == null || currentLocationMarker == null) {
|
||||
Timber.d("Map projection bounds are null");
|
||||
return false;
|
||||
} else {
|
||||
Timber.d("Current location marker %s" , projectorLatLngBounds.contains(currentLocationMarker.getPosition()) ? "visible" : "invisible");
|
||||
return projectorLatLngBounds.contains(currentLocationMarker.getPosition());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets boundaries of visible region in terms of geolocation
|
||||
*/
|
||||
@Override
|
||||
public void setProjectorLatLngBounds() {
|
||||
projectorLatLngBounds = mapBox.getProjection().getVisibleRegion().latLngBounds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes old current location marker
|
||||
*/
|
||||
private void removeCurrentLocationMarker() {
|
||||
if (currentLocationMarker != null && mapBox!=null) {
|
||||
mapBox.removeMarker(currentLocationMarker);
|
||||
if (currentLocationPolygon != null) {
|
||||
mapBox.removePolygon(currentLocationPolygon);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update map camera to trac users current position
|
||||
* @param curLatLng
|
||||
*/
|
||||
@Override
|
||||
public void updateMapToTrackPosition(LatLng curLatLng) {
|
||||
Timber.d("Updates map camera to track user position");
|
||||
final CameraPosition cameraPosition;
|
||||
if(isPermissionDenied){
|
||||
cameraPosition = new CameraPosition.Builder().target
|
||||
(LocationUtils.commonsLatLngToMapBoxLatLng(curLatLng)).build();
|
||||
}else{
|
||||
cameraPosition = new CameraPosition.Builder().target
|
||||
(LocationUtils.commonsLatLngToMapBoxLatLng(curLatLng)).build();
|
||||
}
|
||||
if(null!=mapBox) {
|
||||
mapBox.setCameraPosition(cameraPosition);
|
||||
mapBox.animateCamera(CameraUpdateFactory
|
||||
.newCameraPosition(cameraPosition), 1000);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public LatLng getCameraTarget() {
|
||||
return mapBox == null ? null : LocationUtils.mapBoxLatLngToCommonsLatLng(mapBox.getCameraPosition().target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Centers map to a given place
|
||||
* @param place place to center
|
||||
*/
|
||||
@Override
|
||||
public void centerMapToPlace(Place place) {
|
||||
MapUtils.centerMapToPlace(place, mapBox, lastPlaceToCenter, getActivity());
|
||||
Timber.d("Map is centered to place");
|
||||
final double cameraShift;
|
||||
if (null != place) {
|
||||
lastPlaceToCenter = place;
|
||||
}
|
||||
|
||||
if (null != lastPlaceToCenter) {
|
||||
final Configuration configuration = getActivity().getResources().getConfiguration();
|
||||
if (configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
|
||||
cameraShift = CAMERA_TARGET_SHIFT_FACTOR_PORTRAIT;
|
||||
} else {
|
||||
cameraShift = CAMERA_TARGET_SHIFT_FACTOR_LANDSCAPE;
|
||||
}
|
||||
final CameraPosition position = new CameraPosition.Builder()
|
||||
.target(LocationUtils.commonsLatLngToMapBoxLatLng(
|
||||
new fr.free.nrw.commons.location.LatLng(
|
||||
lastPlaceToCenter.location.getLatitude() - cameraShift,
|
||||
lastPlaceToCenter.getLocation().getLongitude(),
|
||||
0))) // Sets the new camera position
|
||||
.zoom(mapBox.getCameraPosition().zoom) // Same zoom level
|
||||
.build();
|
||||
mapBox.animateCamera(CameraUpdateFactory.newCameraPosition(position), 1000);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public LatLng getLastLocation() {
|
||||
if (lastKnownLocation == null) {
|
||||
lastKnownLocation = locationManager.getLastLocation();
|
||||
}
|
||||
return lastKnownLocation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public com.mapbox.mapboxsdk.geometry.LatLng getLastFocusLocation() {
|
||||
return lastFocusLocation == null? null : LocationUtils.commonsLatLngToMapBoxLatLng(lastFocusLocation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disableFABRecenter() {
|
||||
fabRecenter.setEnabled(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enableFABRecenter() {
|
||||
fabRecenter.setEnabled(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addNearbyMarkersToMapBoxMap(List<NearbyBaseMarker> nearbyBaseMarkers, Marker selectedMarker) {
|
||||
mapBox.clear();
|
||||
if (isMapBoxReady && mapBox != null) {
|
||||
mapBox.addMarkers(nearbyBaseMarkers);
|
||||
setMapMarkerActions(selectedMarker);
|
||||
}
|
||||
}
|
||||
|
||||
private void setMapMarkerActions(final Marker selectedMarker) {
|
||||
if (mapBox != null) {
|
||||
mapBox.setOnInfoWindowCloseListener(marker -> {
|
||||
if (marker == selectedMarker) {
|
||||
presenter.markerUnselected();
|
||||
}
|
||||
});
|
||||
|
||||
mapBox.setOnMarkerClickListener(marker -> {
|
||||
if (marker instanceof NearbyMarker) {
|
||||
presenter.markerSelected(marker);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMapBoundaries(CameraUpdate cameaUpdate) {
|
||||
mapBox.easeCamera(cameaUpdate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFABRecenterAction(OnClickListener onClickListener) {
|
||||
fabRecenter.setOnClickListener(onClickListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean backButtonClicked() {
|
||||
if (!(bottomSheetDetailsBehavior.getState() == BottomSheetBehavior.STATE_HIDDEN)) {
|
||||
bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds network broadcast receiver to recognize connection established
|
||||
*/
|
||||
private void initNetworkBroadCastReceiver() {
|
||||
broadcastReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(final Context context, final Intent intent) {
|
||||
if (getActivity() != null) {
|
||||
if (NetworkUtils.isInternetConnectionEstablished(getActivity())) {
|
||||
if (isNetworkErrorOccurred) {
|
||||
presenter.updateMap(LOCATION_SIGNIFICANTLY_CHANGED);
|
||||
isNetworkErrorOccurred = false;
|
||||
}
|
||||
|
||||
if (snackbar != null) {
|
||||
snackbar.dismiss();
|
||||
snackbar = null;
|
||||
}
|
||||
} else {
|
||||
if (snackbar == null) {
|
||||
snackbar = Snackbar.make(getView(), R.string.no_internet, Snackbar.LENGTH_INDEFINITE);
|
||||
setSearchThisAreaButtonVisibility(false);
|
||||
setProgressBarVisibility(false);
|
||||
}
|
||||
|
||||
isNetworkErrorOccurred = true;
|
||||
snackbar.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,293 @@
|
|||
package fr.free.nrw.commons.explore.map;
|
||||
|
||||
import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED;
|
||||
import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.SEARCH_CUSTOM_AREA;
|
||||
|
||||
|
||||
import android.view.View;
|
||||
import com.mapbox.mapboxsdk.annotations.Marker;
|
||||
import com.mapbox.mapboxsdk.camera.CameraUpdateFactory;
|
||||
import com.mapbox.mapboxsdk.geometry.LatLngBounds;
|
||||
import fr.free.nrw.commons.MapController;
|
||||
import fr.free.nrw.commons.MapController.ExplorePlacesInfo;
|
||||
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao;
|
||||
import fr.free.nrw.commons.explore.map.ExploreMapController.NearbyBaseMarkerThumbCallback;
|
||||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||
import fr.free.nrw.commons.location.LatLng;
|
||||
import fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType;
|
||||
import fr.free.nrw.commons.nearby.NearbyBaseMarker;
|
||||
import fr.free.nrw.commons.utils.LocationUtils;
|
||||
import io.reactivex.Observable;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.List;
|
||||
import timber.log.Timber;
|
||||
|
||||
public class ExploreMapPresenter
|
||||
implements ExploreMapContract.UserActions,
|
||||
NearbyBaseMarkerThumbCallback {
|
||||
BookmarkLocationsDao bookmarkLocationDao;
|
||||
private boolean isNearbyLocked;
|
||||
private boolean placesLoadedOnce;
|
||||
private LatLng curLatLng;
|
||||
private ExploreMapController exploreMapController;
|
||||
|
||||
private static final ExploreMapContract.View DUMMY = (ExploreMapContract.View) Proxy
|
||||
.newProxyInstance(
|
||||
ExploreMapContract.View.class.getClassLoader(),
|
||||
new Class[]{ExploreMapContract.View.class}, (proxy, method, args) -> {
|
||||
if (method.getName().equals("onMyEvent")) {
|
||||
return null;
|
||||
} else if (String.class == method.getReturnType()) {
|
||||
return "";
|
||||
} else if (Integer.class == method.getReturnType()) {
|
||||
return Integer.valueOf(0);
|
||||
} else if (int.class == method.getReturnType()) {
|
||||
return 0;
|
||||
} else if (Boolean.class == method.getReturnType()) {
|
||||
return Boolean.FALSE;
|
||||
} else if (boolean.class == method.getReturnType()) {
|
||||
return false;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
);
|
||||
private ExploreMapContract.View exploreMapFragmentView = DUMMY;
|
||||
|
||||
public ExploreMapPresenter(BookmarkLocationsDao bookmarkLocationDao){
|
||||
this.bookmarkLocationDao = bookmarkLocationDao;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateMap(LocationChangeType locationChangeType) {
|
||||
Timber.d("Presenter updates map and list" + locationChangeType.toString());
|
||||
if (isNearbyLocked) {
|
||||
Timber.d("Nearby is locked, so updateMapAndList returns");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!exploreMapFragmentView.isNetworkConnectionEstablished()) {
|
||||
Timber.d("Network connection is not established");
|
||||
return;
|
||||
}
|
||||
|
||||
LatLng lastLocation = exploreMapFragmentView.getLastLocation();
|
||||
curLatLng = lastLocation;
|
||||
|
||||
if (curLatLng == null) {
|
||||
Timber.d("Skipping update of nearby places as location is unavailable");
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Significant changed - Markers and current location will be updated together
|
||||
* Slightly changed - Only current position marker will be updated
|
||||
*/
|
||||
if (locationChangeType.equals(LOCATION_SIGNIFICANTLY_CHANGED)) {
|
||||
Timber.d("LOCATION_SIGNIFICANTLY_CHANGED");
|
||||
lockUnlockNearby(true);
|
||||
exploreMapFragmentView.setProgressBarVisibility(true);
|
||||
exploreMapFragmentView.populatePlaces(curLatLng, lastLocation);
|
||||
|
||||
} else if (locationChangeType.equals(SEARCH_CUSTOM_AREA)) {
|
||||
Timber.d("SEARCH_CUSTOM_AREA");
|
||||
lockUnlockNearby(true);
|
||||
exploreMapFragmentView.setProgressBarVisibility(true);
|
||||
exploreMapFragmentView.populatePlaces(curLatLng, exploreMapFragmentView.getCameraTarget());
|
||||
} else { // Means location changed slightly, ie user is walking or driving.
|
||||
Timber.d("Means location changed slightly");
|
||||
if (exploreMapFragmentView.isCurrentLocationMarkerVisible()){ // Means user wants to see their live location
|
||||
exploreMapFragmentView.recenterMap(curLatLng);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Nearby updates takes time, since they are network operations. During update time, we don't
|
||||
* want to get any other calls from user. So locking nearby.
|
||||
* @param isNearbyLocked true means lock, false means unlock
|
||||
*/
|
||||
@Override
|
||||
public void lockUnlockNearby(boolean isNearbyLocked) {
|
||||
this.isNearbyLocked = isNearbyLocked;
|
||||
if (isNearbyLocked) {
|
||||
exploreMapFragmentView.disableFABRecenter();
|
||||
} else {
|
||||
exploreMapFragmentView.enableFABRecenter();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attachView(ExploreMapContract.View view) {
|
||||
exploreMapFragmentView = view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void detachView() {
|
||||
exploreMapFragmentView = DUMMY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets click listener of FAB
|
||||
*/
|
||||
@Override
|
||||
public void setActionListeners(JsonKvStore applicationKvStore) {
|
||||
exploreMapFragmentView.setFABRecenterAction(v -> {
|
||||
exploreMapFragmentView.recenterMap(curLatLng);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean backButtonClicked() {
|
||||
return exploreMapFragmentView.backButtonClicked();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCameraMove(com.mapbox.mapboxsdk.geometry.LatLng latLng) {
|
||||
exploreMapFragmentView.setProjectorLatLngBounds();
|
||||
// If our nearby markers are calculated at least once
|
||||
if (exploreMapController.latestSearchLocation != null) {
|
||||
double distance = latLng.distanceTo
|
||||
(LocationUtils.commonsLatLngToMapBoxLatLng(exploreMapController.latestSearchLocation));
|
||||
if (exploreMapFragmentView.isNetworkConnectionEstablished()) {
|
||||
if (distance > exploreMapController.latestSearchRadius && exploreMapController.latestSearchRadius != 0) {
|
||||
exploreMapFragmentView.setSearchThisAreaButtonVisibility(true);
|
||||
} else {
|
||||
exploreMapFragmentView.setSearchThisAreaButtonVisibility(false);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
exploreMapFragmentView.setSearchThisAreaButtonVisibility(false);
|
||||
}
|
||||
}
|
||||
|
||||
public void onMapReady(ExploreMapController exploreMapController) {
|
||||
this.exploreMapController = exploreMapController;
|
||||
exploreMapFragmentView.addSearchThisAreaButtonAction();
|
||||
if(null != exploreMapFragmentView) {
|
||||
exploreMapFragmentView.addSearchThisAreaButtonAction();
|
||||
initializeMapOperations();
|
||||
}
|
||||
}
|
||||
|
||||
public void initializeMapOperations() {
|
||||
lockUnlockNearby(false);
|
||||
updateMap(LOCATION_SIGNIFICANTLY_CHANGED);
|
||||
exploreMapFragmentView.addSearchThisAreaButtonAction();
|
||||
}
|
||||
|
||||
public Observable<ExplorePlacesInfo> loadAttractionsFromLocation(LatLng curLatLng, LatLng searchLatLng, boolean checkingAroundCurrent) {
|
||||
return Observable
|
||||
.fromCallable(() -> exploreMapController
|
||||
.loadAttractionsFromLocation(curLatLng, searchLatLng,checkingAroundCurrent));
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates places for custom location, should be used for finding nearby places around a
|
||||
* location where you are not at.
|
||||
* @param explorePlacesInfo This variable has placeToCenter list information and distances.
|
||||
*/
|
||||
public void updateMapMarkers(
|
||||
MapController.ExplorePlacesInfo explorePlacesInfo, Marker selectedMarker, boolean shouldTrackPosition) {
|
||||
exploreMapFragmentView.setMapBoundaries(CameraUpdateFactory.newLatLngBounds(getLatLngBounds(explorePlacesInfo.boundaryCoordinates), 50));
|
||||
prepareNearbyBaseMarkers(explorePlacesInfo, selectedMarker, shouldTrackPosition);
|
||||
}
|
||||
|
||||
void prepareNearbyBaseMarkers(MapController.ExplorePlacesInfo explorePlacesInfo, Marker selectedMarker, boolean shouldTrackPosition) {
|
||||
exploreMapController
|
||||
.loadAttractionsFromLocationToBaseMarkerOptions(explorePlacesInfo.curLatLng, // Curlatlang will be used to calculate distances
|
||||
explorePlacesInfo.explorePlaceList,
|
||||
exploreMapFragmentView.getContext(),
|
||||
this,
|
||||
selectedMarker,
|
||||
shouldTrackPosition,
|
||||
explorePlacesInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNearbyBaseMarkerThumbsReady(List<NearbyBaseMarker> baseMarkers, ExplorePlacesInfo explorePlacesInfo, Marker selectedMarker, boolean shouldTrackPosition) {
|
||||
if(null != exploreMapFragmentView) {
|
||||
exploreMapFragmentView.addNearbyMarkersToMapBoxMap(baseMarkers, selectedMarker);
|
||||
exploreMapFragmentView.addCurrentLocationMarker(explorePlacesInfo.curLatLng);
|
||||
if(shouldTrackPosition){
|
||||
exploreMapFragmentView.updateMapToTrackPosition(explorePlacesInfo.curLatLng);
|
||||
}
|
||||
lockUnlockNearby(false); // So that new location updates wont come
|
||||
exploreMapFragmentView.setProgressBarVisibility(false);
|
||||
handleCenteringTaskIfAny();
|
||||
}
|
||||
}
|
||||
|
||||
private LatLngBounds getLatLngBounds(LatLng[] boundaries) {
|
||||
LatLngBounds latLngBounds = new LatLngBounds.Builder()
|
||||
.include(LocationUtils.commonsLatLngToMapBoxLatLng(boundaries[0]))
|
||||
.include(LocationUtils.commonsLatLngToMapBoxLatLng(boundaries[1]))
|
||||
.include(LocationUtils.commonsLatLngToMapBoxLatLng(boundaries[2]))
|
||||
.include(LocationUtils.commonsLatLngToMapBoxLatLng(boundaries[3]))
|
||||
.build();
|
||||
return latLngBounds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Some centering task may need to wait for map to be ready, if they are requested before
|
||||
* map is ready. So we will remember it when the map is ready
|
||||
*/
|
||||
private void handleCenteringTaskIfAny() {
|
||||
if (!placesLoadedOnce) {
|
||||
placesLoadedOnce = true;
|
||||
exploreMapFragmentView.centerMapToPlace(null);
|
||||
}
|
||||
}
|
||||
|
||||
public View.OnClickListener onSearchThisAreaClicked() {
|
||||
return v -> {
|
||||
// Lock map operations during search this area operation
|
||||
exploreMapFragmentView.setSearchThisAreaButtonVisibility(false);
|
||||
|
||||
if (searchCloseToCurrentLocation()){
|
||||
updateMap(LOCATION_SIGNIFICANTLY_CHANGED);
|
||||
} else {
|
||||
updateMap(SEARCH_CUSTOM_AREA);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if search this area button is used around our current location, so that
|
||||
* we can continue following our current location again
|
||||
* @return Returns true if search this area button is used around our current location
|
||||
*/
|
||||
public boolean searchCloseToCurrentLocation() {
|
||||
if (null == exploreMapFragmentView.getLastFocusLocation() || exploreMapController.latestSearchRadius == 0) {
|
||||
return true;
|
||||
}
|
||||
double distance = LocationUtils.commonsLatLngToMapBoxLatLng(exploreMapFragmentView.getCameraTarget())
|
||||
.distanceTo(exploreMapFragmentView.getLastFocusLocation());
|
||||
if (distance > exploreMapController.currentLocationSearchRadius * 3 / 4) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void markerUnselected() {
|
||||
exploreMapFragmentView.hideBottomDetailsSheet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void markerSelected(Marker marker) {
|
||||
exploreMapFragmentView.displayBottomSheetWithInfo(marker);
|
||||
}
|
||||
|
||||
public boolean areLocationsClose(LatLng cameraTarget, LatLng lastKnownLocation) {
|
||||
double distance = LocationUtils.commonsLatLngToMapBoxLatLng(cameraTarget)
|
||||
.distanceTo(LocationUtils.commonsLatLngToMapBoxLatLng(lastKnownLocation));
|
||||
if (distance > exploreMapController.currentLocationSearchRadius * 3 / 4) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -15,6 +15,8 @@ import javax.inject.Singleton
|
|||
|
||||
const val PAGE_ID_PREFIX = "M"
|
||||
const val CATEGORY_CONTINUATION_PREFIX = "category_"
|
||||
const val LIMIT = 30
|
||||
const val RADIUS = 10000
|
||||
|
||||
/**
|
||||
* Media Client to handle custom calls to Commons MediaWiki APIs
|
||||
|
|
@ -90,6 +92,16 @@ class MediaClient @Inject constructor(
|
|||
fun getMediaListFromSearch(keyword: String?, limit: Int, offset: Int) =
|
||||
responseMapper(mediaInterface.getMediaListFromSearch(keyword, limit, offset))
|
||||
|
||||
/**
|
||||
* This method takes coordinate as input and returns a list of Media objects.
|
||||
* It uses the generator query API to get the images searched using a query.
|
||||
*
|
||||
* @param coordinate coordinate
|
||||
* @return
|
||||
*/
|
||||
fun getMediaListFromGeoSearch(coordinate: String?) =
|
||||
responseMapper(mediaInterface.getMediaListFromGeoSearch(coordinate, LIMIT, RADIUS))
|
||||
|
||||
/**
|
||||
* @return list of images for a particular depict entity
|
||||
*/
|
||||
|
|
@ -179,9 +191,9 @@ class MediaClient @Inject constructor(
|
|||
}
|
||||
|
||||
private fun mediaFromPageAndEntity(pages: List<MwQueryPage>): Single<List<Media>> {
|
||||
return if (pages.isEmpty())
|
||||
return if (pages.isEmpty()) {
|
||||
Single.just(emptyList())
|
||||
else
|
||||
} else {
|
||||
getEntities(pages.map { "$PAGE_ID_PREFIX${it.pageId()}" })
|
||||
.map {
|
||||
pages.zip(it.entities().values)
|
||||
|
|
@ -191,5 +203,7 @@ class MediaClient @Inject constructor(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import retrofit2.http.QueryMap;
|
|||
* Interface for interacting with Commons media related APIs
|
||||
*/
|
||||
public interface MediaInterface {
|
||||
String MEDIA_PARAMS="&prop=imageinfo&iiprop=url|extmetadata|user&&iiurlwidth=640" +
|
||||
String MEDIA_PARAMS="&prop=imageinfo|coordinates&iiprop=url|extmetadata|user&&iiurlwidth=640" +
|
||||
"&iiextmetadatafilter=DateTime|Categories|GPSLatitude|GPSLongitude|ImageDescription|DateTimeOriginal" +
|
||||
"|Artist|LicenseShortName|LicenseUrl";
|
||||
|
||||
|
|
@ -78,7 +78,20 @@ public interface MediaInterface {
|
|||
@GET("w/api.php?action=query&format=json&formatversion=2" + //Basic parameters
|
||||
"&generator=search&gsrwhat=text&gsrnamespace=6" + //Search parameters
|
||||
MEDIA_PARAMS)
|
||||
Single<MwQueryResponse> getMediaListFromSearch(@Query("gsrsearch") String keyword, @Query("gsrlimit") int itemLimit, @Query("gsroffset") int offset);
|
||||
Single<MwQueryResponse> getMediaListFromSearch(@Query("gsrsearch") String keyword,
|
||||
@Query("gsrlimit") int itemLimit, @Query("gsroffset") int offset);
|
||||
|
||||
/**
|
||||
* This method retrieves a list of Media objects filtered using list geosearch query. Example: https://commons.wikimedia.org/w/api.php?action=query&format=json&formatversion=2&generator=geosearch&ggsnamespace=6&prop=imageinfo|coordinates&iiprop=url|extmetadata|user&&iiurlwidth=640&iiextmetadatafilter=DateTime|Categories|GPSLatitude|GPSLongitude|ImageDescription|DateTimeOriginal|Artist|LicenseShortName|LicenseUrl&ggscoord=37.45579%7C-122.31369&ggslimit=30&ggsradius=10000
|
||||
*
|
||||
* @param location the search location
|
||||
* @param itemLimit how many images are returned
|
||||
* @return
|
||||
*/
|
||||
@GET("w/api.php?action=query&format=json&formatversion=2" + //Basic parameters
|
||||
"&generator=geosearch&ggsnamespace=6" + //Search parameters
|
||||
MEDIA_PARAMS)
|
||||
Single<MwQueryResponse> getMediaListFromGeoSearch(@Query("ggscoord") String location, @Query("ggslimit") int itemLimit, @Query("ggsradius") int radius);
|
||||
|
||||
/**
|
||||
* Fetches Media object from the imageInfo API
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ import androidx.annotation.Nullable;
|
|||
import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;
|
||||
import com.mapbox.mapboxsdk.annotations.IconFactory;
|
||||
import com.mapbox.mapboxsdk.annotations.Marker;
|
||||
|
||||
import fr.free.nrw.commons.MapController;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.location.LatLng;
|
||||
import fr.free.nrw.commons.utils.UiUtils;
|
||||
|
|
@ -24,7 +26,11 @@ import java.util.Map;
|
|||
import javax.inject.Inject;
|
||||
import timber.log.Timber;
|
||||
|
||||
public class NearbyController {
|
||||
import static fr.free.nrw.commons.utils.LengthUtils.computeDistanceBetween;
|
||||
import static fr.free.nrw.commons.utils.LengthUtils.formatDistanceBetween;
|
||||
|
||||
public class NearbyController extends MapController {
|
||||
|
||||
private static final int MAX_RESULTS = 1000;
|
||||
private final NearbyPlaces nearbyPlaces;
|
||||
public static double currentLocationSearchRadius = 10.0; //in kilometers
|
||||
|
|
@ -223,16 +229,6 @@ public class NearbyController {
|
|||
return baseMarkerOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* We pass this variable as a group of placeList and boundaryCoordinates
|
||||
*/
|
||||
public class NearbyPlacesInfo {
|
||||
public List<Place> placeList; // List of nearby places
|
||||
public LatLng[] boundaryCoordinates; // Corners of nearby area
|
||||
public LatLng curLatLng; // Current location when this places are populated
|
||||
public LatLng searchLatLng; // Search location for finding this places
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates makerLabelList item isBookmarked value
|
||||
* @param place place which is bookmarked
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import android.os.Parcelable;
|
|||
import android.text.TextUtils;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import fr.free.nrw.commons.nearby.NearbyController.NearbyPlacesInfo;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import fr.free.nrw.commons.location.LatLng;
|
||||
|
|
@ -34,7 +33,19 @@ public class Place implements Parcelable {
|
|||
public String distance;
|
||||
public final Sitelinks siteLinks;
|
||||
private boolean isMonument;
|
||||
private String thumb;
|
||||
|
||||
public Place() {
|
||||
language = null;
|
||||
name = null;
|
||||
label = null;
|
||||
longDescription = null;
|
||||
location = null;
|
||||
category = null;
|
||||
pic = null;
|
||||
exists = null;
|
||||
siteLinks = null;
|
||||
}
|
||||
|
||||
public Place(String language,String name, Label label, String longDescription, LatLng location, String category, Sitelinks siteLinks, String pic, Boolean exists) {
|
||||
this.language = language;
|
||||
|
|
@ -47,6 +58,20 @@ public class Place implements Parcelable {
|
|||
this.pic = (pic == null) ? "":pic;
|
||||
this.exists = exists;
|
||||
}
|
||||
|
||||
public Place(String name, String longDescription, LatLng location, String category, Sitelinks siteLinks, String pic, String thumb) {
|
||||
this.name = name;
|
||||
this.longDescription = longDescription;
|
||||
this.location = location;
|
||||
this.category = category;
|
||||
this.siteLinks = siteLinks;
|
||||
this.pic = (pic == null) ? "":pic;
|
||||
this.thumb = thumb;
|
||||
this.language = null;
|
||||
this.label = null;
|
||||
this.exists = true;
|
||||
}
|
||||
|
||||
public Place(Parcel in) {
|
||||
this.language = in.readString();
|
||||
this.name = in.readString();
|
||||
|
|
@ -269,4 +294,12 @@ public class Place implements Parcelable {
|
|||
return new Place[size];
|
||||
}
|
||||
};
|
||||
|
||||
public String getThumb() {
|
||||
return thumb;
|
||||
}
|
||||
|
||||
public void setThumb(String thumb) {
|
||||
this.thumb = thumb;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,6 +81,7 @@ import com.mapbox.mapboxsdk.maps.UiSettings;
|
|||
import com.mapbox.pluginscalebar.ScaleBarOptions;
|
||||
import com.mapbox.pluginscalebar.ScaleBarPlugin;
|
||||
import fr.free.nrw.commons.CommonsApplication;
|
||||
import fr.free.nrw.commons.MapController.NearbyPlacesInfo;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.Utils;
|
||||
import fr.free.nrw.commons.auth.LoginActivity;
|
||||
|
|
@ -97,7 +98,6 @@ import fr.free.nrw.commons.nearby.Label;
|
|||
import fr.free.nrw.commons.nearby.MarkerPlaceGroup;
|
||||
import fr.free.nrw.commons.nearby.NearbyBaseMarker;
|
||||
import fr.free.nrw.commons.nearby.NearbyController;
|
||||
import fr.free.nrw.commons.nearby.NearbyController.NearbyPlacesInfo;
|
||||
import fr.free.nrw.commons.nearby.NearbyFilterSearchRecyclerViewAdapter;
|
||||
import fr.free.nrw.commons.nearby.NearbyFilterState;
|
||||
import fr.free.nrw.commons.nearby.NearbyMarker;
|
||||
|
|
@ -710,7 +710,6 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
|
|||
|
||||
@Override
|
||||
public void setFilterState() {
|
||||
Log.d("deneme5","setfilterState");
|
||||
chipNeedsPhoto.setChecked(NearbyFilterState.getInstance().isNeedPhotoSelected());
|
||||
chipExists.setChecked(NearbyFilterState.getInstance().isExistsSelected());
|
||||
chipWlm.setChecked(NearbyFilterState.getInstance().isWlmSelected());
|
||||
|
|
@ -1077,7 +1076,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
|
|||
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<NearbyController.NearbyPlacesInfo> nearbyPlacesInfoObservable = Observable
|
||||
.fromCallable(() -> nearbyController
|
||||
.loadAttractionsFromLocation(curlatLng, searchLatLng,
|
||||
false, true, Utils.isMonumentsEnabled(new Date()), customQuery));
|
||||
|
|
|
|||
|
|
@ -196,7 +196,7 @@ public class NearbyParentFragmentPresenter
|
|||
Timber.d("ADVANCED_QUERY_SEARCH");
|
||||
lockUnlockNearby(true);
|
||||
nearbyParentFragmentView.setProgressBarVisibility(true);
|
||||
LatLng updatedLocationByUser = deriveUpdatedLocationFromSearchQuery(customQuery);
|
||||
LatLng updatedLocationByUser = LocationUtils.deriveUpdatedLocationFromSearchQuery(customQuery);
|
||||
if (updatedLocationByUser == null) {
|
||||
updatedLocationByUser = lastLocation;
|
||||
}
|
||||
|
|
@ -222,38 +222,6 @@ public class NearbyParentFragmentPresenter
|
|||
}
|
||||
}
|
||||
|
||||
private LatLng deriveUpdatedLocationFromSearchQuery(String customQuery) {
|
||||
LatLng latLng = null;
|
||||
final int indexOfPrefix = customQuery.indexOf("Point(");
|
||||
if (indexOfPrefix == -1) {
|
||||
Timber.e("Invalid prefix index - Seems like user has entered an invalid query");
|
||||
return latLng;
|
||||
}
|
||||
final int indexOfSuffix = customQuery.indexOf(")\"", indexOfPrefix);
|
||||
if (indexOfSuffix == -1) {
|
||||
Timber.e("Invalid suffix index - Seems like user has entered an invalid query");
|
||||
return latLng;
|
||||
}
|
||||
String latLngString = customQuery.substring(indexOfPrefix+"Point(".length(), indexOfSuffix);
|
||||
if (latLngString.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String latLngArray[] = latLngString.split(" ");
|
||||
if (latLngArray.length != 2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
latLng = new LatLng(Double.parseDouble(latLngArray[1].trim()),
|
||||
Double.parseDouble(latLngArray[0].trim()), 1f);
|
||||
}catch (Exception e){
|
||||
Timber.e("Error while parsing user entered lat long: %s", e);
|
||||
}
|
||||
|
||||
return latLng;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates places for custom location, should be used for finding nearby places around a
|
||||
* location where you are not at.
|
||||
|
|
|
|||
|
|
@ -5,10 +5,12 @@ import android.app.WallpaperManager;
|
|||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.net.Uri;
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.exifinterface.media.ExifInterface;
|
||||
import com.facebook.common.executors.CallerThreadExecutor;
|
||||
import com.facebook.common.references.CloseableReference;
|
||||
|
|
@ -340,4 +342,19 @@ public class ImageUtils {
|
|||
|
||||
return errorMessage.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds red border to a bitmap
|
||||
* @param bitmap
|
||||
* @param borderSize
|
||||
* @param context
|
||||
* @return
|
||||
*/
|
||||
public static Bitmap addRedBorder(Bitmap bitmap, int borderSize, Context context) {
|
||||
Bitmap bmpWithBorder = Bitmap.createBitmap(bitmap.getWidth() + borderSize * 2, bitmap.getHeight() + borderSize * 2, bitmap.getConfig());
|
||||
Canvas canvas = new Canvas(bmpWithBorder);
|
||||
canvas.drawColor(ContextCompat.getColor(context, R.color.deleteRed));
|
||||
canvas.drawBitmap(bitmap, borderSize, borderSize, null);
|
||||
return bmpWithBorder;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package fr.free.nrw.commons.utils;
|
||||
|
||||
import fr.free.nrw.commons.location.LatLng;
|
||||
import timber.log.Timber;
|
||||
|
||||
public class LocationUtils {
|
||||
public static LatLng mapBoxLatLngToCommonsLatLng(com.mapbox.mapboxsdk.geometry.LatLng mapBoxLatLng) {
|
||||
|
|
@ -10,4 +11,36 @@ public class LocationUtils {
|
|||
public static com.mapbox.mapboxsdk.geometry.LatLng commonsLatLngToMapBoxLatLng(LatLng commonsLatLng) {
|
||||
return new com.mapbox.mapboxsdk.geometry.LatLng(commonsLatLng.getLatitude(), commonsLatLng.getLongitude());
|
||||
}
|
||||
|
||||
public static 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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
73
app/src/main/java/fr/free/nrw/commons/utils/MapUtils.java
Normal file
73
app/src/main/java/fr/free/nrw/commons/utils/MapUtils.java
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
package fr.free.nrw.commons.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import com.mapbox.mapboxsdk.camera.CameraPosition;
|
||||
import com.mapbox.mapboxsdk.camera.CameraUpdateFactory;
|
||||
import com.mapbox.mapboxsdk.maps.MapboxMap;
|
||||
import fr.free.nrw.commons.location.LatLng;
|
||||
import fr.free.nrw.commons.location.LocationServiceManager;
|
||||
import fr.free.nrw.commons.location.LocationUpdateListener;
|
||||
import fr.free.nrw.commons.nearby.Place;
|
||||
import timber.log.Timber;
|
||||
|
||||
public class MapUtils {
|
||||
public static final float ZOOM_LEVEL = 14f;
|
||||
public static final double CAMERA_TARGET_SHIFT_FACTOR_PORTRAIT = 0.005;
|
||||
public static final double CAMERA_TARGET_SHIFT_FACTOR_LANDSCAPE = 0.004;
|
||||
public static final String NETWORK_INTENT_ACTION = "android.net.conn.CONNECTIVITY_CHANGE";
|
||||
public static final float ZOOM_OUT = 0f;
|
||||
|
||||
public static final LatLng defaultLatLng = new fr.free.nrw.commons.location.LatLng(51.50550,-0.07520,1f);
|
||||
|
||||
public static void centerMapToPlace(Place placeToCenter, MapboxMap mapBox, Place lastPlaceToCenter, Context context) {
|
||||
Timber.d("Map is centered to place");
|
||||
final double cameraShift;
|
||||
if(null != placeToCenter){
|
||||
lastPlaceToCenter = placeToCenter;
|
||||
}
|
||||
if (null != lastPlaceToCenter) {
|
||||
final Configuration configuration = context.getResources().getConfiguration();
|
||||
if (configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
|
||||
cameraShift = CAMERA_TARGET_SHIFT_FACTOR_PORTRAIT;
|
||||
} else {
|
||||
cameraShift = CAMERA_TARGET_SHIFT_FACTOR_LANDSCAPE;
|
||||
}
|
||||
final CameraPosition position = new CameraPosition.Builder()
|
||||
.target(LocationUtils.commonsLatLngToMapBoxLatLng(
|
||||
new fr.free.nrw.commons.location.LatLng(lastPlaceToCenter.location.getLatitude() - cameraShift,
|
||||
lastPlaceToCenter.getLocation().getLongitude(),
|
||||
0))) // Sets the new camera position
|
||||
.zoom(ZOOM_LEVEL) // Same zoom level
|
||||
.build();
|
||||
mapBox.animateCamera(CameraUpdateFactory.newCameraPosition(position), 1000);
|
||||
}
|
||||
}
|
||||
|
||||
public static void centerMapToDefaultLatLng(MapboxMap mapBox) {
|
||||
final CameraPosition position = new CameraPosition.Builder()
|
||||
.target(LocationUtils.commonsLatLngToMapBoxLatLng(defaultLatLng))
|
||||
.zoom(MapUtils.ZOOM_OUT)
|
||||
.build();
|
||||
if(mapBox != null){
|
||||
mapBox.moveCamera(CameraUpdateFactory.newCameraPosition(position));
|
||||
}
|
||||
}
|
||||
|
||||
public static void registerUnregisterLocationListener(final boolean removeLocationListener, LocationServiceManager locationManager, LocationUpdateListener locationUpdateListener) {
|
||||
try {
|
||||
if (removeLocationListener) {
|
||||
locationManager.unregisterLocationManager();
|
||||
locationManager.removeLocationListener(locationUpdateListener);
|
||||
Timber.d("Location service manager unregistered and removed");
|
||||
} else {
|
||||
locationManager.addLocationListener(locationUpdateListener);
|
||||
locationManager.registerLocationManager();
|
||||
Timber.d("Location service manager added and registered");
|
||||
}
|
||||
}catch (final Exception e){
|
||||
Timber.e(e);
|
||||
//Broadcasts are tricky, should be catchedonR
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,10 @@
|
|||
package fr.free.nrw.commons.utils;
|
||||
|
||||
import fr.free.nrw.commons.Media;
|
||||
import fr.free.nrw.commons.nearby.Place;
|
||||
import fr.free.nrw.commons.nearby.Sitelinks;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
|
|
@ -23,4 +28,27 @@ public class PlaceUtils {
|
|||
|
||||
return new LatLng(latitude, longitude, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns a Media list to a Place list by creating a new list in Place type
|
||||
* @param mediaList
|
||||
* @return
|
||||
*/
|
||||
public static List<Place> mediaToExplorePlace( List<Media> mediaList) {
|
||||
List<Place> explorePlaceList = new ArrayList<>();
|
||||
for (Media media :mediaList) {
|
||||
explorePlaceList.add(new Place(media.getFilename(),
|
||||
media.getFallbackDescription(),
|
||||
media.getCoordinates(),
|
||||
media.getCategories().toString(),
|
||||
new Sitelinks.Builder()
|
||||
.setCommonsLink(media.getPageTitle().getCanonicalUri())
|
||||
.setWikipediaLink("") // we don't necessarily have them, can be fetched later
|
||||
.setWikidataLink("") // we don't necessarily have them, can be fetched later
|
||||
.build(),
|
||||
media.getImageUrl(),
|
||||
media.getThumbUrl()));
|
||||
}
|
||||
return explorePlaceList;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue