* Fixes #3303
* Refactor Nearby to alig lifecycle methods

* Pass updated place list to listfragment

* Added default zoom rate to mapbox

* Removed NearbyListFragmet and added the ui login to handle the same in NearbyParentFragment

* More code refactor
* Make BottomSheetList hideable
* onFragmentHide, hide the bottom sheets

* BigFix, Fragmet visibility, register/un-register camera move based on fragments lifecycke

* More code refactor
* Let the ExecutorUtil have non-ui thread
* Add Location Marker on non-ui thread (the non-ui stuffs)

* BugFixes
* Removed configchanges "orientation" from MainActivity in Manifest (That was messing with the fragment lifecycle)
* Some null checks
* Initialise lastknown location in onMapReady

* UI Fixes
* Adjusted UI to support dark and no-dark themes both (in nearby)
* Do not update map on Location Slightly changed

* Fix failing test case, let TestCommonsApplication extend Application instead of CommonsApplication

* start map view when nearby is visible

* start the map when NearbyFragmet is visible

* More bugfixes
* Added DUMMY view for NearbyPresenter's onDetach State
* Added a wrapper frame layout parent for MapView to preven it from drawing above other views

* More bugfixes (Fixes #3287)
* Gray out the un-selected markers from the nearby filter list

* BugFix, search this area should search the nearby places for the current camera position

* More BugFixes
* Handle null primitives with proxy
* Current location marker flow via permission flow

* onCameraMove should have null-check on NearbyController.latestSearchLocation instead of currentLocation

* Search for places around last focus location

* Handle location updates
* If the user is browsing the map, donot update the map with current location
This commit is contained in:
Ashish Kumar 2020-02-04 14:35:29 +05:30 committed by GitHub
parent a6d2523588
commit 05e98307be
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 915 additions and 1254 deletions

View file

@ -36,7 +36,7 @@ dependencies {
implementation 'fr.avianey.com.viewpagerindicator:library:2.4.1.1@aar'
implementation 'com.github.chrisbanes:PhotoView:2.0.0'
implementation 'com.github.pedrovgs:renderers:3.3.3'
implementation 'com.mapbox.mapboxsdk:mapbox-android-sdk:7.2.0'
implementation 'com.mapbox.mapboxsdk:mapbox-android-sdk:8.6.1'
implementation 'com.mapbox.mapboxsdk:mapbox-android-plugin-localization-v7:0.7.0'
implementation 'com.github.deano2390:MaterialShowcaseView:1.2.0'
implementation 'com.dinuscxj:circleprogressbar:1.1.1'
@ -102,6 +102,7 @@ dependencies {
//swipe_layout
implementation 'com.daimajia.swipelayout:library:1.2.0@aar'
implementation 'com.squareup.retrofit2:retrofit:2.7.1'
}
android {

View file

@ -78,7 +78,7 @@
android:name=".contributions.MainActivity"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:configChanges="orientation|screenSize|keyboard" />
android:configChanges="screenSize|keyboard" />
<activity
android:name=".settings.SettingsActivity"
android:label="@string/title_activity_settings" />

View file

@ -19,6 +19,7 @@ import com.facebook.imagepipeline.producers.Consumer;
import com.facebook.imagepipeline.producers.FetchState;
import com.facebook.imagepipeline.producers.NetworkFetcher;
import com.facebook.imagepipeline.producers.ProducerContext;
import com.mapbox.mapboxsdk.Mapbox;
import com.squareup.leakcanary.LeakCanary;
import com.squareup.leakcanary.RefWatcher;
@ -134,6 +135,7 @@ public class CommonsApplication extends Application {
INSTANCE = this;
ACRA.init(this);
Mapbox.getInstance(this, getString(R.string.mapbox_commons_app_token));
ApplicationlessInjection
.getInstance(this)

View file

@ -35,7 +35,6 @@ import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.location.LocationServiceManager;
import fr.free.nrw.commons.nearby.NearbyNotificationCardView;
import fr.free.nrw.commons.nearby.fragments.NearbyParentFragment;
import fr.free.nrw.commons.nearby.presenter.NearbyParentFragmentPresenter;
import fr.free.nrw.commons.notification.Notification;
import fr.free.nrw.commons.notification.NotificationActivity;
import fr.free.nrw.commons.notification.NotificationController;
@ -78,6 +77,7 @@ public class MainActivity extends NavigationBaseActivity implements FragmentMana
private MenuItem notificationsMenuItem;
private TextView notificationCount;
private NearbyParentFragment nearbyParentFragment;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -188,8 +188,6 @@ public class MainActivity extends NavigationBaseActivity implements FragmentMana
tabLayout.getTabAt(NEARBY_TAB_POSITION).select();
isContributionsFragmentVisible = false;
updateMenuItem();
// Do all permission and GPS related tasks on tab selected, not on create
NearbyParentFragmentPresenter.getInstance().onTabSelected();
break;
default:
tabLayout.getTabAt(CONTRIBUTIONS_TAB_POSITION).select();
@ -265,7 +263,9 @@ public class MainActivity extends NavigationBaseActivity implements FragmentMana
}
} else if (getSupportFragmentManager().findFragmentByTag(nearbyFragmentTag) != null && !isContributionsFragmentVisible) {
// Means that nearby fragment is visible (not contributions fragment)
NearbyParentFragmentPresenter.getInstance().backButtonClicked();
if (null != nearbyParentFragment) {
nearbyParentFragment.backButtonClicked();
}
} else {
super.onBackPressed();
}
@ -380,12 +380,13 @@ public class MainActivity extends NavigationBaseActivity implements FragmentMana
}
case 1:
NearbyParentFragment retainedNearbyFragment = getNearbyFragment(1);
if (retainedNearbyFragment != null) {
return retainedNearbyFragment;
nearbyParentFragment = getNearbyFragment(1);
if (nearbyParentFragment != null) {
return nearbyParentFragment;
} else {
// If we reach here, retainedNearbyFragment is null
return new NearbyParentFragment();
nearbyParentFragment=new NearbyParentFragment();
return nearbyParentFragment;
}
default:
return null;

View file

@ -13,8 +13,6 @@ import fr.free.nrw.commons.explore.images.SearchImageFragment;
import fr.free.nrw.commons.explore.recentsearches.RecentSearchesFragment;
import fr.free.nrw.commons.media.MediaDetailFragment;
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
import fr.free.nrw.commons.nearby.fragments.NearbyListFragment;
import fr.free.nrw.commons.nearby.fragments.NearbyMapFragment;
import fr.free.nrw.commons.nearby.fragments.NearbyParentFragment;
import fr.free.nrw.commons.review.ReviewImageFragment;
import fr.free.nrw.commons.settings.SettingsFragment;
@ -40,9 +38,6 @@ public abstract class FragmentBuilderModule {
@ContributesAndroidInjector
abstract MediaDetailPagerFragment bindMediaDetailPagerFragment();
@ContributesAndroidInjector
abstract NearbyListFragment bindNearbyListFragment();
@ContributesAndroidInjector
abstract SettingsFragment bindSettingsFragment();
@ -64,9 +59,6 @@ public abstract class FragmentBuilderModule {
@ContributesAndroidInjector
abstract ContributionsFragment bindContributionsFragment();
@ContributesAndroidInjector
abstract NearbyMapFragment bindNearbyMapFragment();
@ContributesAndroidInjector
abstract NearbyParentFragment bindNearbyParentFragment();

View file

@ -9,6 +9,8 @@ import android.widget.CompoundButton;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatCheckBox;
import java.util.List;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.nearby.presenter.NearbyParentFragmentPresenter;
@ -25,6 +27,16 @@ public class CheckBoxTriStates extends AppCompatCheckBox {
private int state;
private Callback callback;
public interface Callback{
void filterByMarkerType(@Nullable List<Label> selectedLabels, int state, boolean b, boolean b1);
}
public void setCallback(Callback callback) {
this.callback = callback;
}
/**
* This is the listener set to the super class which is going to be evoke each
* time the check state has changed.
@ -87,7 +99,7 @@ public class CheckBoxTriStates extends AppCompatCheckBox {
}
if (NearbyController.currentLocation != null) {
NearbyParentFragmentPresenter.getInstance().filterByMarkerType(null, state, false, true);
callback.filterByMarkerType(null, state, false, true);
}
updateBtn();
}

View file

@ -17,10 +17,6 @@ public class NearbyAdapterFactory {
private Fragment fragment;
private ContributionController controller;
NearbyAdapterFactory(){
}
public NearbyAdapterFactory(Fragment fragment, ContributionController controller) {
this.fragment = fragment;
this.controller = controller;

View file

@ -27,20 +27,24 @@ public class NearbyFilterSearchRecyclerViewAdapter
private final LayoutInflater inflater;
private Context context;
private RecyclerView recyclerView;
private ArrayList<Label> labels;
private ArrayList<Label> displayedLabels;
public ArrayList<Label> selectedLabels = new ArrayList<>();
private int state;
private Callback callback;
RecyclerView.SmoothScroller smoothScroller;
public void setCallback(Callback callback) {
this.callback = callback;
}
public NearbyFilterSearchRecyclerViewAdapter(Context context, ArrayList<Label> labels, RecyclerView recyclerView) {
this.context = context;
this.labels = labels;
this.displayedLabels = labels;
this.recyclerView = recyclerView;
smoothScroller = new
LinearSmoothScroller(context) {
@Override protected int getVerticalSnapPreference() {
@ -66,7 +70,7 @@ public class NearbyFilterSearchRecyclerViewAdapter
@NonNull
@Override
public RecyclerViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View itemView = inflater.inflate(R.layout.nearby_search_list_item, parent, false);
View itemView = inflater.inflate(callback.isDarkTheme()?R.layout.nearby_search_list_item_dark:R.layout.nearby_search_list_item, parent, false);
return new RecyclerViewHolder(itemView);
}
@ -76,9 +80,9 @@ public class NearbyFilterSearchRecyclerViewAdapter
holder.placeTypeIcon.setImageResource(label.getIcon());
holder.placeTypeLabel.setText(label.toString());
holder.placeTypeLayout.setBackgroundColor(label.isSelected() ? ContextCompat.getColor(context, R.color.divider_grey) : Color.WHITE);
holder.placeTypeLayout.setBackgroundColor(label.isSelected() ? ContextCompat.getColor(context, R.color.divider_grey) : callback.isDarkTheme()?Color.BLACK:Color.WHITE);
holder.placeTypeLayout.setOnClickListener(view -> {
NearbyParentFragmentPresenter.getInstance().setCheckboxUnknown();
callback.setCheckboxUnknown();
if (label.isSelected()) {
selectedLabels.remove(label);
} else {
@ -86,7 +90,7 @@ public class NearbyFilterSearchRecyclerViewAdapter
}
label.setSelected(!label.isSelected());
holder.placeTypeLayout.setBackgroundColor(label.isSelected() ? ContextCompat.getColor(context, R.color.divider_grey) : Color.WHITE);
NearbyParentFragmentPresenter.getInstance().filterByMarkerType(selectedLabels, 0, false, false);
callback.filterByMarkerType(selectedLabels, 0, false, false);
});
}
@ -161,8 +165,13 @@ public class NearbyFilterSearchRecyclerViewAdapter
notifyDataSetChanged();
}
public void setRecyclerViewAdapterNeutral() {
state = CheckBoxTriStates.UNKNOWN;
public interface Callback{
void setCheckboxUnknown();
void filterByMarkerType(ArrayList<Label> selectedLabels, int i, boolean b, boolean b1);
boolean isDarkTheme();
}
}

View file

@ -34,11 +34,9 @@ import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao;
import fr.free.nrw.commons.contributions.ContributionController;
import fr.free.nrw.commons.di.ApplicationlessInjection;
import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.nearby.fragments.NearbyMapFragment;
import fr.free.nrw.commons.nearby.fragments.NearbyParentFragment;
import timber.log.Timber;
import static fr.free.nrw.commons.nearby.fragments.NearbyParentFragment.TAG_RETAINED_MAP_FRAGMENT;
import static fr.free.nrw.commons.theme.NavigationBaseActivity.startActivityWithFlags;
import static fr.free.nrw.commons.wikidata.WikidataConstants.PLACE_OBJECT;
@ -121,7 +119,7 @@ public class PlaceRenderer extends Renderer<Place> {
}
}
if (onBookmarkClick == null) {
((NearbyParentFragment) fragment.getParentFragment()).centerMapToPlace(place);
((NearbyParentFragment) fragment).centerMapToPlace(place);
}
};
view.setOnClickListener(listener);
@ -194,8 +192,7 @@ public class PlaceRenderer extends Renderer<Place> {
onBookmarkClick.onClick();
}
else {
((NearbyMapFragment)(fragment.getParentFragment()).getChildFragmentManager().
findFragmentByTag(TAG_RETAINED_MAP_FRAGMENT)).
((NearbyParentFragment) (fragment.getParentFragment())).
updateMarker(isBookmarked, place, null);
}
}

View file

@ -1,36 +0,0 @@
package fr.free.nrw.commons.nearby.contract;
import com.mapbox.mapboxsdk.annotations.Marker;
import com.mapbox.mapboxsdk.maps.MapboxMap;
import java.util.List;
import fr.free.nrw.commons.location.LatLng;
import fr.free.nrw.commons.nearby.Label;
import fr.free.nrw.commons.nearby.NearbyBaseMarker;
import fr.free.nrw.commons.nearby.Place;
import fr.free.nrw.commons.nearby.presenter.NearbyParentFragmentPresenter;
/**
* This interface defines specific View and UserActions for map
* part of the nearby.
*/
public interface NearbyMapContract {
interface View{
void updateMapMarkers(LatLng latLng, List<Place> placeList, Marker selectedMarker, NearbyParentFragmentPresenter nearbyParentFragmentPresenter);
void updateMapToTrackPosition(LatLng curLatLng);
void addCurrentLocationMarker(LatLng curLatLng);
void addNearbyMarkersToMapBoxMap(List<NearbyBaseMarker> baseMarkerOptions, Marker marker, NearbyParentFragmentPresenter nearbyParentFragmentPresenter);
LatLng getCameraTarget();
MapboxMap getMapboxMap();
void viewsAreAssignedToPresenter(NearbyParentFragmentContract.ViewsAreReadyCallback viewsAreReadyCallback);
void addOnCameraMoveListener(MapboxMap.OnCameraMoveListener onCameraMoveListener);
void centerMapToPlace(Place place, boolean isPortraitMode);
void removeCurrentLocationMarker();
List<NearbyBaseMarker> getBaseMarkerOptions();
void filterMarkersByLabels(List<Label> labelList, boolean displayExists, boolean displayNeeds, boolean filterForPlaceState, boolean filterForAllNoneType);
void filterOutAllMarkers();
void displayAllMarkers();
}
}

View file

@ -1,5 +1,7 @@
package fr.free.nrw.commons.nearby.contract;
import android.content.Context;
import com.mapbox.mapboxsdk.annotations.Marker;
import com.mapbox.mapboxsdk.maps.MapboxMap;
@ -9,16 +11,16 @@ import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.location.LatLng;
import fr.free.nrw.commons.location.LocationServiceManager;
import fr.free.nrw.commons.nearby.Label;
import fr.free.nrw.commons.nearby.NearbyBaseMarker;
import fr.free.nrw.commons.nearby.Place;
import fr.free.nrw.commons.nearby.presenter.NearbyParentFragmentPresenter;
public interface NearbyParentFragmentContract {
interface View {
void registerLocationUpdates(LocationServiceManager locationServiceManager);
boolean isNetworkConnectionEstablished();
void addNetworkBroadcastReceiver();
void listOptionMenuItemClicked();
void populatePlaces(LatLng curlatLng, LatLng searchLatLng);
void populatePlaces(LatLng curlatLng);
boolean isListBottomSheetExpanded();
void checkPermissionsAndPerformAction(Runnable runnable);
void displayLoginSkippedWarning();
@ -29,7 +31,7 @@ public interface NearbyParentFragmentContract {
void hideBottomSheet();
void hideBottomDetailsSheet();
void displayBottomSheetWithInfo(Marker marker);
void addOnCameraMoveListener(MapboxMap.OnCameraMoveListener onCameraMoveListener);
void addOnCameraMoveListener();
void addSearchThisAreaButtonAction();
void setSearchThisAreaButtonVisibility(boolean isVisible);
void setProgressBarVisibility(boolean isVisible);
@ -44,6 +46,29 @@ public interface NearbyParentFragmentContract {
void setFilterState();
void disableFABRecenter();
void enableFABRecenter();
void addCurrentLocationMarker(LatLng curLatLng);
void updateMapToTrackPosition(LatLng curLatLng);
Context getContext();
void updateMapMarkers(List<NearbyBaseMarker> nearbyBaseMarkers, Marker selectedMarker);
void filterOutAllMarkers();
void displayAllMarkers();
void filterMarkersByLabels(List<Label> selectedLabels, boolean existsSelected, boolean needPhotoSelected, boolean filterForPlaceState, boolean filterForAllNoneType);
LatLng getCameraTarget();
void centerMapToPlace(Place placeToCenter);
void updateListFragment(List<Place> placeList);
LatLng getLastLocation();
com.mapbox.mapboxsdk.geometry.LatLng getLastFocusLocation();
}
interface NearbyListView {
@ -51,19 +76,21 @@ public interface NearbyParentFragmentContract {
}
interface UserActions {
void onTabSelected();
void checkForPermission();
void updateMapAndList(LocationServiceManager.LocationChangeType locationChangeType, LatLng cameraTarget);
void updateMapAndList(LocationServiceManager.LocationChangeType locationChangeType);
void lockUnlockNearby(boolean isNearbyLocked);
void attachView(View view);
void detachView();
void setActionListeners(JsonKvStore applicationKvStore);
void backButtonClicked();
MapboxMap.OnCameraMoveListener onCameraMove(MapboxMap mapboxMap);
void onCameraMove(com.mapbox.mapboxsdk.geometry.LatLng latLng);
void filterByMarkerType(List<Label> selectedLabels, int state, boolean filterForPlaceState, boolean filterForAllNoneType);
void updateMapMarkersToController(List<NearbyBaseMarker> nearbyBaseMarkers);
void searchViewGainedFocus();
void setCheckboxUnknown();
}
interface ViewsAreReadyCallback {
void nearbyFragmentsAreReady();
}
}

View file

@ -1,104 +0,0 @@
package fr.free.nrw.commons.nearby.fragments;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.pedrogomez.renderers.RVRendererAdapter;
import java.lang.reflect.Type;
import java.util.Collections;
import java.util.List;
import javax.inject.Inject;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.contributions.ContributionController;
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
import fr.free.nrw.commons.location.LatLng;
import fr.free.nrw.commons.nearby.NearbyAdapterFactory;
import fr.free.nrw.commons.nearby.NearbyController;
import fr.free.nrw.commons.nearby.Place;
import fr.free.nrw.commons.nearby.contract.NearbyParentFragmentContract;
import timber.log.Timber;
public class NearbyListFragment extends CommonsDaggerSupportFragment implements NearbyParentFragmentContract.NearbyListView {
private static final Type LIST_TYPE = new TypeToken<List<Place>>() {
}.getType();
private static final Type CUR_LAT_LNG_TYPE = new TypeToken<LatLng>() {
}.getType();
private NearbyAdapterFactory adapterFactory;
private RecyclerView recyclerView;
@Inject ContributionController controller;
@Inject Gson gson;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
}
@Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container,
Bundle savedInstanceState) {
Timber.d("NearbyListFragment created");
View view = inflater.inflate(R.layout.fragment_nearby_list, container, false);
recyclerView = view.findViewById(R.id.listView);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
adapterFactory = new NearbyAdapterFactory(this, controller);
return view;
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
// Check that this is the first time view is created,
// to avoid double list when screen orientation changed
Bundle bundle = this.getArguments();
recyclerView.setAdapter(adapterFactory.create(getPlaceListFromBundle(bundle)));
}
/**
* When user moved too much, we need to update nearby list too. This operation is made by passing
* a bundle from NearbyFragment to NearbyListFragment and NearbyMapFragment. This method extracts
* place list from bundle to a list variable.
* @param bundle Bundle passed from NearbyFragment on users significant moving
* @return List of new nearby places
*/
private List<Place> getPlaceListFromBundle(Bundle bundle) {
List<Place> placeList = Collections.emptyList();
if (bundle != null) {
String gsonPlaceList = bundle.getString("PlaceList", "[]");
placeList = gson.fromJson(gsonPlaceList, LIST_TYPE);
String gsonLatLng = bundle.getString("CurLatLng");
LatLng curLatLng = gson.fromJson(gsonLatLng, CUR_LAT_LNG_TYPE);
placeList = NearbyController.loadAttractionsFromLocationToPlaces(curLatLng, placeList);
}
return placeList;
}
@Override
public void updateListFragment(List<Place> placeList) {
Timber.d("Update list fragment");
adapterFactory.updateAdapterData(placeList, (RVRendererAdapter<Place>) recyclerView.getAdapter());
}
}

View file

@ -1,596 +0,0 @@
package fr.free.nrw.commons.nearby.fragments;
import android.content.Context;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;
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.CameraUpdateFactory;
import com.mapbox.mapboxsdk.maps.MapFragment;
import com.mapbox.mapboxsdk.maps.MapView;
import com.mapbox.mapboxsdk.maps.MapboxMap;
import com.mapbox.mapboxsdk.maps.MapboxMapOptions;
import com.mapbox.mapboxsdk.maps.OnMapReadyCallback;
import com.mapbox.mapboxsdk.utils.MapFragmentUtils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import javax.inject.Inject;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao;
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
import fr.free.nrw.commons.location.LatLng;
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.NearbyMarker;
import fr.free.nrw.commons.nearby.Place;
import fr.free.nrw.commons.nearby.contract.NearbyMapContract;
import fr.free.nrw.commons.nearby.contract.NearbyParentFragmentContract;
import fr.free.nrw.commons.nearby.presenter.NearbyParentFragmentPresenter;
import fr.free.nrw.commons.utils.LocationUtils;
import fr.free.nrw.commons.utils.UiUtils;
import timber.log.Timber;
import static fr.free.nrw.commons.utils.LengthUtils.formatDistanceBetween;
/**
* Support Fragment wrapper around a map view.
* <p>
* A Map component in an app. This fragment is the simplest way to place a map in an application.
* It's a wrapper around a view of a map to automatically handle the necessary life cycle needs.
* Being a fragment, this component can be added to an activity's layout or can dynamically be added
* using a FragmentManager.
* </p>
* <p>
* To get a reference to the MapView, use {@link #getMapAsync(OnMapReadyCallback)}}
* </p>
*
* @see #getMapAsync(OnMapReadyCallback)
*/
public class NearbyMapFragment extends CommonsDaggerSupportFragment
implements OnMapReadyCallback, NearbyMapContract.View{
@Inject
BookmarkLocationsDao bookmarkLocationDao;
private final List<OnMapReadyCallback> mapReadyCallbackList = new ArrayList<>();
private MapFragment.OnMapViewReadyCallback mapViewReadyCallback;
private MapboxMap mapboxMap;
private MapView map;
private Marker currentLocationMarker;
private Polygon currentLocationPolygon;
private List<NearbyBaseMarker> customBaseMarkerOptions;
private final double CAMERA_TARGET_SHIFT_FACTOR_PORTRAIT = 0.005;
private final double CAMERA_TARGET_SHIFT_FACTOR_LANDSCAPE = 0.004;
private static final double ZOOM_LEVEL = 14f;
/**
* Creates a MapFragment instance
*
* @param mapboxMapOptions The configuration options to be used.
* @return MapFragment created.
*/
@NonNull
public static NearbyMapFragment newInstance(@Nullable MapboxMapOptions mapboxMapOptions) {
NearbyMapFragment mapFragment = new NearbyMapFragment();
mapFragment.setArguments(MapFragmentUtils.createFragmentArgs(mapboxMapOptions));
return mapFragment;
}
/**
* Called when the context attaches to this fragment.
*
* @param context the context attaching
*/
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof MapFragment.OnMapViewReadyCallback) {
mapViewReadyCallback = (MapFragment.OnMapViewReadyCallback) context;
}
}
/**
* Called when this fragment is inflated, parses XML tag attributes.
*
* @param context The context inflating this fragment.
* @param attrs The XML tag attributes.
* @param savedInstanceState The saved instance state for the map fragment.
*/
@Override
public void onInflate(@NonNull Context context, AttributeSet attrs, Bundle savedInstanceState) {
super.onInflate(context, attrs, savedInstanceState);
setArguments(MapFragmentUtils.createFragmentArgs(MapboxMapOptions.createFromAttributes(context, attrs)));
}
/**
* Creates the fragment view hierarchy.
*
* @param inflater Inflater used to inflate content.
* @param container The parent layout for the map fragment.
* @param savedInstanceState The saved instance state for the map fragment.
* @return The view created
*/
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
Context context = inflater.getContext();
map = new MapView(context, MapFragmentUtils.resolveArgs(context, getArguments()));
return map;
}
/**
* Called when the fragment view hierarchy is created.
*
* @param view The content view of the fragment
* @param savedInstanceState THe saved instance state of the framgnt
*/
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
map.onCreate(savedInstanceState);
map.getMapAsync(this);
// notify listeners about MapView creation
if (mapViewReadyCallback != null) {
mapViewReadyCallback.onMapViewReady(map);
}
}
@Override
public void onMapReady(@NonNull MapboxMap mapboxMap) {
this.mapboxMap = mapboxMap;
for (OnMapReadyCallback onMapReadyCallback : mapReadyCallbackList) {
onMapReadyCallback.onMapReady(mapboxMap);
}
}
/**
* Called when the fragment is visible for the users.
*/
@Override
public void onStart() {
super.onStart();
map.onStart();
}
/**
* Called when the fragment is ready to be interacted with.
*/
@Override
public void onResume() {
super.onResume();
map.onResume();
}
/**
* Called when the fragment is pausing.
*/
@Override
public void onPause() {
super.onPause();
map.onPause();
}
/**
* Called when the fragment state needs to be saved.
*
* @param outState The saved state
*/
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
if (map != null) {
map.onSaveInstanceState(outState);
}
}
/**
* Called when the fragment is no longer visible for the user.
*/
@Override
public void onStop() {
super.onStop();
map.onStop();
}
/**
* Called when the fragment receives onLowMemory call from the hosting Activity.
*/
@Override
public void onLowMemory() {
super.onLowMemory();
if (map != null) {
map.onLowMemory();
}
}
/**
* Called when the fragment is view hierarchy is being destroyed.
*/
@Override
public void onDestroyView() {
super.onDestroyView();
map.onDestroy();
}
/**
* Called when the fragment is destroyed.
*/
@Override
public void onDestroy() {
super.onDestroy();
mapReadyCallbackList.clear();
}
/**
* Sets a callback object which will be triggered when the MapboxMap instance is ready to be used.
*
* @param onMapReadyCallback The callback to be invoked.
*/
public void getMapAsync(@NonNull final OnMapReadyCallback onMapReadyCallback) {
if (mapboxMap == null) {
mapReadyCallbackList.add(onMapReadyCallback);
} else {
onMapReadyCallback.onMapReady(mapboxMap);
}
}
/**
* Clears map, adds nearby markers to map
* @param latLng current location
* @param placeList all places will be displayed on the map
* @param selectedMarker clicked marker by user
* @param nearbyParentFragmentPresenter presenter
*/
@Override
public void updateMapMarkers(LatLng latLng, List<Place> placeList
, Marker selectedMarker
, NearbyParentFragmentPresenter nearbyParentFragmentPresenter) {
Timber.d("Updates map markers");
customBaseMarkerOptions = NearbyController
.loadAttractionsFromLocationToBaseMarkerOptions(latLng, // Curlatlang will be used to calculate distances
placeList,
getActivity(),
bookmarkLocationDao.getAllBookmarksLocations());
mapboxMap.clear();
addNearbyMarkersToMapBoxMap(customBaseMarkerOptions, selectedMarker, nearbyParentFragmentPresenter);
// Re-enable mapbox gestures on custom location markers load
mapboxMap.getUiSettings().setAllGesturesEnabled(true);
}
/**
* Makes map camera follow users location with animation
* @param curLatLng current location of user
*/
@Override
public void updateMapToTrackPosition(LatLng curLatLng) {
Timber.d("Updates map camera to track user position");
CameraPosition cameraPosition = new CameraPosition.Builder().target
(LocationUtils.commonsLatLngToMapBoxLatLng(curLatLng)).build();
mapboxMap.setCameraPosition(cameraPosition);
mapboxMap.animateCamera(CameraUpdateFactory
.newCameraPosition(cameraPosition), 1000);
}
/**
* Adds a marker for the user's current position. Adds a
* circle which uses the accuracy * 2, to draw a circle
* which represents the user's position with an accuracy
* of 95%.
*
* Should be called only on creation of mapboxMap, there
* is other method to update markers location with users
* move.
* @param curLatLng current location
*/
@Override
public void addCurrentLocationMarker(LatLng curLatLng) {
if (null != curLatLng) {
removeCurrentLocationMarker();
Timber.d("Adds current location marker");
Icon icon = IconFactory.getInstance(getContext())
.fromResource(R.drawable.current_location_marker);
MarkerOptions currentLocationMarkerOptions = new MarkerOptions()
.position(new com.mapbox.mapboxsdk.geometry.LatLng(curLatLng.getLatitude(),
curLatLng.getLongitude()));
currentLocationMarkerOptions.setIcon(icon); // Set custom icon
currentLocationMarker = mapboxMap.addMarker(currentLocationMarkerOptions);
List<com.mapbox.mapboxsdk.geometry.LatLng> circle = UiUtils
.createCircleArray(curLatLng.getLatitude(), curLatLng.getLongitude(),
curLatLng.getAccuracy() * 2, 100);
PolygonOptions currentLocationPolygonOptions = new PolygonOptions()
.addAll(circle)
.strokeColor(getResources().getColor(R.color.current_marker_stroke))
.fillColor(getResources().getColor(R.color.current_marker_fill));
currentLocationPolygon = mapboxMap.addPolygon(currentLocationPolygonOptions);
} else {
Timber.d("not adding current location marker..current location is null");
}
}
@Override
public void removeCurrentLocationMarker() {
if (currentLocationMarker != null) {
mapboxMap.removeMarker(currentLocationMarker);
mapboxMap.removePolygon(currentLocationPolygon);
}
}
/**
* Filters markers based on selectedLabels and chips
* @param selectedLabels label list that user clicked
* @param displayExists chip for displaying only existing places
* @param displayNeedsPhoto chip for displaying only places need photos
* @param filterForPlaceState true if we filter places for place state
* @param filterForAllNoneType true if we filter places with all none button
*/
@Override
public void filterMarkersByLabels(List<Label> selectedLabels,
boolean displayExists,
boolean displayNeedsPhoto,
boolean filterForPlaceState,
boolean filterForAllNoneType) {
if (selectedLabels.size() == 0 && filterForPlaceState) { // If nothing is selected, display all
greyOutAllMarkers();
for (MarkerPlaceGroup markerPlaceGroup : NearbyController.markerLabelList) {
if (displayExists && displayNeedsPhoto) {
// Exists and needs photo
if (markerPlaceGroup.getPlace().destroyed.trim().isEmpty() && markerPlaceGroup.getPlace().pic.trim().isEmpty()) {
updateMarker(markerPlaceGroup.getIsBookmarked(), markerPlaceGroup.getPlace(), NearbyController.currentLocation);
}
} else if (displayExists && !displayNeedsPhoto) {
// Exists and all included needs and doesn't needs photo
if (markerPlaceGroup.getPlace().destroyed.trim().isEmpty()) {
updateMarker(markerPlaceGroup.getIsBookmarked(), markerPlaceGroup.getPlace(), NearbyController.currentLocation);
}
} else if (!displayExists && displayNeedsPhoto) {
// All and only needs photo
if (markerPlaceGroup.getPlace().pic.trim().isEmpty()) {
updateMarker(markerPlaceGroup.getIsBookmarked(), markerPlaceGroup.getPlace(), NearbyController.currentLocation);
}
} else if (!displayExists && !displayNeedsPhoto) {
// all
updateMarker(markerPlaceGroup.getIsBookmarked(), markerPlaceGroup.getPlace(), NearbyController.currentLocation);
}
//updateMarker(markerPlaceGroup.getIsBookmarked(), markerPlaceGroup.getPlace(), NearbyController.currentLocation);
}
} else {
// First greyed out all markers
greyOutAllMarkers();
for (MarkerPlaceGroup markerPlaceGroup : NearbyController.markerLabelList) {
for (Label label : selectedLabels) {
if (markerPlaceGroup.getPlace().getLabel().toString().equals(label.toString())) {
if (displayExists && displayNeedsPhoto) {
// Exists and needs photo
if (markerPlaceGroup.getPlace().destroyed.trim().isEmpty() && markerPlaceGroup.getPlace().pic.trim().isEmpty()) {
updateMarker(markerPlaceGroup.getIsBookmarked(), markerPlaceGroup.getPlace(), NearbyController.currentLocation);
}
} else if (displayExists && !displayNeedsPhoto) {
// Exists and all included needs and doesn't needs photo
if (markerPlaceGroup.getPlace().destroyed.trim().isEmpty()) {
updateMarker(markerPlaceGroup.getIsBookmarked(), markerPlaceGroup.getPlace(), NearbyController.currentLocation);
}
} else if (!displayExists && displayNeedsPhoto) {
// All and only needs photo
if (markerPlaceGroup.getPlace().pic.trim().isEmpty()) {
updateMarker(markerPlaceGroup.getIsBookmarked(), markerPlaceGroup.getPlace(), NearbyController.currentLocation);
}
} else if (!displayExists && !displayNeedsPhoto) {
// all
updateMarker(markerPlaceGroup.getIsBookmarked(), markerPlaceGroup.getPlace(), NearbyController.currentLocation);
}
}
}
}
}
}
/**
* Greys out all markers
*/
@Override
public void filterOutAllMarkers() {
greyOutAllMarkers();
}
/**
* Displays all markers
*/
@Override
public void displayAllMarkers() {
for (MarkerPlaceGroup markerPlaceGroup : NearbyController.markerLabelList) {
updateMarker(markerPlaceGroup.getIsBookmarked(), markerPlaceGroup.getPlace(), NearbyController.currentLocation);
}
}
@Override
public List<NearbyBaseMarker> getBaseMarkerOptions() {
return customBaseMarkerOptions;
}
/**
* Adds markers to map
* @param baseMarkerList is markers will be added
* @param selectedMarker is selected marker by user
* @param nearbyParentFragmentPresenter presenter
*/
@Override
public void addNearbyMarkersToMapBoxMap(@Nullable List<NearbyBaseMarker> baseMarkerList
, Marker selectedMarker
, NearbyParentFragmentPresenter nearbyParentFragmentPresenter) {
List<Marker> markers = mapboxMap.addMarkers(baseMarkerList);
NearbyController.markerExistsMap = new HashMap<Boolean, Marker>();
NearbyController.markerNeedPicMap = new HashMap<Boolean, Marker>();
NearbyController.markerLabelList.clear();
for (int i = 0; i < baseMarkerList.size(); i++) {
NearbyBaseMarker nearbyBaseMarker = baseMarkerList.get(i);
NearbyController.markerLabelList.add(
new MarkerPlaceGroup(markers.get(i), bookmarkLocationDao.findBookmarkLocation(baseMarkerList.get(i).getPlace()), nearbyBaseMarker.getPlace()));
//TODO: fix bookmark location
NearbyController.markerExistsMap.put((baseMarkerList.get(i).getPlace().hasWikidataLink()), markers.get(i));
NearbyController.markerNeedPicMap.put(((baseMarkerList.get(i).getPlace().pic == null) ? true : false), markers.get(i));
}
map.getMapAsync(mapboxMap -> {
mapboxMap.addMarkers(baseMarkerList);
setMapMarkerActions(selectedMarker, nearbyParentFragmentPresenter);
});
}
@Override
public LatLng getCameraTarget() {
return LocationUtils
.mapBoxLatLngToCommonsLatLng(mapboxMap.getCameraPosition().target);
}
@Override
public MapboxMap getMapboxMap() {
return mapboxMap;
}
@Override
public void viewsAreAssignedToPresenter(NearbyParentFragmentContract.ViewsAreReadyCallback viewsAreReadyCallback) {
}
@Override
public void addOnCameraMoveListener(MapboxMap.OnCameraMoveListener onCameraMoveListener) {
}
void setMapMarkerActions(Marker selected, NearbyParentFragmentPresenter nearbyParentFragmentPresenter) {
getMapboxMap().setOnInfoWindowCloseListener(marker -> {
if (marker == selected) {
nearbyParentFragmentPresenter.markerUnselected();
}
});
getMapboxMap().setOnMarkerClickListener(marker -> {
if (marker instanceof NearbyMarker) {
nearbyParentFragmentPresenter.markerSelected(marker);
}
return false;
});
}
/**
* Sets marker icon according to marker status. Sets title and distance.
* @param isBookmarked true if place is bookmarked
* @param place
* @param curLatLng current location
*/
public void updateMarker(boolean isBookmarked, Place place, @Nullable LatLng curLatLng) {
VectorDrawableCompat vectorDrawable;
if (isBookmarked) {
vectorDrawable = VectorDrawableCompat.create(
getContext().getResources(), R.drawable.ic_custom_bookmark_marker, getContext().getTheme()
);
} else if (!place.pic.trim().isEmpty()) {
vectorDrawable = VectorDrawableCompat.create( // Means place has picture
getContext().getResources(), R.drawable.ic_custom_map_marker_green, getContext().getTheme()
);
} else if (!place.destroyed.trim().isEmpty()) { // Means place is destroyed
vectorDrawable = VectorDrawableCompat.create( // Means place has picture
getContext().getResources(), R.drawable.ic_custom_map_marker_grey, getContext().getTheme()
);
} else {
vectorDrawable = VectorDrawableCompat.create(
getContext().getResources(), R.drawable.ic_custom_map_marker, getContext().getTheme()
);
}
for (Marker marker : mapboxMap.getMarkers()) {
if (marker.getTitle() != null && marker.getTitle().equals(place.getName())) {
Bitmap icon = UiUtils.getBitmap(vectorDrawable);
if (curLatLng != null) {
String distance = formatDistanceBetween(curLatLng, place.location);
place.setDistance(distance);
}
NearbyBaseMarker nearbyBaseMarker = new NearbyBaseMarker();
nearbyBaseMarker.title(place.name);
nearbyBaseMarker.position(
new com.mapbox.mapboxsdk.geometry.LatLng(
place.location.getLatitude(),
place.location.getLongitude()));
nearbyBaseMarker.place(place);
nearbyBaseMarker.icon(IconFactory.getInstance(getContext())
.fromBitmap(icon));
marker.setIcon(IconFactory.getInstance(getContext()).fromBitmap(icon));
}
}
}
/**
* Greys out all markers except current location marker
*/
public void greyOutAllMarkers() {
VectorDrawableCompat vectorDrawable;
vectorDrawable = VectorDrawableCompat.create(
getContext().getResources(), R.drawable.ic_custom_greyed_out_marker, getContext().getTheme());
Bitmap icon = UiUtils.getBitmap(vectorDrawable);
for (Marker marker : mapboxMap.getMarkers()) {
if (currentLocationMarker.getTitle() != marker.getTitle()) {
marker.setIcon(IconFactory.getInstance(getContext()).fromBitmap(icon));
}
}
addCurrentLocationMarker(NearbyController.currentLocation);
}
/**
* Centers the map in nearby fragment to a given place
* @param place is new center of the map
*/
@Override
public void centerMapToPlace(Place place, boolean isPortraitMode) {
Timber.d("Map is centered to place");
double cameraShift;
if (isPortraitMode) {
cameraShift = CAMERA_TARGET_SHIFT_FACTOR_PORTRAIT;
} else {
cameraShift = CAMERA_TARGET_SHIFT_FACTOR_LANDSCAPE;
}
CameraPosition position = new CameraPosition.Builder()
.target(LocationUtils.commonsLatLngToMapBoxLatLng(
new LatLng(place.location.getLatitude()-cameraShift,
place.getLocation().getLongitude(),
0))) // Sets the new camera position
.zoom(ZOOM_LEVEL) // Same zoom level
.build();
mapboxMap.animateCamera(CameraUpdateFactory.newCameraPosition(position), 1000);
}
}

View file

@ -3,21 +3,27 @@ package fr.free.nrw.commons.nearby.presenter;
import android.view.View;
import com.mapbox.mapboxsdk.annotations.Marker;
import com.mapbox.mapboxsdk.maps.MapboxMap;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.List;
import javax.inject.Inject;
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao;
import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.location.LatLng;
import fr.free.nrw.commons.location.LocationServiceManager;
import fr.free.nrw.commons.location.LocationUpdateListener;
import fr.free.nrw.commons.nearby.CheckBoxTriStates;
import fr.free.nrw.commons.nearby.Label;
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.NearbyFilterState;
import fr.free.nrw.commons.nearby.Place;
import fr.free.nrw.commons.nearby.contract.NearbyMapContract;
import fr.free.nrw.commons.nearby.contract.NearbyParentFragmentContract;
import fr.free.nrw.commons.upload.UploadContract;
import fr.free.nrw.commons.utils.LocationUtils;
import fr.free.nrw.commons.wikidata.WikidataEditListener;
import timber.log.Timber;
@ -32,118 +38,58 @@ import static fr.free.nrw.commons.nearby.CheckBoxTriStates.UNKNOWN;
public class NearbyParentFragmentPresenter
implements NearbyParentFragmentContract.UserActions,
WikidataEditListener.WikidataP18EditListener,
LocationUpdateListener,
NearbyParentFragmentContract.ViewsAreReadyCallback{
WikidataEditListener.WikidataP18EditListener,
LocationUpdateListener {
private NearbyParentFragmentContract.View nearbyParentFragmentView;
private NearbyMapContract.View nearbyMapFragmentView;
private NearbyParentFragmentContract.NearbyListView nearbyListFragmentView;
private boolean isNearbyLocked;
private LatLng curLatLng;
private boolean nearbyViewsAreReady;
private boolean onTabSelected;
private boolean mapInitialized;
private Place placeToCenter;
private boolean isPortraitMode;
private boolean placesLoadedOnce;
private LocationServiceManager locationServiceManager;
BookmarkLocationsDao bookmarkLocationDao;
public static NearbyParentFragmentPresenter presenterInstance;
private static final NearbyParentFragmentContract.View DUMMY = (NearbyParentFragmentContract.View) Proxy.newProxyInstance(
NearbyParentFragmentContract.View.class.getClassLoader(),
new Class[]{NearbyParentFragmentContract.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 NearbyParentFragmentContract.View nearbyParentFragmentView = DUMMY;
private NearbyParentFragmentPresenter(NearbyParentFragmentContract.NearbyListView nearbyListFragmentView,
NearbyParentFragmentContract.View nearbyParentFragmentView,
NearbyMapContract.View nearbyMapFragmentView,
LocationServiceManager locationServiceManager) {
this.nearbyListFragmentView = nearbyListFragmentView;
this.nearbyParentFragmentView = nearbyParentFragmentView;
this.nearbyMapFragmentView = nearbyMapFragmentView;
this.nearbyMapFragmentView.viewsAreAssignedToPresenter(this);
this.locationServiceManager = locationServiceManager;
public NearbyParentFragmentPresenter(BookmarkLocationsDao bookmarkLocationDao){
this.bookmarkLocationDao=bookmarkLocationDao;
}
// static method to create instance of Singleton class
public static NearbyParentFragmentPresenter getInstance(NearbyParentFragmentContract.NearbyListView nearbyListFragmentView,
NearbyParentFragmentContract.View nearbyParentFragmentView,
NearbyMapContract.View nearbyMapFragmentView,
LocationServiceManager locationServiceManager) {
if (presenterInstance == null) {
presenterInstance = new NearbyParentFragmentPresenter(nearbyListFragmentView,
nearbyParentFragmentView,
nearbyMapFragmentView,
locationServiceManager);
}
return presenterInstance;
}
// We call this method when we are sure presenterInstance is not null
public static NearbyParentFragmentPresenter getInstance() {
return presenterInstance;
}
/**
* Note: To initialize nearby operations both views should be ready and tab is selected.
* Initializes nearby operations if nearby views are ready
*/
@Override
public void onTabSelected() {
Timber.d("Nearby tab selected");
onTabSelected = true;
// The condition for initialize operations is both having views ready and tab is selected
if (nearbyViewsAreReady && !mapInitialized) {
checkForPermission();
}
public void attachView(NearbyParentFragmentContract.View view){
this.nearbyParentFragmentView=view;
}
/**
* -To initialize nearby operations both views should be ready and tab is selected.
* Initializes nearby operations if tab selected, otherwise just sets nearby views are ready
*/
@Override
public void nearbyFragmentsAreReady() {
Timber.d("Nearby fragments are ready to be used by presenter");
nearbyViewsAreReady = true;
// The condition for initialize operations is both having views ready and tab is selected
if (onTabSelected) {
checkForPermission();
}
}
/**
* Initializes nearby operations by following these steps:
* -Checks for permission and perform if given
*/
@Override
public void checkForPermission() {
Timber.d("checking for permission");
nearbyParentFragmentView.checkPermissionsAndPerformAction(this::performNearbyOperationsIfPermissionGiven);
}
/**
* - Adds search this area button action
* - Adds camera move action listener
* - Initializes nearby operations, registers listeners, broadcast receivers etc.
*/
public void performNearbyOperationsIfPermissionGiven() {
Timber.d("Permission is given, performing actions");
this.nearbyParentFragmentView.addSearchThisAreaButtonAction();
this.nearbyParentFragmentView.addOnCameraMoveListener(onCameraMove(getMapboxMap()));
initializeMapOperations();
public void detachView(){
this.nearbyParentFragmentView=DUMMY;
}
public void initializeMapOperations() {
lockUnlockNearby(false);
registerUnregisterLocationListener(false);
nearbyParentFragmentView.addNetworkBroadcastReceiver();
updateMapAndList(LOCATION_SIGNIFICANTLY_CHANGED, null);
updateMapAndList(LOCATION_SIGNIFICANTLY_CHANGED);
this.nearbyParentFragmentView.addSearchThisAreaButtonAction();
this.nearbyMapFragmentView.addOnCameraMoveListener(onCameraMove(getMapboxMap()));
nearbyParentFragmentView.setCheckBoxAction();
mapInitialized = true;
}
/**
@ -204,34 +150,13 @@ public class NearbyParentFragmentPresenter
}
}
public void registerUnregisterLocationListener(boolean removeLocationListener) {
if (removeLocationListener) {
locationServiceManager.unregisterLocationManager();
locationServiceManager.removeLocationListener(this);
Timber.d("Location service manager unregistered and removed");
} else {
locationServiceManager.addLocationListener(this);
nearbyParentFragmentView.registerLocationUpdates(locationServiceManager);
Timber.d("Location service manager added and registered");
}
}
public LatLng getCameraTarget() {
return nearbyMapFragmentView.getCameraTarget();
}
public MapboxMap getMapboxMap() {
return nearbyMapFragmentView.getMapboxMap();
}
/**
* This method should be the single point to update Map and List. Triggered by location
* changes
* @param locationChangeType defines if location changed significantly or slightly
* @param cameraTarget will be used for search this area mode, when searching around
* user's camera target
*/
@Override
public void updateMapAndList(LocationServiceManager.LocationChangeType locationChangeType, LatLng cameraTarget) {
public void updateMapAndList(LocationServiceManager.LocationChangeType locationChangeType) {
Timber.d("Presenter updates map and list");
if (isNearbyLocked) {
Timber.d("Nearby is locked, so updateMapAndList returns");
@ -243,7 +168,7 @@ public class NearbyParentFragmentPresenter
return;
}
LatLng lastLocation = locationServiceManager.getLastLocation();
LatLng lastLocation = nearbyParentFragmentView.getLastLocation();
curLatLng = lastLocation;
if (curLatLng == null) {
@ -260,14 +185,13 @@ public class NearbyParentFragmentPresenter
Timber.d("LOCATION_SIGNIFICANTLY_CHANGED");
lockUnlockNearby(true);
nearbyParentFragmentView.setProgressBarVisibility(true);
nearbyParentFragmentView.populatePlaces(lastLocation, lastLocation);
nearbyParentFragmentView.populatePlaces(lastLocation);
} else if (locationChangeType.equals(SEARCH_CUSTOM_AREA)) {
Timber.d("SEARCH_CUSTOM_AREA");
lockUnlockNearby(true);
nearbyParentFragmentView.setProgressBarVisibility(true);
nearbyParentFragmentView.populatePlaces(lastLocation, cameraTarget);
nearbyParentFragmentView.populatePlaces(nearbyParentFragmentView.getCameraTarget());
} else { // Means location changed slightly, ie user is walking or driving.
Timber.d("Means location changed slightly");
if (!nearbyParentFragmentView.isSearchThisAreaButtonVisible()) { // Do not track users position if the user is checking around
@ -281,28 +205,23 @@ public class NearbyParentFragmentPresenter
* location where you are not at.
* @param nearbyPlacesInfo This variable has placeToCenter list information and distances.
*/
public void updateMapMarkers(NearbyController.NearbyPlacesInfo nearbyPlacesInfo, Marker selectedMarker) {
nearbyMapFragmentView.updateMapMarkers(nearbyPlacesInfo.curLatLng, nearbyPlacesInfo.placeList, selectedMarker, this);
nearbyMapFragmentView.addCurrentLocationMarker(nearbyPlacesInfo.curLatLng);
nearbyMapFragmentView.updateMapToTrackPosition(nearbyPlacesInfo.curLatLng);
lockUnlockNearby(false); // So that new location updates wont come
nearbyParentFragmentView.setProgressBarVisibility(false);
nearbyListFragmentView.updateListFragment(nearbyPlacesInfo.placeList);
handleCenteringTaskIfAny();
}
/**
* Populates places for custom location, should be used for finding nearby places around a
* location where you are not at.
* @param nearbyPlacesInfo This variable has placeToCenter list information and distances.
*/
public void updateMapMarkersForCustomLocation(NearbyController.NearbyPlacesInfo nearbyPlacesInfo, Marker selectedMarker) {
nearbyMapFragmentView.updateMapMarkers(nearbyPlacesInfo.curLatLng, nearbyPlacesInfo.placeList, selectedMarker, this);
nearbyMapFragmentView.addCurrentLocationMarker(nearbyPlacesInfo.curLatLng);
lockUnlockNearby(false); // So that new location updates wont come
nearbyParentFragmentView.setProgressBarVisibility(false);
nearbyListFragmentView.updateListFragment(nearbyPlacesInfo.placeList);
handleCenteringTaskIfAny();
public void updateMapMarkers(NearbyController.NearbyPlacesInfo nearbyPlacesInfo, Marker selectedMarker, boolean shouldTrackPosition) {
if(null!=nearbyParentFragmentView) {
List<NearbyBaseMarker> nearbyBaseMarkers = NearbyController
.loadAttractionsFromLocationToBaseMarkerOptions(nearbyPlacesInfo.curLatLng, // Curlatlang will be used to calculate distances
nearbyPlacesInfo.placeList,
nearbyParentFragmentView.getContext(),
bookmarkLocationDao.getAllBookmarksLocations());
nearbyParentFragmentView.updateMapMarkers(nearbyBaseMarkers, selectedMarker);
nearbyParentFragmentView.addCurrentLocationMarker(nearbyPlacesInfo.curLatLng);
if(shouldTrackPosition){
nearbyParentFragmentView.updateMapToTrackPosition(nearbyPlacesInfo.curLatLng);
}
lockUnlockNearby(false); // So that new location updates wont come
nearbyParentFragmentView.setProgressBarVisibility(false);
nearbyParentFragmentView.updateListFragment(nearbyPlacesInfo.placeList);
handleCenteringTaskIfAny();
}
}
/**
@ -312,25 +231,25 @@ public class NearbyParentFragmentPresenter
private void handleCenteringTaskIfAny() {
if (!placesLoadedOnce) {
placesLoadedOnce = true;
nearbyMapFragmentView.centerMapToPlace(placeToCenter, isPortraitMode);
nearbyParentFragmentView.centerMapToPlace(null);
}
}
@Override
public void onWikidataEditSuccessful() {
updateMapAndList(MAP_UPDATED, null);
updateMapAndList(MAP_UPDATED);
}
@Override
public void onLocationChangedSignificantly(LatLng latLng) {
Timber.d("Location significantly changed");
updateMapAndList(LOCATION_SIGNIFICANTLY_CHANGED, null);
updateMapAndList(LOCATION_SIGNIFICANTLY_CHANGED);
}
@Override
public void onLocationChangedSlightly(LatLng latLng) {
Timber.d("Location significantly changed");
updateMapAndList(LOCATION_SLIGHTLY_CHANGED, null);
updateMapAndList(LOCATION_SLIGHTLY_CHANGED);
}
@Override
@ -339,11 +258,10 @@ public class NearbyParentFragmentPresenter
}
@Override
public MapboxMap.OnCameraMoveListener onCameraMove(MapboxMap mapboxMap) {
return () -> {
public void onCameraMove(com.mapbox.mapboxsdk.geometry.LatLng latLng) {
// If our nearby markers are calculated at least once
if (NearbyController.currentLocation != null) {
double distance = mapboxMap.getCameraPosition().target.distanceTo
if (NearbyController.latestSearchLocation != null) {
double distance =latLng.distanceTo
(LocationUtils.commonsLatLngToMapBoxLatLng(NearbyController.latestSearchLocation));
if (nearbyParentFragmentView.isNetworkConnectionEstablished()) {
if (distance > NearbyController.latestSearchRadius) {
@ -355,7 +273,6 @@ public class NearbyParentFragmentPresenter
} else {
nearbyParentFragmentView.setSearchThisAreaButtonVisibility(false);
}
};
}
@Override
@ -366,19 +283,34 @@ public class NearbyParentFragmentPresenter
// Do nothing
break;
case UNCHECKED:
nearbyMapFragmentView.filterOutAllMarkers();
nearbyParentFragmentView.filterOutAllMarkers();
nearbyParentFragmentView.setRecyclerViewAdapterItemsGreyedOut();
break;
case CHECKED:
nearbyMapFragmentView.displayAllMarkers();
nearbyParentFragmentView.displayAllMarkers();
nearbyParentFragmentView.setRecyclerViewAdapterAllSelected();
break;
}
} else {
nearbyMapFragmentView.filterMarkersByLabels(selectedLabels,
nearbyParentFragmentView.filterMarkersByLabels(selectedLabels,
NearbyFilterState.getInstance().isExistsSelected(),
NearbyFilterState.getInstance().isNeedPhotoSelected(),
filterForPlaceState, filterForAllNoneType);
filterForPlaceState, false);
}
}
@Override
public void updateMapMarkersToController(List<NearbyBaseMarker> nearbyBaseMarkers) {
NearbyController.markerExistsMap = new HashMap<>();
NearbyController.markerNeedPicMap = new HashMap<>();
NearbyController.markerLabelList.clear();
for (int i = 0; i < nearbyBaseMarkers.size(); i++) {
NearbyBaseMarker nearbyBaseMarker = nearbyBaseMarkers.get(i);
NearbyController.markerLabelList.add(
new MarkerPlaceGroup(nearbyBaseMarkers.get(i).getMarker(), bookmarkLocationDao.findBookmarkLocation(nearbyBaseMarkers.get(i).getPlace()), nearbyBaseMarker.getPlace()));
//TODO: fix bookmark location
NearbyController.markerExistsMap.put((nearbyBaseMarkers.get(i).getPlace().hasWikidataLink()), nearbyBaseMarkers.get(i).getMarker());
NearbyController.markerNeedPicMap.put(((nearbyBaseMarkers.get(i).getPlace().pic == null) ? true : false), nearbyBaseMarkers.get(i).getMarker());
}
}
@ -403,11 +335,9 @@ public class NearbyParentFragmentPresenter
nearbyParentFragmentView.setSearchThisAreaButtonVisibility(false);
if (searchCloseToCurrentLocation()){
updateMapAndList(LOCATION_SIGNIFICANTLY_CHANGED,
null);
updateMapAndList(LOCATION_SIGNIFICANTLY_CHANGED);
} else {
updateMapAndList(SEARCH_CUSTOM_AREA,
getCameraTarget());
updateMapAndList(SEARCH_CUSTOM_AREA);
}
};
}
@ -418,9 +348,11 @@ public class NearbyParentFragmentPresenter
* @return Returns true if search this area button is used around our current location
*/
public boolean searchCloseToCurrentLocation() {
double distance = LocationUtils.commonsLatLngToMapBoxLatLng(getCameraTarget())
.distanceTo(new com.mapbox.mapboxsdk.geometry.LatLng(NearbyController.currentLocation.getLatitude()
, NearbyController.currentLocation.getLongitude()));
if (null == nearbyParentFragmentView.getLastFocusLocation()) {
return true;
}
double distance = LocationUtils.commonsLatLngToMapBoxLatLng(nearbyParentFragmentView.getCameraTarget())
.distanceTo(nearbyParentFragmentView.getLastFocusLocation());
if (distance > NearbyController.currentLocationSearchRadius * 3 / 4) {
return false;
} else {
@ -428,16 +360,20 @@ public class NearbyParentFragmentPresenter
}
}
/**
* Centers the map in nearby fragment to a given placeToCenter
* @param place is new center of the map
*/
public void centerMapToPlace(Place place, boolean isPortraitMode) {
if (placesLoadedOnce) {
nearbyMapFragmentView.centerMapToPlace(place, isPortraitMode);
public void onMapReady() {
if(null!=nearbyParentFragmentView) {
nearbyParentFragmentView.addSearchThisAreaButtonAction();
initializeMapOperations();
}
}
public boolean areLocationsClose(LatLng cameraTarget, LatLng lastKnownLocation) {
double distance = LocationUtils.commonsLatLngToMapBoxLatLng(cameraTarget)
.distanceTo(LocationUtils.commonsLatLngToMapBoxLatLng(lastKnownLocation));
if (distance > NearbyController.currentLocationSearchRadius * 3 / 4) {
return false;
} else {
this.isPortraitMode = isPortraitMode;
this.placeToCenter = place;
return true;
}
}
}

View file

@ -4,6 +4,8 @@ import android.os.Handler;
import android.os.Looper;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ExecutorUtils {
@ -19,4 +21,11 @@ public class ExecutorUtils {
return uiExecutor;
}
private static final ExecutorService executor = Executors.newFixedThreadPool(3);
public static ExecutorService get() {
return executor;
}
}

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.navigation.NavigationView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/navigation_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:elevation="2dp"
app:headerLayout="@layout/drawer_header"
app:menu="@menu/drawer" />

View file

@ -8,7 +8,7 @@
app:layout_behavior="@string/bottom_sheet_behavior"
app:behavior_peekHeight="72dp"
app:behavior_hideable="true"
android:visibility="gone"
android:visibility="visible"
>

View file

@ -1,51 +1,16 @@
<?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="480dp"
android:id="@+id/bottom_sheet"
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/bottom_sheet"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="?attr/mainBackground"
app:layout_behavior="@string/bottom_sheet_behavior"
app:behavior_peekHeight="0dp"
app:behavior_hideable="true"
>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/loading_nearby_list"
android:visibility="visible"
app:layout_behavior="@string/bottom_sheet_behavior"
android:background="@android:color/white">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_nearby_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="visible">
<ProgressBar
android:id="@+id/progress_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:layout_marginTop="10dp"
android:textSize="20sp"
app:layout_constraintTop_toBottomOf="@+id/progress_bar"
android:text="Loading..."
/>
</androidx.constraintlayout.widget.ConstraintLayout>
<FrameLayout
android:id="@+id/container_sheet"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:focusableInTouchMode="true"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginBottom="8dp"
android:layout_marginTop="8dp"
android:visibility="gone" />
</LinearLayout>
android:layout_height="match_parent" />
</RelativeLayout>

View file

@ -5,5 +5,6 @@
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:elevation="2dp"
app:headerLayout="@layout/drawer_header"
app:menu="@menu/drawer" />

View file

@ -2,6 +2,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:visibility="gone"
>
<androidx.recyclerview.widget.RecyclerView

View file

@ -10,11 +10,6 @@
android:layout_height="match_parent"
android:background="@color/status_bar_blue"
android:id="@+id/map_layout">
<View
android:layout_width="0dp"
android:layout_height="0dp"
android:focusableInTouchMode="true"/>
<include
layout="@layout/nearby_filter_all_items"
@ -27,11 +22,25 @@
android:layout_width="160dp"
android:layout_alignParentEnd="true"/>
<!-- 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 -->
<FrameLayout
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/nearby_filter"/>
android:layout_below="@id/nearby_filter">
<com.mapbox.mapboxsdk.maps.MapView
android:id="@+id/map_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<View
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/transparent" />
</FrameLayout>
<Button
@ -54,7 +63,6 @@
android:id="@+id/transparentView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/toolbar"
android:layout_alignParentLeft="true"
android:background="#aa969696"
android:visibility="gone"

View file

@ -1,21 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/search_list_item"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="25dp"
>
android:orientation="horizontal"
android:padding="4dp">
<ImageView
android:id="@+id/place_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</ImageView>
android:layout_height="wrap_content" />
<TextView
android:id="@+id/place_text"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</TextView>
android:layout_height="wrap_content"></TextView>
</LinearLayout>

View file

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/search_list_item"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="4dp"
android:orientation="horizontal">
<ImageView
android:id="@+id/place_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/place_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/white"></TextView>
</LinearLayout>

View file

@ -277,7 +277,7 @@
<string name="wikicode_copied">The wikitext was copied to the clipboard</string>
<string name="nearby_location_has_not_changed">Location has not changed.</string>
<string name="nearby_location_not_available">Location not available.</string>
<string name="nearby_location_not_available">Nearby might not work properly, Location not available.</string>
<string name="location_permission_rationale_nearby">Permission required to display a list of nearby places</string>
<string name="get_directions">Get directions</string>
<string name="read_article">Read article</string>