mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-26 20:33:53 +01:00
Refactor nearby classes mvp (#2969)
* Create parent contract * Create map child contract and fill methods * Add javadocs and specific interfaces for list * Move general method to parent and add javadocs for parent * Add explanation for keeping an emty View interface under NearbyListContract * Move constracts under contract package * Create presenters for map and list and implement user actions accordingly * Add javadocs * Add presenter, contract and fragment for parent Fragment of both NearyListFragment and NearbyMapFragment * Implement missing methods * Fix typo * Add main views on fragment * İmplement child fragment logic and their retain * Relate parent presenter with parent fragment * Add all location permission related methods to view contract and implement in fragment. Call them from presenter by passing locationServiceManager parameter * Define refreshView method as updateMapAndList which is a better naming. Define it at presenter part. * Define a presenter variable in fragment and call updateMapAndList method from there, if permissions are okay * Add lock neabry method to unlisten nearby operations during updates * Add network connection established check on view side, check it from presenter * try to simplify previous method during refactor * Add missing methods for NearbyMap * Connect child fragment and prent fragment with presenter * Change nearby design, first create views then register listeners * AddnetworkBroadcatsReceiver on view side, call it from presenter * Add comments * Change the old NearbyFragment by our new NearbyParentFragment for first tests * Prevent crash caused child fragment is actually null by checking if it is attached or not. * Makes sure that initialize nearby operations method is called just after all views and fragments are ready and attached * Make sure updateMAoAndList method is called when everything ready * Rename a method with prepoer name * Call update map and add required markers * Find out zoom level problem * Implement add nearby markers and add current marker methods * remove unneeded codes copied from previous implementation * Revert "remove unneeded codes copied from previous implementation" This reverts commit42539651de. * add some commits and clear code * Remove location listener implementation from view, handle them in presenter instead. And add timber debug notes and required method calls * Style ,issues * Refactor a variable name to camel case and bind search this area view * Search this area button action is added * Mostly implement search this area methods, not tested yet even once * Make sure everything is called in required order and seach this area method basically works * Rename methods accordingly and add Javadocs * Add current location marker and remove circle around it * Remove unused methods * Add current location marker with object animator and remove previous marker is exists * include clear map into add markers method and reorder methods * Make search this area button appear at correct time * Try to load un search this area is called * Clear logs * Add changes for permission made by Vivek to newly added fragments along with our nearby classes * Add a view to nearby map ragment and insert map view as an item inside it * Add logs to uınderstand why on meap ready callback is never called * Add list item clicked and bottom sheet for list of nearby items is expanded * Make nearby map ready callback came * Add required methods to be called after map view is ready * State: Map ready call is not called but permissions and methods are in correct order * Remove unused logs * Try to use SupportMapFragment instead, still no success... * use SupportMapFragment instead * Remove unused Near * Upgrade mapbox sdk versions * Remove Style import from fragment/NearbyMapFragment * Remove Style import from **fragments/NearbyParentFragment * Remove nearby/NearbyMapFragment * Remove unused/old NearbyFragment and NearbyMapFragment * Remove import of already removed class * Make sure you removed everything related with mapbox implementation * Update mapbox map * Remove unused classes, do not forget centerMapToPlace doesn't work on this branch * Remove Style class it required updated version of mapbox map * Add base codes for new activity * Prove that our mapbox sdk let us implement the map directly inside the activity * Add base codes for testing mapbox activity in a single fragment inside activity * Add codes from mapbox demo repository to test support fragment, map works in a single layer fragment * Add base codes for test layered fragment activity * Add Support fragment inside a fragment and proves that layered fragmentw with map works * Test view pager and tab layout with support map fragment to see it works, it works! * Move Contributions Fragment related codes to test activity * Move nearby card methods * Inject location manager and implement NerabyParentFragmentContract.View on test fragment * Coppy the content of SupportMapFragment from mapbox repository, use this code to modify later. This method war suggested by mapbox team instead of extending the class * Implement NearbyMapContract.View on our new SupportMapFragment * Start to mplement logic of checking permissions * Fix small dagger issue to inject location manager properly * Request permission for nearby places if fragment is loaded and tab is selected * Initialize map operations if map ready and tab is selected * Markers loads at correct time * Style the map according to new version of mapbox map * Add some map elements like FABs and give their actions * Implement map marker click actions * Implement nearby Fabs logic with a small issue * fix FABs are not closing problem * Unkown problem occurs at map load when I try to use MainActivity again. * Revert "Unkown problem occurs at map load when I try to use MainActivity again." This reverts commit3dc084415b. * Search this area buttons are added but button function does not work and button visibility is problematic * Fix issue with MainActivity with the help of Ashish * Fix search this area button visibility issue * Fix the issues with updating search nearby markers and camera position together * Fix progress bar visibility issue * Prevent loding map each time tab selected * Take toolbar back * Implement back button with presenter * Add click actoion to bottom sheet details * Add nearby list into bottom sheeet * Make reuse existing fragments if there is any * Code cleanup * Cleanup * Code cleanup * Add lifecyle codes to prevent leaks * Cleanup * Code cleanup * cleanup * Make list item clicked and map focus to same place * Add bookmark from list fragment * Make nearby card click action work * Revert "Fix conflicts" This reverts commitf3451745d3, reversing changes made toc5d4d5533d. * Code cleanup * Cleanup * Make recenter button work when list sheet is expanded * Cleanup * Code cleanups * NPE issue is not detected for now, can be solved on seperate PR * Update map after Wikidata edit * Cherry picked previously reverted merge, hoping this will fix enourmus amounts of diff files * Previous merge bringed an file which should be deleted. Delete it. * Revert irrelevant changes on build.gradle * Revert irrelevant changes * Jetbrains annotation is not included to build gradle, this issue caused build issues on my branch so I removed it to be able to build * Rename ListView * Use a singleton to access the presenter, instead of passing it to a variable inside fragment * Fix code style issues * Move hardcoded colors to colors.xml file * Make larger methods smaller * Make current location marker follow * Do not track users position if user is searching around * Revert irrelevant shanges at build gradle * Remove unneeded variable * Remove mvp directory, add sub directories directly under nearby directory instead * Remove unneeded public definiton * Remove unneeded namespace * Add public defiiton, it is needed to reach the constructor.
This commit is contained in:
parent
8a2931270c
commit
958bbbdd81
24 changed files with 1995 additions and 1938 deletions
|
|
@ -36,7 +36,8 @@ 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-plugin-localization:0.6.0'
|
||||
implementation 'com.mapbox.mapboxsdk:mapbox-android-sdk:7.2.0'
|
||||
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'
|
||||
implementation 'com.karumi:dexter:5.0.0'
|
||||
|
|
@ -162,7 +163,7 @@ android {
|
|||
versionNameSuffix "-debug-" + getBranchName()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (isRunningOnTravisAndIsNotPRBuild) {
|
||||
// configure keystore based on env vars in Travis for automated alpha builds
|
||||
signingConfigs.release.storeFile = file("../nr-commons.keystore")
|
||||
|
|
|
|||
|
|
@ -34,8 +34,9 @@ import fr.free.nrw.commons.BuildConfig;
|
|||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.auth.SessionManager;
|
||||
import fr.free.nrw.commons.location.LocationServiceManager;
|
||||
import fr.free.nrw.commons.nearby.NearbyFragment;
|
||||
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;
|
||||
|
|
@ -50,13 +51,15 @@ import static android.content.ContentResolver.requestSync;
|
|||
|
||||
public class MainActivity extends NavigationBaseActivity implements FragmentManager.OnBackStackChangedListener {
|
||||
|
||||
@Inject
|
||||
SessionManager sessionManager;
|
||||
@Inject ContributionController controller;
|
||||
@BindView(R.id.tab_layout)
|
||||
TabLayout tabLayout;
|
||||
@BindView(R.id.pager)
|
||||
public UnswipableViewPager viewPager;
|
||||
|
||||
@Inject
|
||||
SessionManager sessionManager;
|
||||
@Inject
|
||||
ContributionController controller;
|
||||
@Inject
|
||||
public LocationServiceManager locationManager;
|
||||
@Inject
|
||||
|
|
@ -72,10 +75,9 @@ public class MainActivity extends NavigationBaseActivity implements FragmentMana
|
|||
public static final int NEARBY_TAB_POSITION = 1;
|
||||
|
||||
public boolean isContributionsFragmentVisible = true; // False means nearby fragment is visible
|
||||
public boolean onOrientationChanged;
|
||||
private Menu menu;
|
||||
|
||||
private boolean onOrientationChanged = false;
|
||||
|
||||
private MenuItem notificationsMenuItem;
|
||||
private TextView notificationCount;
|
||||
|
||||
|
|
@ -168,9 +170,7 @@ public class MainActivity extends NavigationBaseActivity implements FragmentMana
|
|||
* tab won't change and vice versa. So we have to notify each of them.
|
||||
*/
|
||||
private void setTabAndViewPagerSynchronisation() {
|
||||
//viewPager.canScrollHorizontally(false);
|
||||
viewPager.setFocusableInTouchMode(true);
|
||||
|
||||
viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
|
||||
@Override
|
||||
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
|
||||
|
|
@ -185,7 +185,6 @@ public class MainActivity extends NavigationBaseActivity implements FragmentMana
|
|||
tabLayout.getTabAt(CONTRIBUTIONS_TAB_POSITION).select();
|
||||
isContributionsFragmentVisible = true;
|
||||
updateMenuItem();
|
||||
|
||||
break;
|
||||
case NEARBY_TAB_POSITION:
|
||||
Timber.d("Nearby tab selected");
|
||||
|
|
@ -193,7 +192,7 @@ public class MainActivity extends NavigationBaseActivity implements FragmentMana
|
|||
isContributionsFragmentVisible = false;
|
||||
updateMenuItem();
|
||||
// Do all permission and GPS related tasks on tab selected, not on create
|
||||
((NearbyFragment)contributionsActivityPagerAdapter.getItem(1)).onTabSelected(onOrientationChanged);
|
||||
NearbyParentFragmentPresenter.getInstance().onTabSelected();
|
||||
break;
|
||||
default:
|
||||
tabLayout.getTabAt(CONTRIBUTIONS_TAB_POSITION).select();
|
||||
|
|
@ -269,15 +268,7 @@ public class MainActivity extends NavigationBaseActivity implements FragmentMana
|
|||
}
|
||||
} else if (getSupportFragmentManager().findFragmentByTag(nearbyFragmentTag) != null && !isContributionsFragmentVisible) {
|
||||
// Means that nearby fragment is visible (not contributions fragment)
|
||||
NearbyFragment nearbyFragment = (NearbyFragment) contributionsActivityPagerAdapter.getItem(1);
|
||||
|
||||
if(nearbyFragment.isBottomSheetExpanded()) {
|
||||
// Back should first hide the bottom sheet if it is expanded
|
||||
nearbyFragment.listOptionMenuItemClicked();
|
||||
} else {
|
||||
// Otherwise go back to contributions fragment
|
||||
viewPager.setCurrentItem(0);
|
||||
}
|
||||
NearbyParentFragmentPresenter.getInstance().backButtonClicked();
|
||||
} else {
|
||||
super.onBackPressed();
|
||||
}
|
||||
|
|
@ -334,12 +325,12 @@ public class MainActivity extends NavigationBaseActivity implements FragmentMana
|
|||
// Display notifications menu item
|
||||
menu.findItem(R.id.notifications).setVisible(true);
|
||||
menu.findItem(R.id.list_sheet).setVisible(false);
|
||||
Timber.d("Contributions activity notifications menu item is visible");
|
||||
Timber.d("Contributions fragment notifications menu item is visible");
|
||||
} else {
|
||||
// Display bottom list menu item
|
||||
menu.findItem(R.id.notifications).setVisible(false);
|
||||
menu.findItem(R.id.list_sheet).setVisible(true);
|
||||
Timber.d("Contributions activity list sheet menu item is visible");
|
||||
Timber.d("Nearby fragment list sheet menu item is visible");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -353,7 +344,7 @@ public class MainActivity extends NavigationBaseActivity implements FragmentMana
|
|||
return true;
|
||||
case R.id.list_sheet:
|
||||
if (contributionsActivityPagerAdapter.getItem(1) != null) {
|
||||
((NearbyFragment)contributionsActivityPagerAdapter.getItem(1)).listOptionMenuItemClicked();
|
||||
((NearbyParentFragment)contributionsActivityPagerAdapter.getItem(1)).listOptionMenuItemClicked();
|
||||
}
|
||||
return true;
|
||||
default:
|
||||
|
|
@ -363,8 +354,6 @@ public class MainActivity extends NavigationBaseActivity implements FragmentMana
|
|||
|
||||
public class ContributionsActivityPagerAdapter extends FragmentPagerAdapter {
|
||||
FragmentManager fragmentManager;
|
||||
private boolean isContributionsListFragment = true; // to know what to put in first tab, Contributions of Media Details
|
||||
|
||||
|
||||
public ContributionsActivityPagerAdapter(FragmentManager fragmentManager) {
|
||||
super(fragmentManager);
|
||||
|
|
@ -394,12 +383,12 @@ public class MainActivity extends NavigationBaseActivity implements FragmentMana
|
|||
}
|
||||
|
||||
case 1:
|
||||
NearbyFragment retainedNearbyFragment = getNearbyFragment(1);
|
||||
NearbyParentFragment retainedNearbyFragment = getNearbyFragment(1);
|
||||
if (retainedNearbyFragment != null) {
|
||||
return retainedNearbyFragment;
|
||||
} else {
|
||||
// If we reach here, retainedNearbyFragment is null
|
||||
return new NearbyFragment();
|
||||
return new NearbyParentFragment();
|
||||
}
|
||||
default:
|
||||
return null;
|
||||
|
|
@ -421,9 +410,9 @@ public class MainActivity extends NavigationBaseActivity implements FragmentMana
|
|||
* @param position index of tabs, in our case 0 or 1
|
||||
* @return
|
||||
*/
|
||||
private NearbyFragment getNearbyFragment(int position) {
|
||||
private NearbyParentFragment getNearbyFragment(int position) {
|
||||
String tag = makeFragmentName(R.id.pager, position);
|
||||
return (NearbyFragment)fragmentManager.findFragmentByTag(tag);
|
||||
return (NearbyParentFragment)fragmentManager.findFragmentByTag(tag);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -446,7 +435,6 @@ public class MainActivity extends NavigationBaseActivity implements FragmentMana
|
|||
controller.handleActivityResult(this, requestCode, resultCode, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
setNotificationCount();
|
||||
|
|
|
|||
|
|
@ -13,9 +13,9 @@ 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.NearbyFragment;
|
||||
import fr.free.nrw.commons.nearby.NearbyListFragment;
|
||||
import fr.free.nrw.commons.nearby.NearbyMapFragment;
|
||||
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;
|
||||
import fr.free.nrw.commons.upload.categories.UploadCategoriesFragment;
|
||||
|
|
@ -38,9 +38,6 @@ public abstract class FragmentBuilderModule {
|
|||
@ContributesAndroidInjector
|
||||
abstract NearbyListFragment bindNearbyListFragment();
|
||||
|
||||
@ContributesAndroidInjector
|
||||
abstract NearbyMapFragment bindNearbyMapFragment();
|
||||
|
||||
@ContributesAndroidInjector
|
||||
abstract SettingsFragment bindSettingsFragment();
|
||||
|
||||
|
|
@ -63,7 +60,10 @@ public abstract class FragmentBuilderModule {
|
|||
abstract ContributionsFragment bindContributionsFragment();
|
||||
|
||||
@ContributesAndroidInjector
|
||||
abstract NearbyFragment bindNearbyFragment();
|
||||
abstract NearbyMapFragment bindNearbyMapFragment();
|
||||
|
||||
@ContributesAndroidInjector
|
||||
abstract NearbyParentFragment bindNearbyParentFragment();
|
||||
|
||||
@ContributesAndroidInjector
|
||||
abstract BookmarkPicturesFragment bindBookmarkPictureListFragment();
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import android.location.Location;
|
|||
import android.location.LocationListener;
|
||||
import android.location.LocationManager;
|
||||
import android.os.Bundle;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
|
@ -222,6 +221,7 @@ public class LocationServiceManager implements LocationListener {
|
|||
LOCATION_MEDIUM_CHANGED, //Between slight and significant changes, will be used for nearby card view updates.
|
||||
LOCATION_NOT_CHANGED,
|
||||
PERMISSION_JUST_GRANTED,
|
||||
MAP_UPDATED
|
||||
MAP_UPDATED,
|
||||
SEARCH_CUSTOM_AREA
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ public class NearbyBaseMarker extends BaseMarkerOptions<NearbyMarker, NearbyBase
|
|||
|
||||
private Place place;
|
||||
|
||||
NearbyBaseMarker() {
|
||||
public NearbyBaseMarker() {
|
||||
}
|
||||
|
||||
private NearbyBaseMarker(Parcel in) {
|
||||
|
|
|
|||
|
|
@ -28,8 +28,10 @@ import static fr.free.nrw.commons.utils.LengthUtils.formatDistanceBetween;
|
|||
public class NearbyController {
|
||||
private static final int MAX_RESULTS = 1000;
|
||||
private final NearbyPlaces nearbyPlaces;
|
||||
public static double searchedRadius = 10.0; //in kilometers
|
||||
public static LatLng currentLocation;
|
||||
public static double currentLocationSearchRadius = 10.0; //in kilometers
|
||||
public static LatLng currentLocation; // Users latest fetched location
|
||||
public static LatLng latestSearchLocation; // Can be current and camera target on search this area button is used
|
||||
public static double latestSearchRadius = 10.0; // Any last search radius except closest result search
|
||||
|
||||
@Inject
|
||||
public NearbyController(NearbyPlaces nearbyPlaces) {
|
||||
|
|
@ -41,21 +43,21 @@ public class NearbyController {
|
|||
* Prepares Place list to make their distance information update later.
|
||||
*
|
||||
* @param curLatLng current location for user
|
||||
* @param latLangToSearchAround the location user wants to search around
|
||||
* @param searchLatLng the location user wants to search around
|
||||
* @param returnClosestResult if this search is done to find closest result or all results
|
||||
* @return NearbyPlacesInfo a variable holds Place list without distance information
|
||||
* and boundary coordinates of current Place List
|
||||
*/
|
||||
public NearbyPlacesInfo loadAttractionsFromLocation(LatLng curLatLng, LatLng latLangToSearchAround, boolean returnClosestResult, boolean checkingAroundCurrentLocation) throws IOException {
|
||||
public NearbyPlacesInfo loadAttractionsFromLocation(LatLng curLatLng, LatLng searchLatLng, boolean returnClosestResult, boolean checkingAroundCurrentLocation) throws IOException {
|
||||
|
||||
Timber.d("Loading attractions near %s", latLangToSearchAround);
|
||||
Timber.d("Loading attractions near %s", searchLatLng);
|
||||
NearbyPlacesInfo nearbyPlacesInfo = new NearbyPlacesInfo();
|
||||
|
||||
if (latLangToSearchAround == null) {
|
||||
if (searchLatLng == null) {
|
||||
Timber.d("Loading attractions nearby, but curLatLng is null");
|
||||
return null;
|
||||
}
|
||||
List<Place> places = nearbyPlaces.radiusExpander(latLangToSearchAround, Locale.getDefault().getLanguage(), returnClosestResult);
|
||||
List<Place> places = nearbyPlaces.radiusExpander(searchLatLng, Locale.getDefault().getLanguage(), returnClosestResult);
|
||||
|
||||
if (null != places && places.size() > 0) {
|
||||
LatLng[] boundaryCoordinates = {places.get(0).location, // south
|
||||
|
|
@ -91,13 +93,25 @@ public class NearbyController {
|
|||
}
|
||||
);
|
||||
}
|
||||
nearbyPlacesInfo.curLatLng = curLatLng;
|
||||
nearbyPlacesInfo.searchLatLng = searchLatLng;
|
||||
nearbyPlacesInfo.placeList = places;
|
||||
nearbyPlacesInfo.boundaryCoordinates = boundaryCoordinates;
|
||||
if (!returnClosestResult && checkingAroundCurrentLocation) {
|
||||
// Do not update searched radius, if controller is used for nearby card notification
|
||||
searchedRadius = nearbyPlaces.radius;
|
||||
currentLocation = curLatLng;
|
||||
|
||||
// Returning closes result means we use the controller for nearby card. So no need to set search this area flags
|
||||
if (!returnClosestResult) {
|
||||
// To remember latest search either around user or any point on map
|
||||
latestSearchLocation = searchLatLng;
|
||||
latestSearchRadius = nearbyPlaces.radius*1000; // to meter
|
||||
|
||||
// Our radius searched around us, will be used to understand when user search their own location, we will follow them
|
||||
if (checkingAroundCurrentLocation) {
|
||||
currentLocationSearchRadius = nearbyPlaces.radius*1000; // to meter
|
||||
currentLocation = curLatLng;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return nearbyPlacesInfo;
|
||||
}
|
||||
else {
|
||||
|
|
@ -112,7 +126,7 @@ public class NearbyController {
|
|||
* @param placeList list of nearby places in Place data type
|
||||
* @return Place list that holds nearby places
|
||||
*/
|
||||
static List<Place> loadAttractionsFromLocationToPlaces(
|
||||
public static List<Place> loadAttractionsFromLocationToPlaces(
|
||||
LatLng curLatLng,
|
||||
List<Place> placeList) {
|
||||
placeList = placeList.subList(0, Math.min(placeList.size(), MAX_RESULTS));
|
||||
|
|
@ -212,5 +226,7 @@ public class NearbyController {
|
|||
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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,723 +0,0 @@
|
|||
package fr.free.nrw.commons.nearby;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ProgressBar;
|
||||
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import com.google.gson.Gson;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.contributions.MainActivity;
|
||||
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
|
||||
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.utils.FragmentUtils;
|
||||
import fr.free.nrw.commons.utils.NetworkUtils;
|
||||
import fr.free.nrw.commons.utils.PermissionUtils;
|
||||
import fr.free.nrw.commons.utils.ViewUtil;
|
||||
import fr.free.nrw.commons.wikidata.WikidataEditListener;
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import timber.log.Timber;
|
||||
|
||||
import static fr.free.nrw.commons.contributions.MainActivity.CONTRIBUTIONS_TAB_POSITION;
|
||||
import static fr.free.nrw.commons.contributions.MainActivity.NEARBY_TAB_POSITION;
|
||||
import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED;
|
||||
import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.LOCATION_SLIGHTLY_CHANGED;
|
||||
import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.MAP_UPDATED;
|
||||
import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.PERMISSION_JUST_GRANTED;
|
||||
|
||||
public class NearbyFragment extends CommonsDaggerSupportFragment
|
||||
implements LocationUpdateListener,
|
||||
WikidataEditListener.WikidataP18EditListener {
|
||||
|
||||
@BindView(R.id.progressBar)
|
||||
ProgressBar progressBar;
|
||||
@BindView(R.id.bottom_sheet)
|
||||
LinearLayout bottomSheet;
|
||||
@BindView(R.id.bottom_sheet_details)
|
||||
LinearLayout bottomSheetDetails;
|
||||
@BindView(R.id.transparentView)
|
||||
View transparentView;
|
||||
@BindView(R.id.container_sheet)
|
||||
FrameLayout frameLayout;
|
||||
@BindView(R.id.loading_nearby_list)
|
||||
ConstraintLayout loading_nearby_layout;
|
||||
|
||||
@Inject
|
||||
LocationServiceManager locationManager;
|
||||
@Inject
|
||||
NearbyController nearbyController;
|
||||
@Inject
|
||||
WikidataEditListener wikidataEditListener;
|
||||
@Inject Gson gson;
|
||||
|
||||
public NearbyMapFragment nearbyMapFragment;
|
||||
private NearbyListFragment nearbyListFragment;
|
||||
private static final String TAG_RETAINED_MAP_FRAGMENT = NearbyMapFragment.class.getSimpleName();
|
||||
private static final String TAG_RETAINED_LIST_FRAGMENT = NearbyListFragment.class.getSimpleName();
|
||||
private Bundle bundle;
|
||||
private BottomSheetBehavior bottomSheetBehavior; // Behavior for list bottom sheet
|
||||
private BottomSheetBehavior bottomSheetBehaviorForDetails; // Behavior for details bottom sheet
|
||||
|
||||
private LatLng curLatLng;
|
||||
private boolean lockNearbyView; //Determines if the nearby places needs to be refreshed
|
||||
public View view;
|
||||
private Snackbar snackbar;
|
||||
|
||||
private LatLng lastKnownLocation;
|
||||
private LatLng customLatLng;
|
||||
|
||||
private final String NETWORK_INTENT_ACTION = "android.net.conn.CONNECTIVITY_CHANGE";
|
||||
private BroadcastReceiver broadcastReceiver;
|
||||
|
||||
private boolean onOrientationChanged = false;
|
||||
private boolean populateForCurrentLocation = false;
|
||||
private boolean isNetworkErrorOccured = false;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setRetainInstance(true);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_nearby, container, false);
|
||||
ButterKnife.bind(this, view);
|
||||
|
||||
/*// Resume the fragment if exist
|
||||
resumeFragment();*/
|
||||
bundle = new Bundle();
|
||||
initBottomSheetBehaviour();
|
||||
this.view = view;
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
if (savedInstanceState != null) {
|
||||
onOrientationChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide or expand bottom sheet according to states of all sheets
|
||||
*/
|
||||
public void listOptionMenuItemClicked() {
|
||||
if(bottomSheetBehavior.getState()==BottomSheetBehavior.STATE_COLLAPSED || bottomSheetBehavior.getState()==BottomSheetBehavior.STATE_HIDDEN){
|
||||
bottomSheetBehaviorForDetails.setState(BottomSheetBehavior.STATE_HIDDEN);
|
||||
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
|
||||
}else if(bottomSheetBehavior.getState()==BottomSheetBehavior.STATE_EXPANDED){
|
||||
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resume fragments if they exists
|
||||
*/
|
||||
private void resumeFragment() {
|
||||
// Find the retained fragment on activity restarts
|
||||
nearbyMapFragment = getMapFragment();
|
||||
nearbyListFragment = getListFragment();
|
||||
addNetworkBroadcastReceiver();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the map fragment added to child fragment manager previously, if exists.
|
||||
*/
|
||||
private NearbyMapFragment getMapFragment() {
|
||||
return (NearbyMapFragment) getChildFragmentManager().findFragmentByTag(TAG_RETAINED_MAP_FRAGMENT);
|
||||
}
|
||||
|
||||
private void removeMapFragment() {
|
||||
if (nearbyMapFragment != null) {
|
||||
FragmentManager fm = getFragmentManager();
|
||||
fm.beginTransaction().remove(nearbyMapFragment).commit();
|
||||
nearbyMapFragment = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the list fragment added to child fragment manager previously, if exists.
|
||||
*/
|
||||
private NearbyListFragment getListFragment() {
|
||||
return (NearbyListFragment) getChildFragmentManager().findFragmentByTag(TAG_RETAINED_LIST_FRAGMENT);
|
||||
}
|
||||
|
||||
private void removeListFragment() {
|
||||
if (nearbyListFragment != null) {
|
||||
FragmentManager fm = getFragmentManager();
|
||||
fm.beginTransaction().remove(nearbyListFragment).commit();
|
||||
nearbyListFragment = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize bottom sheet behaviour (sheet for map list.) Set height 9/16 of all window.
|
||||
* Add callback for bottom sheet changes, so that we can sync it with bottom sheet for details
|
||||
* (sheet for nearby details)
|
||||
*/
|
||||
private void initBottomSheetBehaviour() {
|
||||
|
||||
transparentView.setAlpha(0);
|
||||
bottomSheet.getLayoutParams().height = getActivity().getWindowManager()
|
||||
.getDefaultDisplay().getHeight() / 16 * 9;
|
||||
bottomSheetBehavior = BottomSheetBehavior.from(bottomSheet);
|
||||
bottomSheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
|
||||
|
||||
@Override
|
||||
public void onStateChanged(View bottomSheet, int unusedNewState) {
|
||||
prepareViewsForSheetPosition();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSlide(View bottomSheet, float slideOffset) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
|
||||
bottomSheetBehaviorForDetails = BottomSheetBehavior.from(bottomSheetDetails);
|
||||
bottomSheetBehaviorForDetails.setState(BottomSheetBehavior.STATE_HIDDEN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets camera position, zoom level according to sheet positions
|
||||
*/
|
||||
private void prepareViewsForSheetPosition() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLocationChangedSignificantly(LatLng latLng) {
|
||||
refreshView(LOCATION_SIGNIFICANTLY_CHANGED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLocationChangedSlightly(LatLng latLng) {
|
||||
refreshView(LOCATION_SLIGHTLY_CHANGED);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onLocationChangedMedium(LatLng latLng) {
|
||||
// For nearby map actions, there are no differences between 500 meter location change (aka medium change) and slight change
|
||||
refreshView(LOCATION_SLIGHTLY_CHANGED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWikidataEditSuccessful() {
|
||||
// Do not refresh nearby map if we are checking other areas with search this area button
|
||||
if (nearbyMapFragment != null && !nearbyMapFragment.searchThisAreaModeOn) {
|
||||
refreshView(MAP_UPDATED);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method should be the single point to load/refresh nearby places
|
||||
*
|
||||
* @param locationChangeType defines if location changed significantly or slightly
|
||||
*/
|
||||
private void refreshView(LocationServiceManager.LocationChangeType locationChangeType) {
|
||||
Timber.d("Refreshing nearby places");
|
||||
if (lockNearbyView) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!NetworkUtils.isInternetConnectionEstablished(getActivity())) {
|
||||
hideProgressBar();
|
||||
return;
|
||||
}
|
||||
|
||||
registerLocationUpdates();
|
||||
LatLng lastLocation = locationManager.getLastLocation();
|
||||
|
||||
if (curLatLng != null && curLatLng.equals(lastLocation)
|
||||
&& !locationChangeType.equals(MAP_UPDATED)) { //refresh view only if location has changed
|
||||
// Two exceptional cases to refresh nearby map manually.
|
||||
if (!onOrientationChanged) {
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
curLatLng = lastLocation;
|
||||
|
||||
if (locationChangeType.equals(PERMISSION_JUST_GRANTED)) {
|
||||
curLatLng = lastKnownLocation;
|
||||
}
|
||||
|
||||
if (curLatLng == null) {
|
||||
Timber.d("Skipping update of nearby places as location is unavailable");
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
onOrientation changed is true whenever activities orientation changes. After orientation
|
||||
change we want to refresh map significantly, doesn't matter if location changed significantly
|
||||
or not. Thus, we included onOrientationChanged boolean to if clause
|
||||
*/
|
||||
if (locationChangeType.equals(LOCATION_SIGNIFICANTLY_CHANGED)
|
||||
|| locationChangeType.equals(PERMISSION_JUST_GRANTED)
|
||||
|| locationChangeType.equals(MAP_UPDATED)
|
||||
|| onOrientationChanged) {
|
||||
progressBar.setVisibility(View.VISIBLE);
|
||||
|
||||
//TODO: This hack inserts curLatLng before populatePlaces is called (see #1440). Ideally a proper fix should be found
|
||||
String gsonCurLatLng = gson.toJson(curLatLng);
|
||||
bundle.clear();
|
||||
bundle.putString("CurLatLng", gsonCurLatLng);
|
||||
|
||||
compositeDisposable.add(Observable.fromCallable(() -> nearbyController
|
||||
.loadAttractionsFromLocation(curLatLng, curLatLng, false, true))
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(this::populatePlaces,
|
||||
throwable -> {
|
||||
Timber.d(throwable);
|
||||
showErrorMessage(getString(R.string.error_fetching_nearby_places));
|
||||
progressBar.setVisibility(View.GONE);
|
||||
}));
|
||||
|
||||
} else if (locationChangeType
|
||||
.equals(LOCATION_SLIGHTLY_CHANGED) && nearbyMapFragment != null) {
|
||||
String gsonCurLatLng = gson.toJson(curLatLng);
|
||||
bundle.putString("CurLatLng", gsonCurLatLng);
|
||||
updateMapFragment(false,true, null, null);
|
||||
}
|
||||
|
||||
if (nearbyMapFragment != null && nearbyMapFragment.searchThisAreaButton != null) {
|
||||
nearbyMapFragment.searchThisAreaButton.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method should be used with "Search this are button". This method will search nearby
|
||||
* points around any custom location (target location when user clicked on search this area)
|
||||
* button. It populates places for custom location.
|
||||
* @param customLatLng Custom area which we will search around
|
||||
*/
|
||||
void refreshViewForCustomLocation(LatLng customLatLng, boolean refreshForCurrentLocation) {
|
||||
if (customLatLng == null) {
|
||||
// If null, return
|
||||
return;
|
||||
}
|
||||
|
||||
populateForCurrentLocation = refreshForCurrentLocation;
|
||||
this.customLatLng = customLatLng;
|
||||
compositeDisposable.add(Observable.fromCallable(() -> nearbyController
|
||||
.loadAttractionsFromLocation(curLatLng, customLatLng, false, populateForCurrentLocation))
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(this::populatePlacesFromCustomLocation,
|
||||
throwable -> {
|
||||
Timber.d(throwable);
|
||||
showErrorMessage(getString(R.string.error_fetching_nearby_places));
|
||||
}));
|
||||
|
||||
if (nearbyMapFragment != null) {
|
||||
nearbyMapFragment.searchThisAreaButton.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 place list information and distances.
|
||||
*/
|
||||
private void populatePlacesFromCustomLocation(NearbyController.NearbyPlacesInfo nearbyPlacesInfo) {
|
||||
if (nearbyMapFragment != null) {
|
||||
nearbyMapFragment.searchThisAreaButtonProgressBar.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (nearbyMapFragment != null && curLatLng != null) {
|
||||
if (!populateForCurrentLocation) {
|
||||
nearbyMapFragment.updateMapSignificantlyForCustomLocation(customLatLng, nearbyPlacesInfo.placeList);
|
||||
} else {
|
||||
updateMapFragment(true,true, customLatLng, nearbyPlacesInfo);
|
||||
}
|
||||
updateListFragmentForCustomLocation(nearbyPlacesInfo.placeList);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns nearby place lists and boundary coordinates into gson and update map and list fragments
|
||||
* accordingly
|
||||
* @param nearbyPlacesInfo a variable holds both nearby place list and boundary coordinates
|
||||
*/
|
||||
private void populatePlaces(NearbyController.NearbyPlacesInfo nearbyPlacesInfo) {
|
||||
Timber.d("Populating nearby places");
|
||||
List<Place> placeList = nearbyPlacesInfo.placeList;
|
||||
LatLng[] boundaryCoordinates = nearbyPlacesInfo.boundaryCoordinates;
|
||||
String gsonPlaceList = gson.toJson(placeList);
|
||||
String gsonCurLatLng = gson.toJson(curLatLng);
|
||||
String gsonBoundaryCoordinates = gson.toJson(boundaryCoordinates);
|
||||
|
||||
if (placeList.size() == 0) {
|
||||
ViewUtil.showShortSnackbar(view.findViewById(R.id.container), R.string.no_nearby);
|
||||
}
|
||||
|
||||
bundle.putString("PlaceList", gsonPlaceList);
|
||||
bundle.putString("BoundaryCoord", gsonBoundaryCoordinates);
|
||||
|
||||
// First time to init fragments
|
||||
if (nearbyMapFragment == null) {
|
||||
Timber.d("Init map fragment for the first time");
|
||||
lockNearbyView(true);
|
||||
setMapFragment();
|
||||
setListFragment();
|
||||
hideProgressBar();
|
||||
lockNearbyView(false);
|
||||
} else {
|
||||
// There are fragments, just update the map and list
|
||||
Timber.d("Map fragment already exists, just update the map and list");
|
||||
updateMapFragment(false,false, null, null);
|
||||
updateListFragment();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lock nearby view updates while updating map or list. Because we don't want new update calls
|
||||
* when we already updating for old location update.
|
||||
* @param lock true if we should lock nearby map
|
||||
*/
|
||||
private void lockNearbyView(boolean lock) {
|
||||
if (lock) {
|
||||
lockNearbyView = true;
|
||||
locationManager.unregisterLocationManager();
|
||||
locationManager.removeLocationListener(this);
|
||||
} else {
|
||||
lockNearbyView = false;
|
||||
registerLocationUpdates();
|
||||
locationManager.addLocationListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates map fragment,
|
||||
* For slight update: camera follows users location
|
||||
* For significant update: nearby markers are removed and new markers added again
|
||||
* Slight updates stop if user is checking another area of map
|
||||
*
|
||||
* @param updateViaButton search this area button is clicked
|
||||
* @param isSlightUpdate Means no need to update markers, just follow user location with camera
|
||||
* @param customLatLng Will be used for updates for other locations than users current location.
|
||||
* Ie. when we use search this area feature
|
||||
* @param nearbyPlacesInfo Includes nearby places list and boundary coordinates
|
||||
*/
|
||||
private void updateMapFragment(boolean updateViaButton, boolean isSlightUpdate, @Nullable LatLng customLatLng, @Nullable NearbyController.NearbyPlacesInfo nearbyPlacesInfo) {
|
||||
/*
|
||||
Significant update means updating nearby place markers. Slightly update means only
|
||||
updating current location marker and camera target.
|
||||
We update our map Significantly on each 1000 meter change, but we can't never know
|
||||
the frequency of nearby places. Thus we check if we are close to the boundaries of
|
||||
our nearby markers, we update our map Significantly.
|
||||
*/
|
||||
NearbyMapFragment nearbyMapFragment = getMapFragment();
|
||||
|
||||
if (nearbyMapFragment != null && !nearbyMapFragment.isCurrentLocationMarkerVisible() && !onOrientationChanged) {
|
||||
Timber.d("Do not update the map, user is not seeing current location marker" +
|
||||
" means they are checking around and moving on map");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (nearbyMapFragment != null && curLatLng != null) {
|
||||
hideProgressBar(); // In case it is visible (this happens, not an impossible case)
|
||||
/*
|
||||
* If we are close to nearby places boundaries, we need a significant update to
|
||||
* get new nearby places. Check order is south, north, west, east
|
||||
* */
|
||||
if (nearbyMapFragment.boundaryCoordinates != null
|
||||
&& !nearbyMapFragment.checkingAround
|
||||
&& !nearbyMapFragment.searchThisAreaModeOn
|
||||
&& !onOrientationChanged
|
||||
&& (curLatLng.getLatitude() < nearbyMapFragment.boundaryCoordinates[0].getLatitude()
|
||||
|| curLatLng.getLatitude() > nearbyMapFragment.boundaryCoordinates[1].getLatitude()
|
||||
|| curLatLng.getLongitude() < nearbyMapFragment.boundaryCoordinates[2].getLongitude()
|
||||
|| curLatLng.getLongitude() > nearbyMapFragment.boundaryCoordinates[3].getLongitude())) {
|
||||
// populate places
|
||||
compositeDisposable.add(Observable.fromCallable(() -> nearbyController
|
||||
.loadAttractionsFromLocation(curLatLng, curLatLng, false, updateViaButton))
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(this::populatePlaces,
|
||||
throwable -> {
|
||||
Timber.d(throwable);
|
||||
showErrorMessage(getString(R.string.error_fetching_nearby_places));
|
||||
progressBar.setVisibility(View.GONE);
|
||||
}));
|
||||
nearbyMapFragment.setBundleForUpdates(bundle);
|
||||
nearbyMapFragment.updateMapSignificantlyForCurrentLocation();
|
||||
updateListFragment();
|
||||
return;
|
||||
}
|
||||
|
||||
if (updateViaButton) {
|
||||
nearbyMapFragment.updateMapSignificantlyForCustomLocation(customLatLng, nearbyPlacesInfo.placeList);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
If this is the map update just after orientation change, then it is not a slight update
|
||||
anymore. We want to significantly update map after each orientation change
|
||||
*/
|
||||
if (onOrientationChanged) {
|
||||
isSlightUpdate = false;
|
||||
onOrientationChanged = false;
|
||||
}
|
||||
|
||||
if (isSlightUpdate) {
|
||||
nearbyMapFragment.setBundleForUpdates(bundle);
|
||||
nearbyMapFragment.updateMapSlightly();
|
||||
} else {
|
||||
nearbyMapFragment.setBundleForUpdates(bundle);
|
||||
nearbyMapFragment.updateMapSignificantlyForCurrentLocation();
|
||||
updateListFragment();
|
||||
}
|
||||
} else {
|
||||
lockNearbyView(true);
|
||||
setMapFragment();
|
||||
setListFragment();
|
||||
hideProgressBar();
|
||||
lockNearbyView(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates already existing list fragment with bundle includes nearby places and boundary
|
||||
* coordinates
|
||||
*/
|
||||
private void updateListFragment() {
|
||||
nearbyListFragment.setBundleForUpdates(bundle);
|
||||
nearbyListFragment.updateNearbyListSignificantly();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates nearby list for custom location, will be used with search this area method. When you
|
||||
* want to search for a place where you are not at.
|
||||
* @param placeList List of places around your manually chosen target location from map.
|
||||
*/
|
||||
private void updateListFragmentForCustomLocation(List<Place> placeList) {
|
||||
nearbyListFragment.updateNearbyListSignificantlyForCustomLocation(placeList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls fragment for map view.
|
||||
*/
|
||||
private void setMapFragment() {
|
||||
FragmentTransaction fragmentTransaction = getChildFragmentManager().beginTransaction();
|
||||
nearbyMapFragment = new NearbyMapFragment();
|
||||
nearbyMapFragment.setArguments(bundle);
|
||||
fragmentTransaction.replace(R.id.container, nearbyMapFragment, TAG_RETAINED_MAP_FRAGMENT);
|
||||
fragmentTransaction.commitAllowingStateLoss();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls fragment for list view.
|
||||
*/
|
||||
private void setListFragment() {
|
||||
loading_nearby_layout.setVisibility(View.GONE);
|
||||
frameLayout.setVisibility(View.VISIBLE);
|
||||
FragmentTransaction fragmentTransaction = getChildFragmentManager().beginTransaction();
|
||||
nearbyListFragment = new NearbyListFragment();
|
||||
nearbyListFragment.setArguments(bundle);
|
||||
fragmentTransaction.replace(R.id.container_sheet, nearbyListFragment, TAG_RETAINED_LIST_FRAGMENT);
|
||||
initBottomSheetBehaviour();
|
||||
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
|
||||
fragmentTransaction.commitAllowingStateLoss();
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides progress bar
|
||||
*/
|
||||
private void hideProgressBar() {
|
||||
if (progressBar != null) {
|
||||
progressBar.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method first checks if the location permissions has been granted and then register the location manager for updates.
|
||||
*/
|
||||
private void registerLocationUpdates() {
|
||||
locationManager.registerLocationManager();
|
||||
}
|
||||
|
||||
private void showErrorMessage(String message) {
|
||||
ViewUtil.showLongToast(getActivity(), message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds network broadcast receiver to recognize connection established
|
||||
*/
|
||||
private void addNetworkBroadcastReceiver() {
|
||||
if (!FragmentUtils.isFragmentUIActive(this)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (broadcastReceiver != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
IntentFilter intentFilter = new IntentFilter(NETWORK_INTENT_ACTION);
|
||||
|
||||
broadcastReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (getActivity() != null) {
|
||||
if (NetworkUtils.isInternetConnectionEstablished(getActivity())) {
|
||||
if (isNetworkErrorOccured) {
|
||||
refreshView(LOCATION_SIGNIFICANTLY_CHANGED);
|
||||
isNetworkErrorOccured = false;
|
||||
}
|
||||
|
||||
if (snackbar != null) {
|
||||
snackbar.dismiss();
|
||||
snackbar = null;
|
||||
}
|
||||
} else {
|
||||
if (snackbar == null) {
|
||||
snackbar = Snackbar.make(view, R.string.no_internet, Snackbar.LENGTH_INDEFINITE);
|
||||
if (nearbyMapFragment != null && nearbyMapFragment.searchThisAreaButton != null) {
|
||||
nearbyMapFragment.searchThisAreaButton.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
isNetworkErrorOccured = true;
|
||||
snackbar.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
getActivity().registerReceiver(broadcastReceiver, intentFilter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
// Resume the fragment if exist
|
||||
if (((MainActivity) getActivity()).viewPager.getCurrentItem() == NEARBY_TAB_POSITION) {
|
||||
checkPermissionsAndPerformAction(this::resumeFragment);
|
||||
} else {
|
||||
resumeFragment();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform nearby operations on nearby tab selected
|
||||
* @param onOrientationChanged pass orientation changed info to fragment
|
||||
*/
|
||||
public void onTabSelected(boolean onOrientationChanged) {
|
||||
Timber.d("On nearby tab selected");
|
||||
this.onOrientationChanged = onOrientationChanged;
|
||||
checkPermissionsAndPerformAction(this::performNearbyOperations);
|
||||
}
|
||||
|
||||
private void checkPermissionsAndPerformAction(Runnable runnable) {
|
||||
PermissionUtils.checkPermissionsAndPerformAction(getActivity(),
|
||||
Manifest.permission.ACCESS_FINE_LOCATION,
|
||||
runnable,
|
||||
() -> ((MainActivity) getActivity()).viewPager.setCurrentItem(CONTRIBUTIONS_TAB_POSITION),
|
||||
R.string.location_permission_title,
|
||||
R.string.location_permission_rationale_nearby);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls nearby operations in required order.
|
||||
*/
|
||||
private void performNearbyOperations() {
|
||||
locationManager.addLocationListener(this);
|
||||
registerLocationUpdates();
|
||||
lockNearbyView = false;
|
||||
addNetworkBroadcastReceiver();
|
||||
refreshView(LOCATION_SIGNIFICANTLY_CHANGED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
wikidataEditListener.setAuthenticationStateListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
wikidataEditListener.setAuthenticationStateListener(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetach() {
|
||||
super.onDetach();
|
||||
snackbar = null;
|
||||
broadcastReceiver = null;
|
||||
wikidataEditListener.setAuthenticationStateListener(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
// this means that this activity will not be recreated now, user is leaving it
|
||||
// or the activity is otherwise finishing
|
||||
if(getActivity().isFinishing()) {
|
||||
// we will not need this fragment anymore, this may also be a good place to signal
|
||||
// to the retained fragment object to perform its own cleanup.
|
||||
//removeMapFragment();
|
||||
removeListFragment();
|
||||
|
||||
}
|
||||
if (broadcastReceiver != null) {
|
||||
getActivity().unregisterReceiver(broadcastReceiver);
|
||||
broadcastReceiver = null;
|
||||
}
|
||||
|
||||
if (locationManager != null) {
|
||||
locationManager.removeLocationListener(this);
|
||||
locationManager.unregisterLocationManager();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Centers the map in nearby fragment to a given place
|
||||
* @param place is new center of the map
|
||||
*/
|
||||
public void centerMapToPlace(Place place) {
|
||||
if (nearbyMapFragment != null) {
|
||||
nearbyMapFragment.centerMapToPlace(place);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isBottomSheetExpanded() { return bottomSheetBehavior.getState() == BottomSheetBehavior.STATE_EXPANDED;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -2,7 +2,7 @@ package fr.free.nrw.commons.nearby;
|
|||
|
||||
import com.mapbox.mapboxsdk.annotations.Marker;
|
||||
|
||||
class NearbyMarker extends Marker {
|
||||
public class NearbyMarker extends Marker {
|
||||
private final Place place;
|
||||
private NearbyBaseMarker nearbyBaseMarker;
|
||||
|
||||
|
|
@ -17,7 +17,7 @@ class NearbyMarker extends Marker {
|
|||
this.nearbyBaseMarker = baseMarkerOptions;
|
||||
}
|
||||
|
||||
NearbyBaseMarker getNearbyBaseMarker() {
|
||||
public NearbyBaseMarker getNearbyBaseMarker() {
|
||||
return nearbyBaseMarker;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import android.widget.TextView;
|
|||
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.contributions.MainActivity;
|
||||
import fr.free.nrw.commons.nearby.fragments.NearbyParentFragment;
|
||||
import fr.free.nrw.commons.utils.SwipableCardView;
|
||||
import fr.free.nrw.commons.utils.ViewUtil;
|
||||
import timber.log.Timber;
|
||||
|
|
@ -90,7 +91,7 @@ public class NearbyNotificationCardView extends SwipableCardView {
|
|||
m.viewPager.setCurrentItem(NEARBY_TAB_POSITION);
|
||||
|
||||
// Center the map to the place
|
||||
((NearbyFragment) m.contributionsActivityPagerAdapter.getItem(NEARBY_TAB_POSITION)).centerMapToPlace(place);
|
||||
((NearbyParentFragment) m.contributionsActivityPagerAdapter.getItem(NEARBY_TAB_POSITION)).centerMapToPlace(place);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@ public class Place implements Parcelable {
|
|||
* Checks if the Wikidata item has a Wikipedia page associated with it
|
||||
* @return true if there is a Wikipedia link
|
||||
*/
|
||||
boolean hasWikipediaLink() {
|
||||
public boolean hasWikipediaLink() {
|
||||
return !(siteLinks == null || Uri.EMPTY.equals(siteLinks.getWikipediaLink()));
|
||||
}
|
||||
|
||||
|
|
@ -131,7 +131,7 @@ public class Place implements Parcelable {
|
|||
* Checks if the Wikidata item has a Wikidata page associated with it
|
||||
* @return true if there is a Wikidata link
|
||||
*/
|
||||
boolean hasWikidataLink() {
|
||||
public boolean hasWikidataLink() {
|
||||
return !(siteLinks == null || Uri.EMPTY.equals(siteLinks.getWikidataLink()));
|
||||
}
|
||||
|
||||
|
|
@ -139,7 +139,7 @@ public class Place implements Parcelable {
|
|||
* Checks if the Wikidata item has a Commons page associated with it
|
||||
* @return true if there is a Commons link
|
||||
*/
|
||||
boolean hasCommonsLink() {
|
||||
public boolean hasCommonsLink() {
|
||||
return !(siteLinks == null || Uri.EMPTY.equals(siteLinks.getCommonsLink()));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -33,8 +33,11 @@ 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;
|
||||
|
||||
|
|
@ -117,7 +120,7 @@ public class PlaceRenderer extends Renderer<Place> {
|
|||
}
|
||||
}
|
||||
if (onBookmarkClick == null) {
|
||||
((NearbyFragment) fragment.getParentFragment()).centerMapToPlace(place);
|
||||
((NearbyParentFragment) fragment.getParentFragment()).centerMapToPlace(place);
|
||||
}
|
||||
};
|
||||
view.setOnClickListener(listener);
|
||||
|
|
@ -190,7 +193,9 @@ public class PlaceRenderer extends Renderer<Place> {
|
|||
onBookmarkClick.onClick();
|
||||
}
|
||||
else {
|
||||
((NearbyMapFragment)((NearbyFragment)((NearbyListFragment)fragment).getParentFragment()).getChildFragmentManager().findFragmentByTag(NearbyMapFragment.class.getSimpleName())).updateMarker(isBookmarked, place);
|
||||
((NearbyMapFragment)(fragment.getParentFragment()).getChildFragmentManager().
|
||||
findFragmentByTag(TAG_RETAINED_MAP_FRAGMENT)).
|
||||
updateMarker(isBookmarked, place, null);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
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.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();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
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.kvstore.JsonKvStore;
|
||||
import fr.free.nrw.commons.location.LatLng;
|
||||
import fr.free.nrw.commons.location.LocationServiceManager;
|
||||
import fr.free.nrw.commons.nearby.Place;
|
||||
|
||||
public interface NearbyParentFragmentContract {
|
||||
|
||||
interface View {
|
||||
void registerLocationUpdates(LocationServiceManager locationServiceManager);
|
||||
boolean isNetworkConnectionEstablished();
|
||||
void addNetworkBroadcastReceiver();
|
||||
void listOptionMenuItemClicked();
|
||||
void populatePlaces(LatLng curlatLng, LatLng searchLatLng);
|
||||
boolean isListBottomSheetExpanded();
|
||||
void checkPermissionsAndPerformAction(Runnable runnable);
|
||||
void displayLoginSkippedWarning();
|
||||
void setFABPlusAction(android.view.View.OnClickListener onClickListener);
|
||||
void setFABRecenterAction(android.view.View.OnClickListener onClickListener);
|
||||
void animateFABs();
|
||||
void recenterMap(LatLng curLatLng);
|
||||
void hideBottomSheet();
|
||||
void displayBottomSheetWithInfo(Marker marker);
|
||||
void addOnCameraMoveListener(MapboxMap.OnCameraMoveListener onCameraMoveListener);
|
||||
void addSearchThisAreaButtonAction();
|
||||
void setSearchThisAreaButtonVisibility(boolean isVisible);
|
||||
void setProgressBarVisibility(boolean isVisible);
|
||||
void setTabItemContributions();
|
||||
boolean isDetailsBottomSheetVisible();
|
||||
void setBottomSheetDetailsSmaller();
|
||||
boolean isSearchThisAreaButtonVisible();
|
||||
}
|
||||
|
||||
interface NearbyListView {
|
||||
void updateListFragment(List<Place> placeList);
|
||||
}
|
||||
|
||||
interface UserActions {
|
||||
void onTabSelected();
|
||||
void checkForPermission();
|
||||
void updateMapAndList(LocationServiceManager.LocationChangeType locationChangeType, LatLng cameraTarget);
|
||||
void lockUnlockNearby(boolean isNearbyLocked);
|
||||
void setActionListeners(JsonKvStore applicationKvStore);
|
||||
void backButtonClicked();
|
||||
MapboxMap.OnCameraMoveListener onCameraMove(MapboxMap mapboxMap);
|
||||
}
|
||||
|
||||
interface ViewsAreReadyCallback {
|
||||
void nearbyFragmentsAreReady();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package fr.free.nrw.commons.nearby;
|
||||
package fr.free.nrw.commons.nearby.fragments;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
|
|
@ -22,10 +22,13 @@ 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 {
|
||||
private Bundle bundleForUpdates; // Carry information from activity about changed nearby places and current location
|
||||
public class NearbyListFragment extends CommonsDaggerSupportFragment implements NearbyParentFragmentContract.NearbyListView {
|
||||
|
||||
private static final Type LIST_TYPE = new TypeToken<List<Place>>() {
|
||||
}.getType();
|
||||
|
|
@ -69,30 +72,6 @@ public class NearbyListFragment extends CommonsDaggerSupportFragment {
|
|||
recyclerView.setAdapter(adapterFactory.create(getPlaceListFromBundle(bundle)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates nearby list elements all together
|
||||
*/
|
||||
public void updateNearbyListSignificantly() {
|
||||
try {
|
||||
adapterFactory.updateAdapterData(getPlaceListFromBundle(bundleForUpdates), (RVRendererAdapter<Place>) recyclerView.getAdapter());
|
||||
} catch (NullPointerException e) {
|
||||
Timber.e("Null pointer exception from calling recyclerView.getAdapter()");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* While nearby updates for current location held with bundle, automatically, custom updates are
|
||||
* done by calling this method, triggered by search this are button input from user.
|
||||
* @param placeList List of nearby places to be added list fragment
|
||||
*/
|
||||
public void updateNearbyListSignificantlyForCustomLocation(List<Place> placeList) {
|
||||
try {
|
||||
adapterFactory.updateAdapterData(placeList, (RVRendererAdapter<Place>) recyclerView.getAdapter());
|
||||
} catch (NullPointerException e) {
|
||||
Timber.e("Null pointer exception from calling recyclerView.getAdapter()");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
|
@ -116,12 +95,9 @@ public class NearbyListFragment extends CommonsDaggerSupportFragment {
|
|||
return placeList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets bundles for updates in map. Ie. user is moved too much so we need to update nearby markers.
|
||||
* @param bundleForUpdates includes new calculated nearby places.
|
||||
*/
|
||||
public void setBundleForUpdates(Bundle bundleForUpdates) {
|
||||
this.bundleForUpdates = bundleForUpdates;
|
||||
@Override
|
||||
public void updateListFragment(List<Place> placeList) {
|
||||
Timber.d("Update list fragment");
|
||||
adapterFactory.updateAdapterData(placeList, (RVRendererAdapter<Place>) recyclerView.getAdapter());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,457 @@
|
|||
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.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.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 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 default MapFragment instance
|
||||
*
|
||||
* @return MapFragment created
|
||||
*/
|
||||
public static NearbyMapFragment newInstance() {
|
||||
return new NearbyMapFragment();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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");
|
||||
List<NearbyBaseMarker> 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) {
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeCurrentLocationMarker() {
|
||||
mapboxMap.removeMarker(currentLocationMarker);
|
||||
mapboxMap.removePolygon(currentLocationPolygon);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
mapboxMap.addMarkers(baseMarkerList);
|
||||
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 {
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,884 @@
|
|||
package fr.free.nrw.commons.nearby.fragments;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Bundle;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.animation.Animation;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.widget.Button;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
|
||||
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.Mapbox;
|
||||
import com.mapbox.mapboxsdk.annotations.Marker;
|
||||
import com.mapbox.mapboxsdk.camera.CameraPosition;
|
||||
import com.mapbox.mapboxsdk.camera.CameraUpdateFactory;
|
||||
import com.mapbox.mapboxsdk.geometry.LatLng;
|
||||
import com.mapbox.mapboxsdk.maps.MapboxMap;
|
||||
import com.mapbox.mapboxsdk.maps.MapboxMapOptions;
|
||||
import com.mapbox.mapboxsdk.maps.Style;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import fr.free.nrw.commons.CommonsApplication;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.Utils;
|
||||
import fr.free.nrw.commons.auth.LoginActivity;
|
||||
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao;
|
||||
import fr.free.nrw.commons.contributions.ContributionController;
|
||||
import fr.free.nrw.commons.contributions.MainActivity;
|
||||
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
|
||||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||
import fr.free.nrw.commons.location.LocationServiceManager;
|
||||
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.NearbyParentFragmentContract;
|
||||
import fr.free.nrw.commons.nearby.presenter.NearbyParentFragmentPresenter;
|
||||
import fr.free.nrw.commons.utils.FragmentUtils;
|
||||
import fr.free.nrw.commons.utils.NearbyFABUtils;
|
||||
import fr.free.nrw.commons.utils.NetworkUtils;
|
||||
import fr.free.nrw.commons.utils.PermissionUtils;
|
||||
import fr.free.nrw.commons.utils.ViewUtil;
|
||||
import fr.free.nrw.commons.wikidata.WikidataEditListener;
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import timber.log.Timber;
|
||||
|
||||
import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED;
|
||||
import static fr.free.nrw.commons.contributions.MainActivity.CONTRIBUTIONS_TAB_POSITION;
|
||||
import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.MAP_UPDATED;
|
||||
import static fr.free.nrw.commons.wikidata.WikidataConstants.PLACE_OBJECT;
|
||||
|
||||
|
||||
public class NearbyParentFragment extends CommonsDaggerSupportFragment
|
||||
implements NearbyParentFragmentContract.View,
|
||||
WikidataEditListener.WikidataP18EditListener {
|
||||
|
||||
@BindView(R.id.bottom_sheet) View bottomSheetList;
|
||||
@BindView(R.id.bottom_sheet_details) View bottomSheetDetails;
|
||||
@BindView(R.id.transparentView) View transparentView;
|
||||
@BindView(R.id.directionsButtonText) TextView directionsButtonText;
|
||||
@BindView(R.id.wikipediaButtonText) TextView wikipediaButtonText;
|
||||
@BindView(R.id.wikidataButtonText) TextView wikidataButtonText;
|
||||
@BindView(R.id.commonsButtonText) TextView commonsButtonText;
|
||||
@BindView(R.id.fab_plus) FloatingActionButton fabPlus;
|
||||
@BindView(R.id.fab_camera) FloatingActionButton fabCamera;
|
||||
@BindView(R.id.fab_gallery) FloatingActionButton fabGallery;
|
||||
@BindView(R.id.fab_recenter) FloatingActionButton fabRecenter;
|
||||
@BindView(R.id.bookmarkButtonImage) ImageView bookmarkButtonImage;
|
||||
@BindView(R.id.bookmarkButton) LinearLayout bookmarkButton;
|
||||
@BindView(R.id.wikipediaButton) LinearLayout wikipediaButton;
|
||||
@BindView(R.id.wikidataButton) LinearLayout wikidataButton;
|
||||
@BindView(R.id.directionsButton) LinearLayout directionsButton;
|
||||
@BindView(R.id.commonsButton) LinearLayout commonsButton;
|
||||
@BindView(R.id.description) TextView description;
|
||||
@BindView(R.id.title) TextView title;
|
||||
@BindView(R.id.category) TextView distance;
|
||||
@BindView(R.id.icon) ImageView icon;
|
||||
@BindView(R.id.search_this_area_button) Button searchThisAreaButton;
|
||||
@BindView(R.id.map_progress_bar) ProgressBar progressBar;
|
||||
@BindView(R.id.container_sheet) FrameLayout frameLayout;
|
||||
@BindView(R.id.loading_nearby_list) ConstraintLayout loadingNearbyLayout;
|
||||
|
||||
@Inject LocationServiceManager locationManager;
|
||||
@Inject NearbyController nearbyController;
|
||||
@Inject @Named("default_preferences") JsonKvStore applicationKvStore;
|
||||
@Inject BookmarkLocationsDao bookmarkLocationDao;
|
||||
@Inject ContributionController controller;
|
||||
@Inject WikidataEditListener wikidataEditListener;
|
||||
|
||||
private BottomSheetBehavior bottomSheetListBehavior;
|
||||
private BottomSheetBehavior bottomSheetDetailsBehavior;
|
||||
private Animation rotate_backward;
|
||||
private Animation fab_close;
|
||||
private Animation fab_open;
|
||||
private Animation rotate_forward;
|
||||
|
||||
private static final double ZOOM_LEVEL = 14f;
|
||||
private final String NETWORK_INTENT_ACTION = "android.net.conn.CONNECTIVITY_CHANGE";
|
||||
private BroadcastReceiver broadcastReceiver;
|
||||
private boolean isNetworkErrorOccurred = false;
|
||||
private Snackbar snackbar;
|
||||
private View view;
|
||||
private NearbyParentFragmentPresenter nearbyParentFragmentPresenter;
|
||||
private boolean isDarkTheme;
|
||||
private boolean isFABsExpanded;
|
||||
private Marker selectedMarker;
|
||||
private Place selectedPlace;
|
||||
|
||||
private final double CAMERA_TARGET_SHIFT_FACTOR_PORTRAIT = 0.005;
|
||||
private final double CAMERA_TARGET_SHIFT_FACTOR_LANDSCAPE = 0.004;
|
||||
|
||||
private NearbyMapFragment nearbyMapFragment;
|
||||
private NearbyListFragment nearbyListFragment;
|
||||
|
||||
public static final String TAG_RETAINED_MAP_FRAGMENT = NearbyMapFragment.class.getSimpleName();
|
||||
private static final String TAG_RETAINED_LIST_FRAGMENT = NearbyListFragment.class.getSimpleName();
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
view = inflater.inflate(R.layout.fragment_nearby_parent, container, false);
|
||||
ButterKnife.bind(this, view);
|
||||
// Inflate the layout for this fragment
|
||||
return view;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
nearbyMapFragment = getNearbyMapFragment();
|
||||
nearbyListFragment = getListFragment();
|
||||
}
|
||||
|
||||
|
||||
private void initViews() {
|
||||
Timber.d("init views called");
|
||||
initBottomSheets();
|
||||
loadAnimations();
|
||||
setBottomSheetCallbacks();
|
||||
decideButtonVisibilities();
|
||||
addActionToTitle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates bottom sheet behaviours from bottom sheets, sets initial states and visibility
|
||||
*/
|
||||
private void initBottomSheets() {
|
||||
bottomSheetListBehavior = BottomSheetBehavior.from(bottomSheetList);
|
||||
bottomSheetDetailsBehavior = BottomSheetBehavior.from(bottomSheetDetails);
|
||||
bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
|
||||
bottomSheetDetails.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines how bottom sheets will act on click
|
||||
*/
|
||||
private void setBottomSheetCallbacks() {
|
||||
bottomSheetDetailsBehavior.setBottomSheetCallback(new BottomSheetBehavior
|
||||
.BottomSheetCallback() {
|
||||
@Override
|
||||
public void onStateChanged(@NonNull View bottomSheet, int newState) {
|
||||
prepareViewsForSheetPosition(newState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSlide(@NonNull View bottomSheet, float slideOffset) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
||||
bottomSheetList.getLayoutParams().height = getActivity().getWindowManager()
|
||||
.getDefaultDisplay().getHeight() / 16 * 9;
|
||||
bottomSheetListBehavior = BottomSheetBehavior.from(bottomSheetList);
|
||||
|
||||
bottomSheetListBehavior.setBottomSheetCallback(new BottomSheetBehavior
|
||||
.BottomSheetCallback() {
|
||||
@Override
|
||||
public void onStateChanged(@NonNull View bottomSheet, int newState) {
|
||||
if (newState == BottomSheetBehavior.STATE_EXPANDED) {
|
||||
bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSlide(@NonNull View bottomSheet, float slideOffset) {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads animations will be used for FABs
|
||||
*/
|
||||
private void loadAnimations() {
|
||||
fab_open = AnimationUtils.loadAnimation(getActivity(), R.anim.fab_open);
|
||||
fab_close = AnimationUtils.loadAnimation(getActivity(), R.anim.fab_close);
|
||||
rotate_forward = AnimationUtils.loadAnimation(getActivity(), R.anim.rotate_forward);
|
||||
rotate_backward = AnimationUtils.loadAnimation(getActivity(), R.anim.rotate_backward);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fits buttons according to our layout
|
||||
*/
|
||||
private void decideButtonVisibilities() {
|
||||
// Remove button text if they exceed 1 line or if internal layout has not been built
|
||||
// Only need to check for directions button because it is the longest
|
||||
if (directionsButtonText.getLineCount() > 1 || directionsButtonText.getLineCount() == 0) {
|
||||
wikipediaButtonText.setVisibility(View.GONE);
|
||||
wikidataButtonText.setVisibility(View.GONE);
|
||||
commonsButtonText.setVisibility(View.GONE);
|
||||
directionsButtonText.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private void addActionToTitle() {
|
||||
title.setOnClickListener(view -> {
|
||||
Utils.copy("place", title.getText().toString(), getContext());
|
||||
Toast.makeText(getContext(), "Text copied to clipboard", Toast.LENGTH_SHORT).show();
|
||||
if (bottomSheetDetailsBehavior.getState() == BottomSheetBehavior.STATE_COLLAPSED) {
|
||||
bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
|
||||
} else {
|
||||
bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list fragment added to child fragment manager previously, if exists.
|
||||
*/
|
||||
private NearbyListFragment getListFragment() {
|
||||
NearbyListFragment existingFragment = (NearbyListFragment) getChildFragmentManager()
|
||||
.findFragmentByTag(TAG_RETAINED_LIST_FRAGMENT);
|
||||
if (existingFragment == null) {
|
||||
existingFragment = setListFragment();
|
||||
}
|
||||
return existingFragment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the map fragment added to child fragment manager previously, if exists.
|
||||
*/
|
||||
private NearbyMapFragment getNearbyMapFragment() {
|
||||
NearbyMapFragment existingFragment = (NearbyMapFragment) getChildFragmentManager()
|
||||
.findFragmentByTag(TAG_RETAINED_MAP_FRAGMENT);
|
||||
if (existingFragment == null) {
|
||||
existingFragment = setMapFragment();
|
||||
}
|
||||
return existingFragment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the map fragment and prepares map
|
||||
* @return the map fragment created
|
||||
*/
|
||||
private NearbyMapFragment setMapFragment() {
|
||||
// Mapbox access token is configured here. This needs to be called either in your application
|
||||
// object or in the same activity which contains the mapview.
|
||||
Mapbox.getInstance(getActivity(), getString(R.string.mapbox_commons_app_token));
|
||||
NearbyMapFragment mapFragment;
|
||||
|
||||
// Create fragment
|
||||
FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
|
||||
|
||||
// Build mapboxMap
|
||||
isDarkTheme = applicationKvStore.getBoolean("theme", false);
|
||||
MapboxMapOptions options = new MapboxMapOptions()
|
||||
.compassGravity(Gravity.BOTTOM | Gravity.LEFT)
|
||||
.compassMargins(new int[]{12, 0, 0, 24})
|
||||
.logoEnabled(false)
|
||||
.attributionEnabled(false)
|
||||
.camera(new CameraPosition.Builder()
|
||||
.zoom(ZOOM_LEVEL)
|
||||
.target(new com.mapbox.mapboxsdk.geometry.LatLng(-52.6885, -70.1395))
|
||||
.build());
|
||||
|
||||
// Create map fragment
|
||||
mapFragment = NearbyMapFragment.newInstance(options);
|
||||
|
||||
// Add map fragment to parent container
|
||||
getChildFragmentManager().executePendingTransactions();
|
||||
transaction.add(R.id.container, mapFragment, TAG_RETAINED_MAP_FRAGMENT);
|
||||
transaction.commit();
|
||||
|
||||
mapFragment.getMapAsync(mapboxMap ->
|
||||
mapboxMap.setStyle(
|
||||
NearbyParentFragment.this.isDarkTheme ? Style.DARK : Style.OUTDOORS, style -> {
|
||||
NearbyParentFragment.this.childMapFragmentAttached();
|
||||
// Map is set up and the style has loaded. Now you can add data or make other map adjustments
|
||||
}));
|
||||
return mapFragment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the list fragment and put it into container
|
||||
* @return the list fragment created
|
||||
*/
|
||||
private NearbyListFragment setListFragment() {
|
||||
NearbyListFragment nearbyListFragment;
|
||||
loadingNearbyLayout.setVisibility(View.GONE);
|
||||
frameLayout.setVisibility(View.VISIBLE);
|
||||
FragmentTransaction fragmentTransaction = getChildFragmentManager().beginTransaction();
|
||||
nearbyListFragment = new NearbyListFragment();
|
||||
nearbyListFragment.setArguments(null);
|
||||
fragmentTransaction.replace(R.id.container_sheet, nearbyListFragment, TAG_RETAINED_LIST_FRAGMENT);
|
||||
fragmentTransaction.commitAllowingStateLoss();
|
||||
return nearbyListFragment;
|
||||
}
|
||||
|
||||
private void removeListFragment() {
|
||||
if (nearbyListFragment != null) {
|
||||
FragmentManager fm = getChildFragmentManager();
|
||||
fm.beginTransaction().remove(nearbyListFragment).commit();
|
||||
nearbyListFragment = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void removeMapFragment() {
|
||||
if (nearbyMapFragment != null) {
|
||||
FragmentManager fm = getChildFragmentManager();
|
||||
fm.beginTransaction().remove(nearbyMapFragment).commit();
|
||||
nearbyMapFragment = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls presenter to center map to any place
|
||||
* @param place the place we want to center map
|
||||
*/
|
||||
public void centerMapToPlace(Place place) {
|
||||
if (nearbyMapFragment != null) {
|
||||
nearbyParentFragmentPresenter.centerMapToPlace(place,
|
||||
getActivity().getResources().getConfiguration().orientation ==
|
||||
Configuration.ORIENTATION_PORTRAIT);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Thanks to this method we make sure NearbyMapFragment is ready and attached. So that we can
|
||||
* prevent NPE caused by null child fragment. This method is called from child fragment when
|
||||
* it is attached.
|
||||
*/
|
||||
private void childMapFragmentAttached() {
|
||||
Timber.d("Child map fragment attached");
|
||||
nearbyParentFragmentPresenter = NearbyParentFragmentPresenter.getInstance
|
||||
(nearbyListFragment,this, nearbyMapFragment, locationManager);
|
||||
nearbyParentFragmentPresenter.nearbyFragmentsAreReady();
|
||||
initViews();
|
||||
nearbyParentFragmentPresenter.setActionListeners(applicationKvStore);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addOnCameraMoveListener(MapboxMap.OnCameraMoveListener onCameraMoveListener) {
|
||||
nearbyMapFragment.getMapboxMap().addOnCameraMoveListener(onCameraMoveListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerLocationUpdates(LocationServiceManager locationManager) {
|
||||
locationManager.registerLocationManager();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNetworkConnectionEstablished() {
|
||||
return NetworkUtils.isInternetConnectionEstablished(getActivity());
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds network broadcast receiver to recognize connection established
|
||||
*/
|
||||
@Override
|
||||
public void addNetworkBroadcastReceiver() {
|
||||
if (!FragmentUtils.isFragmentUIActive(this)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (broadcastReceiver != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
IntentFilter intentFilter = new IntentFilter(NETWORK_INTENT_ACTION);
|
||||
|
||||
broadcastReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (getActivity() != null) {
|
||||
if (NetworkUtils.isInternetConnectionEstablished(getActivity())) {
|
||||
if (isNetworkErrorOccurred) {
|
||||
nearbyParentFragmentPresenter.updateMapAndList(LOCATION_SIGNIFICANTLY_CHANGED, null);
|
||||
isNetworkErrorOccurred = false;
|
||||
}
|
||||
|
||||
if (snackbar != null) {
|
||||
snackbar.dismiss();
|
||||
snackbar = null;
|
||||
}
|
||||
} else {
|
||||
if (snackbar == null) {
|
||||
snackbar = Snackbar.make(view, R.string.no_internet, Snackbar.LENGTH_INDEFINITE);
|
||||
setSearchThisAreaButtonVisibility(false);
|
||||
setProgressBarVisibility(false);
|
||||
}
|
||||
|
||||
isNetworkErrorOccurred = true;
|
||||
snackbar.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
getActivity().registerReceiver(broadcastReceiver, intentFilter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide or expand bottom sheet according to states of all sheets
|
||||
*/
|
||||
@Override
|
||||
public void listOptionMenuItemClicked() {
|
||||
if(bottomSheetListBehavior.getState()== BottomSheetBehavior.STATE_COLLAPSED || bottomSheetListBehavior.getState()==BottomSheetBehavior.STATE_HIDDEN){
|
||||
bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
|
||||
bottomSheetListBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
|
||||
}else if(bottomSheetListBehavior.getState()==BottomSheetBehavior.STATE_EXPANDED){
|
||||
bottomSheetListBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void populatePlaces(fr.free.nrw.commons.location.LatLng curlatLng, fr.free.nrw.commons.location.LatLng searchLatLng) {
|
||||
if (curlatLng.equals(searchLatLng)) { // Means we are checking around current location
|
||||
populatePlacesForCurrentLocation(curlatLng, searchLatLng);
|
||||
} else {
|
||||
populatePlacesForAnotherLocation(curlatLng, searchLatLng);
|
||||
}
|
||||
}
|
||||
|
||||
private void populatePlacesForCurrentLocation(fr.free.nrw.commons.location.LatLng curlatLng,
|
||||
fr.free.nrw.commons.location.LatLng searchLatLng) {
|
||||
compositeDisposable.add(Observable.fromCallable(() -> nearbyController
|
||||
.loadAttractionsFromLocation(curlatLng, searchLatLng, false, true))
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(this::updateMapMarkers,
|
||||
throwable -> {
|
||||
Timber.d(throwable);
|
||||
// TODO: find out why NPE occurs here
|
||||
// showErrorMessage(getString(R.string.error_fetching_nearby_places));
|
||||
setProgressBarVisibility(false);
|
||||
nearbyParentFragmentPresenter.lockUnlockNearby(false);
|
||||
}));
|
||||
}
|
||||
|
||||
private void populatePlacesForAnotherLocation(fr.free.nrw.commons.location.LatLng curlatLng,
|
||||
fr.free.nrw.commons.location.LatLng searchLatLng) {
|
||||
compositeDisposable.add(Observable.fromCallable(() -> nearbyController
|
||||
.loadAttractionsFromLocation(curlatLng, searchLatLng, false, false))
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(this::updateMapMarkersForCustomLocation,
|
||||
throwable -> {
|
||||
Timber.d(throwable);
|
||||
// TODO: find out why NPE occurs here
|
||||
// showErrorMessage(getString(R.string.error_fetching_nearby_places));
|
||||
setProgressBarVisibility(false);
|
||||
nearbyParentFragmentPresenter.lockUnlockNearby(false);
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates places for your location, should be used for finding nearby places around a
|
||||
* location where you are.
|
||||
* @param nearbyPlacesInfo This variable has place list information and distances.
|
||||
*/
|
||||
private void updateMapMarkers(NearbyController.NearbyPlacesInfo nearbyPlacesInfo) {
|
||||
nearbyParentFragmentPresenter.updateMapMarkers(nearbyPlacesInfo, selectedMarker);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 place list information and distances.
|
||||
*/
|
||||
private void updateMapMarkersForCustomLocation(NearbyController.NearbyPlacesInfo nearbyPlacesInfo) {
|
||||
nearbyParentFragmentPresenter.updateMapMarkersForCustomLocation(nearbyPlacesInfo, selectedMarker);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isListBottomSheetExpanded() {
|
||||
return bottomSheetListBehavior.getState() == BottomSheetBehavior.STATE_EXPANDED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDetailsBottomSheetVisible() {
|
||||
return !(bottomSheetDetailsBehavior.getState() == BottomSheetBehavior.STATE_HIDDEN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBottomSheetDetailsSmaller() {
|
||||
if (bottomSheetDetailsBehavior.getState() == BottomSheetBehavior.STATE_EXPANDED) {
|
||||
bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
|
||||
} else {
|
||||
bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addSearchThisAreaButtonAction() {
|
||||
searchThisAreaButton.setOnClickListener(nearbyParentFragmentPresenter.onSearchThisAreaClicked());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSearchThisAreaButtonVisibility(boolean isVisible) {
|
||||
if (isVisible) {
|
||||
searchThisAreaButton.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
searchThisAreaButton.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSearchThisAreaButtonVisible() {
|
||||
if (searchThisAreaButton.getVisibility() == View.VISIBLE) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProgressBarVisibility(boolean isVisible) {
|
||||
if (isVisible) {
|
||||
progressBar.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
progressBar.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTabItemContributions() {
|
||||
((MainActivity)getActivity()).viewPager.setCurrentItem(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkPermissionsAndPerformAction(Runnable runnable) {
|
||||
Timber.d("Checking permission and perfoming action");
|
||||
PermissionUtils.checkPermissionsAndPerformAction(getActivity(),
|
||||
Manifest.permission.ACCESS_FINE_LOCATION,
|
||||
runnable,
|
||||
() -> ((MainActivity) getActivity()).viewPager.setCurrentItem(CONTRIBUTIONS_TAB_POSITION),
|
||||
R.string.location_permission_title,
|
||||
R.string.location_permission_rationale_nearby);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts animation of fab plus (turning on opening) and other FABs
|
||||
*/
|
||||
@Override
|
||||
public void animateFABs() {
|
||||
if (fabPlus.isShown()){
|
||||
if (isFABsExpanded) {
|
||||
collapseFABs(isFABsExpanded);
|
||||
} else {
|
||||
expandFABs(isFABsExpanded);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void showFABs() {
|
||||
NearbyFABUtils.addAnchorToBigFABs(fabPlus, bottomSheetDetails.getId());
|
||||
fabPlus.show();
|
||||
NearbyFABUtils.addAnchorToSmallFABs(fabGallery, getView().findViewById(R.id.empty_view).getId());
|
||||
NearbyFABUtils.addAnchorToSmallFABs(fabCamera, getView().findViewById(R.id.empty_view1).getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Expands camera and gallery FABs, turn forward plus FAB
|
||||
* @param isFABsExpanded true if they are already expanded
|
||||
*/
|
||||
private void expandFABs(boolean isFABsExpanded){
|
||||
if (!isFABsExpanded) {
|
||||
showFABs();
|
||||
fabPlus.startAnimation(rotate_forward);
|
||||
fabCamera.startAnimation(fab_open);
|
||||
fabGallery.startAnimation(fab_open);
|
||||
fabCamera.show();
|
||||
fabGallery.show();
|
||||
this.isFABsExpanded = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides all fabs
|
||||
*/
|
||||
private void hideFABs() {
|
||||
NearbyFABUtils.removeAnchorFromFAB(fabPlus);
|
||||
fabPlus.hide();
|
||||
NearbyFABUtils.removeAnchorFromFAB(fabCamera);
|
||||
fabCamera.hide();
|
||||
NearbyFABUtils.removeAnchorFromFAB(fabGallery);
|
||||
fabGallery.hide();
|
||||
}
|
||||
|
||||
/**
|
||||
* Collapses camera and gallery FABs, turn back plus FAB
|
||||
* @param isFABsExpanded
|
||||
*/
|
||||
private void collapseFABs(boolean isFABsExpanded){
|
||||
if (isFABsExpanded) {
|
||||
fabPlus.startAnimation(rotate_backward);
|
||||
fabCamera.startAnimation(fab_close);
|
||||
fabGallery.startAnimation(fab_close);
|
||||
fabCamera.hide();
|
||||
fabGallery.hide();
|
||||
this.isFABsExpanded = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void displayLoginSkippedWarning() {
|
||||
if (applicationKvStore.getBoolean("login_skipped", false)) {
|
||||
// prompt the user to login
|
||||
new AlertDialog.Builder(getContext())
|
||||
.setMessage(R.string.login_alert_message)
|
||||
.setPositiveButton(R.string.login, (dialog, which) -> {
|
||||
// logout of the app
|
||||
BaseLogoutListener logoutListener = new BaseLogoutListener();
|
||||
CommonsApplication app = (CommonsApplication) getActivity().getApplication();
|
||||
app.clearApplicationData(getContext(), logoutListener);
|
||||
})
|
||||
.show();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* onLogoutComplete is called after shared preferences and data stored in local database are cleared.
|
||||
*/
|
||||
private class BaseLogoutListener implements CommonsApplication.LogoutListener {
|
||||
@Override
|
||||
public void onLogoutComplete() {
|
||||
Timber.d("Logout complete callback received.");
|
||||
Intent nearbyIntent = new Intent( getActivity(), LoginActivity.class);
|
||||
nearbyIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||
nearbyIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(nearbyIntent);
|
||||
getActivity().finish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFABPlusAction(View.OnClickListener onClickListener) {
|
||||
fabPlus.setOnClickListener(onClickListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFABRecenterAction(View.OnClickListener onClickListener) {
|
||||
fabRecenter.setOnClickListener(onClickListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recenterMap(fr.free.nrw.commons.location.LatLng curLatLng) {
|
||||
nearbyMapFragment.removeCurrentLocationMarker();
|
||||
nearbyMapFragment.addCurrentLocationMarker(curLatLng);
|
||||
CameraPosition position;
|
||||
|
||||
if (ViewUtil.isPortrait(getActivity())){
|
||||
position = new CameraPosition.Builder()
|
||||
.target(isListBottomSheetExpanded() ?
|
||||
new LatLng(curLatLng.getLatitude() - CAMERA_TARGET_SHIFT_FACTOR_PORTRAIT,
|
||||
curLatLng.getLongitude())
|
||||
: new LatLng(curLatLng.getLatitude(), curLatLng.getLongitude(), 0)) // Sets the new camera position
|
||||
.zoom(isListBottomSheetExpanded() ?
|
||||
ZOOM_LEVEL
|
||||
: nearbyMapFragment.getMapboxMap().getCameraPosition().zoom) // Same zoom level
|
||||
.build();
|
||||
}else {
|
||||
position = new CameraPosition.Builder()
|
||||
.target(isListBottomSheetExpanded() ?
|
||||
new LatLng(curLatLng.getLatitude() - CAMERA_TARGET_SHIFT_FACTOR_LANDSCAPE,
|
||||
curLatLng.getLongitude())
|
||||
: new LatLng(curLatLng.getLatitude(), curLatLng.getLongitude(), 0)) // Sets the new camera position
|
||||
.zoom(isListBottomSheetExpanded() ?
|
||||
ZOOM_LEVEL
|
||||
: nearbyMapFragment.getMapboxMap().getCameraPosition().zoom) // Same zoom level
|
||||
.build();
|
||||
}
|
||||
|
||||
nearbyMapFragment.getMapboxMap().animateCamera(CameraUpdateFactory.newCameraPosition(position), 1000);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hideBottomSheet() {
|
||||
bottomSheetListBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void displayBottomSheetWithInfo(Marker marker) {
|
||||
this.selectedMarker = marker;
|
||||
NearbyMarker nearbyMarker = (NearbyMarker) marker;
|
||||
Place place = nearbyMarker.getNearbyBaseMarker().getPlace();
|
||||
passInfoToSheet(place);
|
||||
hideBottomSheet();
|
||||
bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
|
||||
}
|
||||
|
||||
/**
|
||||
* If nearby details bottom sheet state is collapsed: show fab plus
|
||||
* If nearby details bottom sheet state is expanded: show fab plus
|
||||
* If nearby details bottom sheet state is hidden: hide all fabs
|
||||
* @param bottomSheetState see bottom sheet states
|
||||
*/
|
||||
public void prepareViewsForSheetPosition(int bottomSheetState) {
|
||||
|
||||
switch (bottomSheetState) {
|
||||
case (BottomSheetBehavior.STATE_COLLAPSED):
|
||||
collapseFABs(isFABsExpanded);
|
||||
if (!fabPlus.isShown()) showFABs();
|
||||
this.getView().requestFocus();
|
||||
break;
|
||||
case (BottomSheetBehavior.STATE_EXPANDED):
|
||||
this.getView().requestFocus();
|
||||
break;
|
||||
case (BottomSheetBehavior.STATE_HIDDEN):
|
||||
nearbyMapFragment.getMapboxMap().deselectMarkers();
|
||||
transparentView.setClickable(false);
|
||||
transparentView.setAlpha(0);
|
||||
collapseFABs(isFABsExpanded);
|
||||
hideFABs();
|
||||
if (this.getView() != null) {
|
||||
this.getView().requestFocus();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(Place place) {
|
||||
this.selectedPlace = place;
|
||||
updateBookmarkButtonImage(this.selectedPlace);
|
||||
|
||||
bookmarkButton.setOnClickListener(view -> {
|
||||
boolean isBookmarked = bookmarkLocationDao.updateBookmarkLocation(this.selectedPlace);
|
||||
updateBookmarkButtonImage(this.selectedPlace);
|
||||
nearbyMapFragment.updateMarker(isBookmarked, this.selectedPlace, locationManager.getLastLocation());
|
||||
});
|
||||
|
||||
wikipediaButton.setVisibility(place.hasWikipediaLink()?View.VISIBLE:View.GONE);
|
||||
wikipediaButton.setOnClickListener(view -> Utils.handleWebUrl(getContext(), this.selectedPlace.siteLinks.getWikipediaLink()));
|
||||
|
||||
wikidataButton.setVisibility(place.hasWikidataLink()?View.VISIBLE:View.GONE);
|
||||
wikidataButton.setOnClickListener(view -> Utils.handleWebUrl(getContext(), this.selectedPlace.siteLinks.getWikidataLink()));
|
||||
|
||||
directionsButton.setOnClickListener(view -> Utils.handleGeoCoordinates(getActivity(), this.selectedPlace.getLocation()));
|
||||
|
||||
commonsButton.setVisibility(this.selectedPlace.hasCommonsLink()?View.VISIBLE:View.GONE);
|
||||
commonsButton.setOnClickListener(view -> Utils.handleWebUrl(getContext(), this.selectedPlace.siteLinks.getCommonsLink()));
|
||||
|
||||
icon.setImageResource(this.selectedPlace.getLabel().getIcon());
|
||||
|
||||
title.setText(this.selectedPlace.name);
|
||||
distance.setText(this.selectedPlace.distance);
|
||||
description.setText(this.selectedPlace.getLongDescription());
|
||||
|
||||
fabCamera.setOnClickListener(view -> {
|
||||
if (fabCamera.isShown()) {
|
||||
Timber.d("Camera button tapped. Place: %s", this.selectedPlace.toString());
|
||||
storeSharedPrefs(selectedPlace);
|
||||
controller.initiateCameraPick(getActivity());
|
||||
}
|
||||
});
|
||||
|
||||
fabGallery.setOnClickListener(view -> {
|
||||
if (fabGallery.isShown()) {
|
||||
Timber.d("Gallery button tapped. Place: %s", this.selectedPlace.toString());
|
||||
storeSharedPrefs(selectedPlace);
|
||||
controller.initiateGalleryPick(getActivity(), false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void storeSharedPrefs(Place selectedPlace) {
|
||||
Timber.d("Store place object %s", selectedPlace.toString());
|
||||
applicationKvStore.putJson(PLACE_OBJECT, selectedPlace);
|
||||
}
|
||||
|
||||
private void updateBookmarkButtonImage(Place place) {
|
||||
int bookmarkIcon;
|
||||
if (bookmarkLocationDao.findBookmarkLocation(place)) {
|
||||
bookmarkIcon = R.drawable.ic_round_star_filled_24px;
|
||||
} else {
|
||||
bookmarkIcon = R.drawable.ic_round_star_border_24px;
|
||||
}
|
||||
if (bookmarkButtonImage != null) {
|
||||
bookmarkButtonImage.setImageResource(bookmarkIcon);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
wikidataEditListener.setAuthenticationStateListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
wikidataEditListener.setAuthenticationStateListener(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
// this means that this activity will not be recreated now, user is leaving it
|
||||
// or the activity is otherwise finishing
|
||||
if(getActivity().isFinishing()) {
|
||||
// we will not need this fragment anymore, this may also be a good place to signal
|
||||
// to the retained fragment object to perform its own cleanup.
|
||||
removeMapFragment();
|
||||
removeListFragment();
|
||||
|
||||
}
|
||||
if (broadcastReceiver != null) {
|
||||
getActivity().unregisterReceiver(broadcastReceiver);
|
||||
broadcastReceiver = null;
|
||||
}
|
||||
|
||||
if (locationManager != null && nearbyParentFragmentPresenter != null) {
|
||||
locationManager.removeLocationListener(nearbyParentFragmentPresenter);
|
||||
locationManager.unregisterLocationManager();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWikidataEditSuccessful() {
|
||||
if (nearbyMapFragment != null && nearbyParentFragmentPresenter != null && locationManager != null) {
|
||||
nearbyParentFragmentPresenter.updateMapAndList(MAP_UPDATED, locationManager.getLastLocation());
|
||||
}
|
||||
}
|
||||
|
||||
private void showErrorMessage(String message) {
|
||||
ViewUtil.showLongToast(getActivity(), message);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,391 @@
|
|||
package fr.free.nrw.commons.nearby.presenter;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import com.mapbox.mapboxsdk.annotations.Marker;
|
||||
import com.mapbox.mapboxsdk.maps.MapboxMap;
|
||||
|
||||
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.NearbyController;
|
||||
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.utils.LocationUtils;
|
||||
|
||||
import fr.free.nrw.commons.wikidata.WikidataEditListener;
|
||||
import timber.log.Timber;
|
||||
|
||||
import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED;
|
||||
import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.LOCATION_SLIGHTLY_CHANGED;
|
||||
import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.MAP_UPDATED;
|
||||
import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.SEARCH_CUSTOM_AREA;
|
||||
|
||||
public class NearbyParentFragmentPresenter
|
||||
implements NearbyParentFragmentContract.UserActions,
|
||||
WikidataEditListener.WikidataP18EditListener,
|
||||
LocationUpdateListener,
|
||||
NearbyParentFragmentContract.ViewsAreReadyCallback{
|
||||
|
||||
|
||||
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;
|
||||
|
||||
public static NearbyParentFragmentPresenter presenterInstance;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* -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 initializeMapOperations() {
|
||||
lockUnlockNearby(false);
|
||||
registerUnregisterLocationListener(false);
|
||||
nearbyParentFragmentView.addNetworkBroadcastReceiver();
|
||||
updateMapAndList(LOCATION_SIGNIFICANTLY_CHANGED, null);
|
||||
this.nearbyParentFragmentView.addSearchThisAreaButtonAction();
|
||||
this.nearbyMapFragmentView.addOnCameraMoveListener(onCameraMove(getMapboxMap()));
|
||||
mapInitialized = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets click listeners of FABs, and 2 bottom sheets
|
||||
*/
|
||||
@Override
|
||||
public void setActionListeners(JsonKvStore applicationKvStore) {
|
||||
nearbyParentFragmentView.setFABPlusAction(v -> {
|
||||
if (applicationKvStore.getBoolean("login_skipped", false)) {
|
||||
// prompt the user to login
|
||||
nearbyParentFragmentView.displayLoginSkippedWarning();
|
||||
}else {
|
||||
nearbyParentFragmentView.animateFABs();
|
||||
}
|
||||
});
|
||||
|
||||
nearbyParentFragmentView.setFABRecenterAction(v -> {
|
||||
nearbyParentFragmentView.recenterMap(curLatLng);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void backButtonClicked() {
|
||||
if(nearbyParentFragmentView.isListBottomSheetExpanded()) {
|
||||
// Back should first hide the bottom sheet if it is expanded
|
||||
nearbyParentFragmentView.listOptionMenuItemClicked();
|
||||
} else if (nearbyParentFragmentView.isDetailsBottomSheetVisible()) {
|
||||
nearbyParentFragmentView.setBottomSheetDetailsSmaller();
|
||||
} else {
|
||||
// Otherwise go back to contributions fragment
|
||||
nearbyParentFragmentView.setTabItemContributions();
|
||||
}
|
||||
}
|
||||
|
||||
public void markerUnselected() {
|
||||
nearbyParentFragmentView.hideBottomSheet();
|
||||
}
|
||||
|
||||
|
||||
public void markerSelected(Marker marker) {
|
||||
nearbyParentFragmentView.displayBottomSheetWithInfo(marker);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
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) {
|
||||
Timber.d("Presenter updates map and list");
|
||||
if (isNearbyLocked) {
|
||||
Timber.d("Nearby is locked, so updateMapAndList returns");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!nearbyParentFragmentView.isNetworkConnectionEstablished()) {
|
||||
Timber.d("Network connection is not established");
|
||||
return;
|
||||
}
|
||||
|
||||
LatLng lastLocation = locationServiceManager.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)
|
||||
|| locationChangeType.equals(MAP_UPDATED)) {
|
||||
Timber.d("LOCATION_SIGNIFICANTLY_CHANGED");
|
||||
lockUnlockNearby(true);
|
||||
nearbyParentFragmentView.setProgressBarVisibility(true);
|
||||
nearbyParentFragmentView.populatePlaces(lastLocation, lastLocation);
|
||||
|
||||
} else if (locationChangeType.equals(SEARCH_CUSTOM_AREA)) {
|
||||
Timber.d("SEARCH_CUSTOM_AREA");
|
||||
lockUnlockNearby(true);
|
||||
nearbyParentFragmentView.setProgressBarVisibility(true);
|
||||
nearbyParentFragmentView.populatePlaces(lastLocation, cameraTarget);
|
||||
|
||||
} 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
|
||||
nearbyParentFragmentView.recenterMap(curLatLng);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 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();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
nearbyMapFragmentView.centerMapToPlace(placeToCenter, isPortraitMode);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWikidataEditSuccessful() {
|
||||
updateMapAndList(MAP_UPDATED, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLocationChangedSignificantly(LatLng latLng) {
|
||||
Timber.d("Location significantly changed");
|
||||
updateMapAndList(LOCATION_SIGNIFICANTLY_CHANGED, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLocationChangedSlightly(LatLng latLng) {
|
||||
Timber.d("Location significantly changed");
|
||||
updateMapAndList(LOCATION_SLIGHTLY_CHANGED, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLocationChangedMedium(LatLng latLng) {
|
||||
Timber.d("Location changed medium");
|
||||
}
|
||||
|
||||
@Override
|
||||
public MapboxMap.OnCameraMoveListener onCameraMove(MapboxMap mapboxMap) {
|
||||
return () -> {
|
||||
// If our nearby markers are calculated at least once
|
||||
if (NearbyController.currentLocation != null) {
|
||||
double distance = mapboxMap.getCameraPosition().target.distanceTo
|
||||
(LocationUtils.commonsLatLngToMapBoxLatLng(NearbyController.latestSearchLocation));
|
||||
if (nearbyParentFragmentView.isNetworkConnectionEstablished()) {
|
||||
if (distance > NearbyController.latestSearchRadius) {
|
||||
nearbyParentFragmentView.setSearchThisAreaButtonVisibility(true);
|
||||
} else {
|
||||
nearbyParentFragmentView.setSearchThisAreaButtonVisibility(false);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
nearbyParentFragmentView.setSearchThisAreaButtonVisibility(false);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public View.OnClickListener onSearchThisAreaClicked() {
|
||||
return v -> {
|
||||
// Lock map operations during search this area operation
|
||||
nearbyParentFragmentView.setSearchThisAreaButtonVisibility(false);
|
||||
|
||||
if (searchCloseToCurrentLocation()){
|
||||
updateMapAndList(LOCATION_SIGNIFICANTLY_CHANGED,
|
||||
null);
|
||||
} else {
|
||||
updateMapAndList(SEARCH_CUSTOM_AREA,
|
||||
getCameraTarget());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
double distance = LocationUtils.commonsLatLngToMapBoxLatLng(getCameraTarget())
|
||||
.distanceTo(new com.mapbox.mapboxsdk.geometry.LatLng(NearbyController.currentLocation.getLatitude()
|
||||
, NearbyController.currentLocation.getLongitude()));
|
||||
if (distance > NearbyController.currentLocationSearchRadius * 3 / 4) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
} else {
|
||||
this.isPortraitMode = isPortraitMode;
|
||||
this.placeToCenter = place;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -18,7 +18,6 @@ import androidx.appcompat.widget.Toolbar;
|
|||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
|
@ -39,7 +38,6 @@ import fr.free.nrw.commons.contributions.MainActivity;
|
|||
import fr.free.nrw.commons.explore.categories.ExploreActivity;
|
||||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||
import fr.free.nrw.commons.logging.CommonsLogSender;
|
||||
import fr.free.nrw.commons.notification.NotificationActivity;
|
||||
import fr.free.nrw.commons.review.ReviewActivity;
|
||||
import fr.free.nrw.commons.settings.SettingsActivity;
|
||||
import timber.log.Timber;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,51 @@
|
|||
package fr.free.nrw.commons.utils;
|
||||
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout;
|
||||
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
|
||||
public class NearbyFABUtils {
|
||||
/*
|
||||
* Add anchors back before making them visible again.
|
||||
* */
|
||||
public static void addAnchorToBigFABs(FloatingActionButton floatingActionButton, int anchorID) {
|
||||
CoordinatorLayout.LayoutParams params = new CoordinatorLayout.LayoutParams
|
||||
(ViewGroup.LayoutParams.WRAP_CONTENT,ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
params.setAnchorId(anchorID);
|
||||
params.anchorGravity = Gravity.TOP|Gravity.RIGHT|Gravity.END;
|
||||
floatingActionButton.setLayoutParams(params);
|
||||
}
|
||||
|
||||
/*
|
||||
* Add anchors back before making them visible again. Big and small fabs have different anchor
|
||||
* gravities, therefore the are two methods.
|
||||
* */
|
||||
public static void addAnchorToSmallFABs(FloatingActionButton floatingActionButton, int anchorID) {
|
||||
CoordinatorLayout.LayoutParams params = new CoordinatorLayout.LayoutParams
|
||||
(ViewGroup.LayoutParams.WRAP_CONTENT,ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
params.setAnchorId(anchorID);
|
||||
params.anchorGravity = Gravity.CENTER_HORIZONTAL;
|
||||
floatingActionButton.setLayoutParams(params);
|
||||
}
|
||||
|
||||
/*
|
||||
* We are not able to hide FABs without removing anchors, this method removes anchors
|
||||
* */
|
||||
public static void removeAnchorFromFAB(FloatingActionButton floatingActionButton) {
|
||||
//get rid of anchors
|
||||
//Somehow this was the only way https://stackoverflow.com/questions/32732932
|
||||
// /floatingactionbutton-visible-for-sometime-even-if-visibility-is-set-to-gone
|
||||
CoordinatorLayout.LayoutParams param = (CoordinatorLayout.LayoutParams) floatingActionButton
|
||||
.getLayoutParams();
|
||||
param.setAnchorId(View.NO_ID);
|
||||
// If we don't set them to zero, then they become visible for a moment on upper left side
|
||||
param.width = 0;
|
||||
param.height = 0;
|
||||
floatingActionButton.setLayoutParams(param);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -6,6 +6,11 @@ import android.graphics.Canvas;
|
|||
import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;
|
||||
import android.util.DisplayMetrics;
|
||||
|
||||
import com.mapbox.mapboxsdk.geometry.LatLng;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class UiUtils {
|
||||
|
||||
/**
|
||||
|
|
@ -43,4 +48,28 @@ public class UiUtils {
|
|||
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
|
||||
return px / ((float) metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a series of points that create a circle on the map.
|
||||
* Takes the center latitude, center longitude of the circle,
|
||||
* the radius in meter and the number of nodes of the circle.
|
||||
*
|
||||
* @return List List of LatLng points of the circle.
|
||||
*/
|
||||
public static List<com.mapbox.mapboxsdk.geometry.LatLng> createCircleArray(
|
||||
double centerLat, double centerLong, float radius, int nodes) {
|
||||
List<com.mapbox.mapboxsdk.geometry.LatLng> circle = new ArrayList<>();
|
||||
float radiusKilometer = radius / 1000;
|
||||
double radiusLong = radiusKilometer
|
||||
/ (111.320 * Math.cos(centerLat * Math.PI / 180));
|
||||
double radiusLat = radiusKilometer / 110.574;
|
||||
|
||||
for (int i = 0; i < nodes; i++) {
|
||||
double theta = ((double) i / (double) nodes) * (2 * Math.PI);
|
||||
double nodeLongitude = centerLong + radiusLong * Math.cos(theta);
|
||||
double nodeLatitude = centerLat + radiusLat * Math.sin(theta);
|
||||
circle.add(new com.mapbox.mapboxsdk.geometry.LatLng(nodeLatitude, nodeLongitude));
|
||||
}
|
||||
return circle;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,23 +11,14 @@
|
|||
android:background="@color/status_bar_blue">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_below="@id/toolbar"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
/>
|
||||
android:orientation="vertical" android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
|
|
@ -37,6 +28,7 @@
|
|||
android:layout_below="@id/toolbar"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:background="#aa969696"
|
||||
android:visibility="gone"
|
||||
android:elevation="6dp">
|
||||
|
||||
</View>
|
||||
|
|
@ -49,7 +41,7 @@
|
|||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:clickable="true"
|
||||
android:visibility="invisible"
|
||||
android:visibility="visible"
|
||||
app:backgroundTint="@color/main_background_light"
|
||||
app:elevation="6dp"
|
||||
app:fabSize="normal"
|
||||
|
|
@ -82,10 +74,11 @@
|
|||
app:elevation="6dp" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/search_this_area_button_progress_bar"
|
||||
android:id="@+id/map_progress_bar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
app:elevation="6dp"
|
||||
android:visibility="gone" />
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
|
|
@ -100,7 +93,7 @@
|
|||
android:clickable="true"
|
||||
android:visibility="invisible"
|
||||
app:backgroundTint="@color/button_blue"
|
||||
app:elevation="6dp"
|
||||
app:elevation="8dp"
|
||||
app:fabSize="normal"
|
||||
app:layout_anchor="@id/bottom_sheet_details"
|
||||
app:layout_anchorGravity="top|right|end"
|
||||
|
|
@ -154,5 +147,4 @@
|
|||
app:pressedTranslationZ="12dp"
|
||||
app:srcCompat="@drawable/ic_photo_white_24dp" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
|
@ -70,4 +70,7 @@
|
|||
<color name="color_error">#FF0000</color>
|
||||
<color name="yes_button_color">#B22222</color>
|
||||
<color name="no_button_color">#006400</color>
|
||||
|
||||
<color name="current_marker_stroke">#55000000</color>
|
||||
<color name="current_marker_fill">#11000000</color>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ class u {
|
|||
.thenReturn(Single.just(false))
|
||||
`when`(mediaClient?.checkPageExistsUsingTitle(ArgumentMatchers.anyString()))
|
||||
.thenReturn(Single.just(false))
|
||||
`when`(readFBMD?.processMetadata(ArgumentMatchers.any(),ArgumentMatchers.any()))
|
||||
`when`(readFBMD?.processMetadata(ArgumentMatchers.any()))
|
||||
.thenReturn(Single.just(ImageUtils.IMAGE_OK))
|
||||
`when`(readEXIF?.processMetadata(ArgumentMatchers.anyString()))
|
||||
.thenReturn(Single.just(ImageUtils.IMAGE_OK))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue