mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-26 20:33:53 +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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
BIN
app/src/main/res/drawable/image_placeholder_96.png
Normal file
BIN
app/src/main/res/drawable/image_placeholder_96.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
|
|
@ -68,7 +68,7 @@
|
|||
android:layout_below="@id/toolbar_layout"
|
||||
/>
|
||||
|
||||
<androidx.viewpager.widget.ViewPager
|
||||
<fr.free.nrw.commons.explore.ParentViewPager
|
||||
android:id="@+id/viewPager"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
|
|
|
|||
162
app/src/main/res/layout/bottom_sheet_details_explore.xml
Normal file
162
app/src/main/res/layout/bottom_sheet_details_explore.xml
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="vertical"
|
||||
android:background="?attr/mainBackground"
|
||||
app:layout_behavior="@string/bottom_sheet_behavior"
|
||||
app:behavior_peekHeight="@dimen/large_height"
|
||||
app:behavior_hideable="true"
|
||||
android:visibility="visible"
|
||||
|
||||
>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/large_height"
|
||||
android:layout_marginVertical="@dimen/activity_margin_horizontal"
|
||||
android:gravity="center_vertical"
|
||||
>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginLeft="@dimen/standard_gap"
|
||||
android:layout_marginRight="@dimen/standard_gap">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="16sp"
|
||||
android:layout_marginRight="50dp"
|
||||
android:maxLines="2"
|
||||
android:ellipsize="end"
|
||||
/>
|
||||
<TextView
|
||||
android:id="@+id/category"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="14sp" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/tiny_height"
|
||||
android:layout_marginTop="@dimen/small_height"
|
||||
android:layout_marginBottom="@dimen/activity_margin_horizontal"
|
||||
android:background="@android:color/darker_gray"/>
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
>
|
||||
<LinearLayout
|
||||
android:id="@+id/directionsButton"
|
||||
android:layout_width="@dimen/dimen_0"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:padding="@dimen/standard_gap"
|
||||
android:clickable="true"
|
||||
android:background="@drawable/button_background_selector"
|
||||
android:orientation="vertical"
|
||||
>
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:duplicateParentState="true"
|
||||
app:srcCompat="@drawable/ic_directions_black_24dp"
|
||||
android:tint="?attr/rowButtonColor"
|
||||
/>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/directionsButtonText"
|
||||
android:paddingTop="@dimen/activity_margin_horizontal"
|
||||
android:duplicateParentState="true"
|
||||
android:textColor="@color/text_color_selector"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:text="@string/nearby_directions"
|
||||
android:textAllCaps="true"
|
||||
/>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/commonsButton"
|
||||
android:layout_width="@dimen/dimen_0"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:padding="@dimen/standard_gap"
|
||||
android:clickable="true"
|
||||
android:background="@drawable/button_background_selector"
|
||||
android:orientation="vertical"
|
||||
>
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:duplicateParentState="true"
|
||||
app:srcCompat="@drawable/ic_commons_icon_vector"
|
||||
/>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/commonsButtonText"
|
||||
android:paddingTop="@dimen/activity_margin_horizontal"
|
||||
android:duplicateParentState="true"
|
||||
android:textColor="@color/text_color_selector"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:text="@string/nearby_commons"
|
||||
android:textAllCaps="true"
|
||||
/>
|
||||
</LinearLayout>
|
||||
<LinearLayout
|
||||
android:id="@+id/mediaDetailsButton"
|
||||
android:layout_width="@dimen/dimen_0"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:padding="@dimen/standard_gap"
|
||||
android:clickable="true"
|
||||
android:background="@drawable/button_background_selector"
|
||||
android:orientation="vertical"
|
||||
>
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:duplicateParentState="true"
|
||||
app:srcCompat="@drawable/ic_search_blue_24dp"
|
||||
/>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/mediaDetailsButtonText"
|
||||
android:paddingTop="@dimen/activity_margin_horizontal"
|
||||
android:duplicateParentState="true"
|
||||
android:textColor="@color/text_color_selector"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:text="@string/explore_map_details"
|
||||
android:textAllCaps="true"
|
||||
/>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/tiny_height"
|
||||
android:layout_marginTop="@dimen/small_height"
|
||||
android:layout_marginBottom="@dimen/activity_margin_horizontal"
|
||||
android:background="@android:color/darker_gray"/>
|
||||
<TextView
|
||||
android:id="@+id/description"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="@dimen/large_height"
|
||||
android:layout_marginRight="@dimen/standard_gap"
|
||||
android:layout_marginBottom="@dimen/standard_gap"
|
||||
android:textSize="16sp" />
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
83
app/src/main/res/layout/fragment_explore_map.xml
Normal file
83
app/src/main/res/layout/fragment_explore_map.xml
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/coordinator_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<!-- I have done this intentionally, the mapview because of some elevation or something,
|
||||
sometimes hangs over the drawer layout and sometimes draws its onPaused state over the contributions, this seems to be the probable fix -->
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_below="@id/container">
|
||||
|
||||
<com.mapbox.mapboxsdk.maps.MapView
|
||||
android:id="@+id/map_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" >
|
||||
</com.mapbox.mapboxsdk.maps.MapView>
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/fab_recenter"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:visibility="visible"
|
||||
app:backgroundTint="@color/main_background_light"
|
||||
app:elevation="@dimen/dimen_6"
|
||||
app:fabSize="normal"
|
||||
app:srcCompat="@drawable/ic_my_location_black_24dp"
|
||||
app:useCompatPadding="true" />
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@android:color/transparent" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/tv_attribution"
|
||||
android:textStyle="bold"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:text="@string/map_attribution"
|
||||
android:textAlignment="center"
|
||||
android:textSize="10sp" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/search_this_area_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_margin="@dimen/activity_margin_horizontal"
|
||||
android:background="@color/white"
|
||||
android:padding="@dimen/activity_margin_horizontal"
|
||||
android:singleLine="true"
|
||||
android:text="@string/search_this_area"
|
||||
android:textColor="@color/status_bar_blue"
|
||||
android:visibility="gone"
|
||||
app:elevation="@dimen/dimen_6"
|
||||
/>
|
||||
|
||||
<include
|
||||
android:id="@+id/bottom_sheet_details"
|
||||
layout="@layout/bottom_sheet_details_explore" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/map_progress_bar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
app:elevation="@dimen/dimen_6"
|
||||
android:visibility="gone" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
|
@ -302,6 +302,7 @@
|
|||
|
||||
<string name="explore_tab_title_featured">Featured</string>
|
||||
<string name="explore_tab_title_mobile">Uploaded via mobile</string>
|
||||
<string name="explore_tab_title_map">Map</string>
|
||||
<string name="successful_wikidata_edit">Image added to %1$s on Wikidata!</string>
|
||||
<string name="wikidata_edit_failure">Failed to update corresponding Wikidata entity!</string>
|
||||
<string name="menu_set_wallpaper">Set as wallpaper</string>
|
||||
|
|
@ -701,6 +702,7 @@ Upload your first media by tapping on the add button.</string>
|
|||
<string name="no_location_found_message">How about adding the place where this image was taken?\nLocation data helps Wiki editors find your picture, making it much more useful.\nThank you!</string>
|
||||
<string name="add_location">Add location</string>
|
||||
<string name="feedback_sharing_data_alert">Please remove from this email any information that you are not comfortable sharing publicly. Also, please be aware that your email address with which you are posting, and the associated name and profile picture, will be visible publicly.</string>
|
||||
<string name="explore_map_details">Details</string>
|
||||
<string name="achievements_unavailable_beta">Achievements are only available in the prod flavor, please check the developer documentation.</string>
|
||||
<string name="leaderboard_unavailable_beta">The leaderboard is only available in the prod flavor, please check the developer documentation.</string>
|
||||
<string name="copyright_popup">Please only upload pictures you have taken by yourself. Uploaders of copyrighted images will be blocked. This applies to the beta flavor too. Thank you for testing the app!</string>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue