diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionController.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionController.java index 2ddbd9ece..37b3d5377 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionController.java +++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionController.java @@ -86,6 +86,7 @@ public class ContributionController { return; } Timber.d("startGalleryPick() called with pickImageIntent"); + fragment.startActivityForResult(pickImageIntent, SELECT_FROM_GALLERY); } @@ -115,6 +116,7 @@ public class ContributionController { if (isDirectUpload) { shareIntent.putExtra("isDirectUpload", true); } + break; default: break; diff --git a/app/src/main/java/fr/free/nrw/commons/location/LatLng.java b/app/src/main/java/fr/free/nrw/commons/location/LatLng.java index eeea75078..81e9ec707 100644 --- a/app/src/main/java/fr/free/nrw/commons/location/LatLng.java +++ b/app/src/main/java/fr/free/nrw/commons/location/LatLng.java @@ -159,3 +159,4 @@ public class LatLng { return Uri.parse("geo:0,0?q=" + latitude + "," + longitude); } } + diff --git a/app/src/main/java/fr/free/nrw/commons/location/LocationServiceManager.java b/app/src/main/java/fr/free/nrw/commons/location/LocationServiceManager.java index 851114ef9..f9a171461 100644 --- a/app/src/main/java/fr/free/nrw/commons/location/LocationServiceManager.java +++ b/app/src/main/java/fr/free/nrw/commons/location/LocationServiceManager.java @@ -10,6 +10,7 @@ import android.location.LocationManager; import android.os.Bundle; import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat; +import android.util.Log; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; @@ -19,7 +20,8 @@ import timber.log.Timber; public class LocationServiceManager implements LocationListener { public static final int LOCATION_REQUEST = 1; - private static final long MIN_LOCATION_UPDATE_REQUEST_TIME_IN_MILLIS = 2 * 60 * 1000; + // Maybe these values can be improved for efficiency + private static final long MIN_LOCATION_UPDATE_REQUEST_TIME_IN_MILLIS = 2 * 60 * 100; private static final long MIN_LOCATION_UPDATE_REQUEST_DISTANCE_IN_METERS = 10; private Context context; @@ -120,12 +122,14 @@ public class LocationServiceManager implements LocationListener { * * @param location the location to be tested * @param currentBestLocation the current best location - * @return true if the given location is better + * @return LOCATION_SIGNIFICANTLY_CHANGED if location changed significantly + * LOCATION_SLIGHTLY_CHANGED if location changed slightly */ - protected boolean isBetterLocation(Location location, Location currentBestLocation) { + protected LocationChangeType isBetterLocation(Location location, Location currentBestLocation) { + if (currentBestLocation == null) { // A new location is always better than no location - return true; + return LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED; } // Check whether the new location fix is newer or older @@ -134,15 +138,6 @@ public class LocationServiceManager implements LocationListener { boolean isSignificantlyOlder = timeDelta < -MIN_LOCATION_UPDATE_REQUEST_TIME_IN_MILLIS; boolean isNewer = timeDelta > 0; - // If it's been more than two minutes since the current location, use the new location - // because the user has likely moved - if (isSignificantlyNewer) { - return true; - // If the new location is more than two minutes older, it must be worse - } else if (isSignificantlyOlder) { - return false; - } - // Check whether the new location fix is more or less accurate int accuracyDelta = (int) (location.getAccuracy() - currentBestLocation.getAccuracy()); boolean isLessAccurate = accuracyDelta > 0; @@ -153,15 +148,28 @@ public class LocationServiceManager implements LocationListener { boolean isFromSameProvider = isSameProvider(location.getProvider(), currentBestLocation.getProvider()); - // Determine location quality using a combination of timeliness and accuracy - if (isMoreAccurate) { - return true; - } else if (isNewer && !isLessAccurate) { - return true; - } else if (isNewer && !isSignificantlyLessAccurate && isFromSameProvider) { - return true; + float[] results = new float[5]; + Location.distanceBetween( + currentBestLocation.getLatitude(), + currentBestLocation.getLongitude(), + location.getLatitude(), + location.getLongitude(), + results); + + // If it's been more than two minutes since the current location, use the new location + // because the user has likely moved + if (isSignificantlyNewer + || isMoreAccurate + || (isNewer && !isLessAccurate) + || (isNewer && !isSignificantlyLessAccurate && isFromSameProvider)) { + if (results[0] < 1000) { // Means change is smaller than 1000 meter + return LocationChangeType.LOCATION_SLIGHTLY_CHANGED; + } else { + return LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED; + } + } else{ + return LocationChangeType.LOCATION_NOT_CHANGED; } - return false; } /** @@ -208,12 +216,19 @@ public class LocationServiceManager implements LocationListener { @Override public void onLocationChanged(Location location) { - if (isBetterLocation(location, lastLocation)) { - lastLocation = location; - for (LocationUpdateListener listener : locationListeners) { - listener.onLocationChanged(LatLng.from(lastLocation)); + if (isBetterLocation(location, lastLocation) + .equals(LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED)) { + lastLocation = location; + for (LocationUpdateListener listener : locationListeners) { + listener.onLocationChangedSignificantly(LatLng.from(lastLocation)); + } + } else if (isBetterLocation(location, lastLocation) + .equals(LocationChangeType.LOCATION_SLIGHTLY_CHANGED)) { + lastLocation = location; + for (LocationUpdateListener listener : locationListeners) { + listener.onLocationChangedSlightly(LatLng.from(lastLocation)); + } } - } } @Override @@ -230,4 +245,10 @@ public class LocationServiceManager implements LocationListener { public void onProviderDisabled(String provider) { Timber.d("Provider %s disabled", provider); } + + public enum LocationChangeType{ + LOCATION_SIGNIFICANTLY_CHANGED, //Went out of borders of nearby markers + LOCATION_SLIGHTLY_CHANGED, //User might be walking or driving + LOCATION_NOT_CHANGED + } } diff --git a/app/src/main/java/fr/free/nrw/commons/location/LocationUpdateListener.java b/app/src/main/java/fr/free/nrw/commons/location/LocationUpdateListener.java index 69d3048a1..f3e920e18 100644 --- a/app/src/main/java/fr/free/nrw/commons/location/LocationUpdateListener.java +++ b/app/src/main/java/fr/free/nrw/commons/location/LocationUpdateListener.java @@ -1,5 +1,6 @@ package fr.free.nrw.commons.location; public interface LocationUpdateListener { - void onLocationChanged(LatLng latLng); + void onLocationChangedSignificantly(LatLng latLng); + void onLocationChangedSlightly(LatLng latLng); } diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyActivity.java b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyActivity.java index 0310fb287..06845b850 100644 --- a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyActivity.java +++ b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyActivity.java @@ -7,9 +7,8 @@ import android.os.Build; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.design.widget.BottomSheetBehavior; -import android.support.v4.app.Fragment; + import android.support.v4.app.FragmentTransaction; -import android.support.v4.widget.SwipeRefreshLayout; import android.support.v7.app.AlertDialog; import android.view.Menu; import android.view.MenuInflater; @@ -28,17 +27,19 @@ import javax.inject.Inject; import butterknife.BindView; import butterknife.ButterKnife; + import fr.free.nrw.commons.R; 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.theme.NavigationBaseActivity; import fr.free.nrw.commons.utils.UriSerializer; -import fr.free.nrw.commons.utils.ViewUtil; + import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; + import timber.log.Timber; @@ -68,7 +69,9 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp private BottomSheetBehavior bottomSheetBehavior; // Behavior for list bottom sheet private BottomSheetBehavior bottomSheetBehaviorForDetails; // Behavior for details bottom sheet private NearbyMapFragment nearbyMapFragment; - private static final String TAG_RETAINED_FRAGMENT = "RetainedFragment"; + 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(); @Override protected void onCreate(Bundle savedInstanceState) { @@ -83,21 +86,13 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp } private void resumeFragment() { - // find the retained fragment on activity restarts - android.support.v4.app.FragmentManager fm = getSupportFragmentManager(); - nearbyMapFragment = (NearbyMapFragment) fm.findFragmentByTag(TAG_RETAINED_FRAGMENT); - - // create the fragment and data the first time - if (nearbyMapFragment == null) { - // add the fragment - nearbyMapFragment = new NearbyMapFragment(); - fm.beginTransaction().add(nearbyMapFragment, TAG_RETAINED_FRAGMENT).commit(); - // load data from a data source or perform any calculation - } - + // Find the retained fragment on activity restarts + nearbyMapFragment = getMapFragment(); + nearbyListFragment = getListFragment(); } private void initBottomSheetBehaviour() { + transparentView.setAlpha(0); bottomSheet.getLayoutParams().height = getWindowManager() @@ -154,7 +149,7 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp switch (requestCode) { case LOCATION_REQUEST: { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - refreshView(); + refreshView(LocationServiceManager.LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED); } else { //If permission not granted, go to page that says Nearby Places cannot be displayed hideProgressBar(); @@ -209,7 +204,7 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp private void checkLocationPermission() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (locationManager.isLocationPermissionGranted()) { - refreshView(); + refreshView(LocationServiceManager.LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED); } else { // Should we show an explanation? if (locationManager.isPermissionExplanationRequired(this)) { @@ -235,7 +230,7 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp } } } else { - refreshView(); + refreshView(LocationServiceManager.LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED); } } @@ -244,7 +239,7 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp super.onActivityResult(requestCode, resultCode, data); if (requestCode == 1) { Timber.d("User is back from Settings page"); - refreshView(); + refreshView(LocationServiceManager.LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED); } } @@ -252,6 +247,7 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp protected void onStart() { super.onStart(); locationManager.addLocationListener(this); + locationManager.registerLocationManager(); } @Override @@ -282,10 +278,10 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp // this means that this activity will not be recreated now, user is leaving it // or the activity is otherwise finishing if(isFinishing()) { - android.support.v4.app.FragmentManager fm = getSupportFragmentManager(); // 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. - fm.beginTransaction().remove(nearbyMapFragment).commit(); + removeMapFragment(); + removeListFragment(); } } @@ -294,13 +290,15 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp /** * This method should be the single point to load/refresh nearby places * + * @param locationChangeType defines if location shanged significantly or slightly */ - private void refreshView() { + private void refreshView(LocationServiceManager.LocationChangeType locationChangeType) { if (lockNearbyView) { return; } locationManager.registerLocationManager(); LatLng lastLocation = locationManager.getLastLocation(); + if (curLatLang != null && curLatLang.equals(lastLocation)) { //refresh view only if location has changed return; @@ -312,20 +310,34 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp return; } - progressBar.setVisibility(View.VISIBLE); - placesDisposable = Observable.fromCallable(() -> nearbyController - .loadAttractionsFromLocation(curLatLang)) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(this::populatePlaces); + if (locationChangeType + .equals(LocationServiceManager.LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED)) { + progressBar.setVisibility(View.VISIBLE); + placesDisposable = Observable.fromCallable(() -> nearbyController + .loadAttractionsFromLocation(curLatLang)) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(this::populatePlaces); + } else if (locationChangeType + .equals(LocationServiceManager.LocationChangeType.LOCATION_SLIGHTLY_CHANGED)) { + Gson gson = new GsonBuilder() + .registerTypeAdapter(Uri.class, new UriSerializer()) + .create(); + String gsonCurLatLng = gson.toJson(curLatLang); + bundle.putString("CurLatLng", gsonCurLatLng); + updateMapFragment(true); + } } - private void populatePlaces(List placeList) { + private void populatePlaces(NearbyController.NearbyPlacesInfo nearbyPlacesInfo) { + List placeList = nearbyPlacesInfo.placeList; + LatLng[] boundaryCoordinates = nearbyPlacesInfo.boundaryCoordinates; Gson gson = new GsonBuilder() .registerTypeAdapter(Uri.class, new UriSerializer()) .create(); String gsonPlaceList = gson.toJson(placeList); String gsonCurLatLng = gson.toJson(curLatLang); + String gsonBoundaryCoordinates = gson.toJson(boundaryCoordinates); if (placeList.size() == 0) { int duration = Toast.LENGTH_SHORT; @@ -336,12 +348,20 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp bundle.clear(); bundle.putString("PlaceList", gsonPlaceList); bundle.putString("CurLatLng", gsonCurLatLng); + bundle.putString("BoundaryCoord", gsonBoundaryCoordinates); - lockNearbyView(true); - setMapFragment(); - setListFragment(); - - hideProgressBar(); + // First time to init fragments + if (nearbyMapFragment == null) { + lockNearbyView(true); + setMapFragment(); + setListFragment(); + hideProgressBar(); + lockNearbyView(false); + } else { + // There are fragments, just update the map and list + updateMapFragment(false); + updateListFragment(); + } } private void lockNearbyView(boolean lock) { @@ -362,14 +382,92 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp } } + private NearbyMapFragment getMapFragment() { + return (NearbyMapFragment) getSupportFragmentManager().findFragmentByTag(TAG_RETAINED_MAP_FRAGMENT); + } + + private void removeMapFragment() { + if (nearbyMapFragment != null) { + android.support.v4.app.FragmentManager fm = getSupportFragmentManager(); + fm.beginTransaction().remove(nearbyMapFragment).commit(); + } + } + + private NearbyListFragment getListFragment() { + return (NearbyListFragment) getSupportFragmentManager().findFragmentByTag(TAG_RETAINED_LIST_FRAGMENT); + } + + private void removeListFragment() { + if (nearbyListFragment != null) { + android.support.v4.app.FragmentManager fm = getSupportFragmentManager(); + fm.beginTransaction().remove(nearbyListFragment).commit(); + } + } + + private void updateMapFragment(boolean isSlightUpdate) { + /* + * 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 && curLatLang != 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 + && (curLatLang.getLatitude() <= nearbyMapFragment.boundaryCoordinates[0].getLatitude() + || curLatLang.getLatitude() >= nearbyMapFragment.boundaryCoordinates[1].getLatitude() + || curLatLang.getLongitude() <= nearbyMapFragment.boundaryCoordinates[2].getLongitude() + || curLatLang.getLongitude() >= nearbyMapFragment.boundaryCoordinates[3].getLongitude())) { + // populate places + placesDisposable = Observable.fromCallable(() -> nearbyController + .loadAttractionsFromLocation(curLatLang)) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(this::populatePlaces); + nearbyMapFragment.setArguments(bundle); + nearbyMapFragment.updateMapSignificantly(); + updateListFragment(); + return; + } + + if (isSlightUpdate) { + nearbyMapFragment.setArguments(bundle); + nearbyMapFragment.updateMapSlightly(); + } else { + nearbyMapFragment.setArguments(bundle); + nearbyMapFragment.updateMapSignificantly(); + updateListFragment(); + } + } else { + lockNearbyView(true); + setMapFragment(); + setListFragment(); + hideProgressBar(); + lockNearbyView(false); + } + } + + private void updateListFragment() { + nearbyListFragment.setArguments(bundle); + nearbyListFragment.updateNearbyListSignificantly(); + } + /** * Calls fragment for map view. */ private void setMapFragment() { FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction(); - Fragment fragment = new NearbyMapFragment(); - fragment.setArguments(bundle); - fragmentTransaction.replace(R.id.container, fragment, fragment.getClass().getSimpleName()); + nearbyMapFragment = new NearbyMapFragment(); + nearbyMapFragment.setArguments(bundle); + fragmentTransaction.replace(R.id.container, nearbyMapFragment, TAG_RETAINED_MAP_FRAGMENT); fragmentTransaction.commitAllowingStateLoss(); } @@ -378,16 +476,22 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp */ private void setListFragment() { FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction(); - Fragment fragment = new NearbyListFragment(); - fragment.setArguments(bundle); - fragmentTransaction.replace(R.id.container_sheet, fragment); + 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(); } @Override - public void onLocationChanged(LatLng latLng) { - refreshView(); + public void onLocationChangedSignificantly(LatLng latLng) { + refreshView(LocationServiceManager.LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED); + } + + @Override + public void onLocationChangedSlightly(LatLng latLng) { + refreshView(LocationServiceManager.LocationChangeType.LOCATION_SLIGHTLY_CHANGED); } public void prepareViewsForSheetPosition(int bottomSheetState) { diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyAdapterFactory.java b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyAdapterFactory.java index c060c31b5..f596dc05a 100644 --- a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyAdapterFactory.java +++ b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyAdapterFactory.java @@ -1,5 +1,6 @@ package fr.free.nrw.commons.nearby; + import android.support.v4.app.Fragment; import com.pedrogomez.renderers.ListAdapteeCollection; @@ -32,4 +33,9 @@ class NearbyAdapterFactory { placeList != null ? placeList : Collections.emptyList()); return new RVRendererAdapter<>(builder, collection); } + + public void updateAdapterData(List newPlaceList, RVRendererAdapter rendererAdapter) { + rendererAdapter.notifyDataSetChanged(); + rendererAdapter.diffUpdate(newPlaceList); + } } \ No newline at end of file diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyController.java b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyController.java index f4a8fc3f7..0ecf09160 100644 --- a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyController.java +++ b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyController.java @@ -41,19 +41,42 @@ public class NearbyController { * Prepares Place list to make their distance information update later. * * @param curLatLng current location for user - * @return Place list without distance information + * @return NearbyPlacesInfo a variable holds Place list without distance information + * and boundary coordinates of current Place List */ - public List loadAttractionsFromLocation(LatLng curLatLng) { + public NearbyPlacesInfo loadAttractionsFromLocation(LatLng curLatLng) { + Timber.d("Loading attractions near %s", curLatLng); + NearbyPlacesInfo nearbyPlacesInfo = new NearbyPlacesInfo(); + if (curLatLng == null) { - return Collections.emptyList(); + return null; } List places = nearbyPlaces.getFromWikidataQuery(curLatLng, Locale.getDefault().getLanguage()); + + LatLng[] boundaryCoordinates = {places.get(0).location, // south + places.get(0).location, // north + places.get(0).location, // west + places.get(0).location};// east, init with a random location + if (curLatLng != null) { Timber.d("Sorting places by distance..."); final Map distances = new HashMap<>(); for (Place place: places) { distances.put(place, computeDistanceBetween(place.location, curLatLng)); + // Find boundaries with basic find max approach + if (place.location.getLatitude() < boundaryCoordinates[0].getLatitude()) { + boundaryCoordinates[0] = place.location; + } + if (place.location.getLatitude() > boundaryCoordinates[1].getLatitude()) { + boundaryCoordinates[1] = place.location; + } + if (place.location.getLongitude() < boundaryCoordinates[2].getLongitude()) { + boundaryCoordinates[2] = place.location; + } + if (place.location.getLongitude() > boundaryCoordinates[3].getLongitude()) { + boundaryCoordinates[3] = place.location; + } } Collections.sort(places, (lhs, rhs) -> { @@ -63,7 +86,9 @@ public class NearbyController { } ); } - return places; + nearbyPlacesInfo.placeList = places; + nearbyPlacesInfo.boundaryCoordinates = boundaryCoordinates; + return nearbyPlacesInfo; } /** @@ -128,4 +153,9 @@ public class NearbyController { } return baseMarkerOptions; } + + public class NearbyPlacesInfo { + List placeList; // List of nearby places + LatLng[] boundaryCoordinates; // Corners of nearby area + } } diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyListFragment.java b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyListFragment.java index a2796d582..dcc7f5e24 100644 --- a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyListFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyListFragment.java @@ -15,6 +15,7 @@ import android.view.ViewGroup; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.reflect.TypeToken; +import com.pedrogomez.renderers.RVRendererAdapter; import java.lang.reflect.Type; import java.util.Collections; @@ -64,6 +65,7 @@ public class NearbyListFragment extends DaggerFragment { View view = inflater.inflate(R.layout.fragment_nearby, container, false); recyclerView = view.findViewById(R.id.listView); recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); + controller = new ContributionController(this); adapterFactory = new NearbyAdapterFactory(this, controller); return view; @@ -73,8 +75,19 @@ public class NearbyListFragment extends DaggerFragment { public void onViewCreated(View view, Bundle savedInstanceState) { // Check that this is the first time view is created, // to avoid double list when screen orientation changed - List placeList = Collections.emptyList(); Bundle bundle = this.getArguments(); + recyclerView.setAdapter(adapterFactory.create(getPlaceListFromBundle(bundle))); + } + + public void updateNearbyListSignificantly() { + Bundle bundle = this.getArguments(); + adapterFactory.updateAdapterData(getPlaceListFromBundle(bundle), + (RVRendererAdapter) recyclerView.getAdapter()); + } + + private List getPlaceListFromBundle(Bundle bundle) { + List placeList = Collections.emptyList(); + if (bundle != null) { String gsonPlaceList = bundle.getString("PlaceList", "[]"); placeList = gson.fromJson(gsonPlaceList, LIST_TYPE); @@ -84,9 +97,9 @@ public class NearbyListFragment extends DaggerFragment { placeList = NearbyController.loadAttractionsFromLocationToPlaces(curLatLng, placeList); } - recyclerView.setAdapter(adapterFactory.create(placeList)); - } + return placeList; + } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { @@ -126,4 +139,5 @@ public class NearbyListFragment extends DaggerFragment { requestCode, resultCode, data); } } + } \ No newline at end of file diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyMapFragment.java b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyMapFragment.java index 4eedabf67..6a1475e5a 100644 --- a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyMapFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyMapFragment.java @@ -1,5 +1,9 @@ package fr.free.nrw.commons.nearby; +import android.animation.ObjectAnimator; +import android.animation.TypeEvaluator; +import android.animation.ValueAnimator; + import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; @@ -11,6 +15,7 @@ import android.support.annotation.Nullable; import android.support.design.widget.BottomSheetBehavior; import android.support.design.widget.CoordinatorLayout; import android.support.design.widget.FloatingActionButton; + import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; @@ -29,11 +34,13 @@ import com.mapbox.mapboxsdk.annotations.Marker; import com.mapbox.mapboxsdk.annotations.MarkerOptions; import com.mapbox.mapboxsdk.annotations.PolygonOptions; import com.mapbox.mapboxsdk.camera.CameraPosition; +import com.mapbox.mapboxsdk.camera.CameraUpdateFactory; import com.mapbox.mapboxsdk.constants.Style; import com.mapbox.mapboxsdk.geometry.LatLng; 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.services.android.telemetry.MapboxTelemetry; import java.lang.reflect.Type; @@ -57,6 +64,8 @@ public class NearbyMapFragment extends DaggerFragment { private MapView mapView; private List baseMarkerOptions; private fr.free.nrw.commons.location.LatLng curLatLng; + public fr.free.nrw.commons.location.LatLng[] boundaryCoordinates; + private View bottomSheetList; private View bottomSheetDetails; @@ -89,6 +98,12 @@ public class NearbyMapFragment extends DaggerFragment { private Place place; private Marker selected; + private Marker currentLocationMarker; + private MapboxMap mapboxMap; + private PolygonOptions currentLocationPolygonOptions; + + private boolean isBottomListSheetExpanded; + private final double CAMERA_TARGET_SHIFT_FACTOR = 0.06; @Inject @Named("prefs") SharedPreferences prefs; @Inject @Named("direct_nearby_upload_prefs") SharedPreferences directPrefs; @@ -100,22 +115,23 @@ public class NearbyMapFragment extends DaggerFragment { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Bundle bundle = this.getArguments(); - initViews(); - setListeners(); Gson gson = new GsonBuilder() .registerTypeAdapter(Uri.class, new UriDeserializer()) .create(); if (bundle != null) { String gsonPlaceList = bundle.getString("PlaceList"); String gsonLatLng = bundle.getString("CurLatLng"); + String gsonBoundaryCoordinates = bundle.getString("BoundaryCoord"); Type listType = new TypeToken>() {}.getType(); List placeList = gson.fromJson(gsonPlaceList, listType); Type curLatLngType = new TypeToken() {}.getType(); + Type gsonBoundaryCoordinatesType = new TypeToken() {}.getType(); curLatLng = gson.fromJson(gsonLatLng, curLatLngType); baseMarkerOptions = NearbyController .loadAttractionsFromLocationToBaseMarkerOptions(curLatLng, placeList, getActivity()); + boundaryCoordinates = gson.fromJson(gsonBoundaryCoordinates, gsonBoundaryCoordinatesType); } Mapbox.getInstance(getActivity(), getString(R.string.mapbox_commons_app_token)); @@ -160,12 +176,123 @@ public class NearbyMapFragment extends DaggerFragment { }); } + public void updateMapSlightly() { + // Get arguments from bundle for new location + Bundle bundle = this.getArguments(); + if (mapboxMap != null) { + Gson gson = new GsonBuilder() + .registerTypeAdapter(Uri.class, new UriDeserializer()) + .create(); + if (bundle != null) { + String gsonLatLng = bundle.getString("CurLatLng"); + Type curLatLngType = new TypeToken() {}.getType(); + curLatLng = gson.fromJson(gsonLatLng, curLatLngType); + } + updateMapToTrackPosition(); + } + + } + + public void updateMapSignificantly() { + + Bundle bundle = this.getArguments(); + if (mapboxMap != null) { + if (bundle != null) { + Gson gson = new GsonBuilder() + .registerTypeAdapter(Uri.class, new UriDeserializer()) + .create(); + + String gsonPlaceList = bundle.getString("PlaceList"); + String gsonLatLng = bundle.getString("CurLatLng"); + String gsonBoundaryCoordinates = bundle.getString("BoundaryCoord"); + Type listType = new TypeToken>() {}.getType(); + List placeList = gson.fromJson(gsonPlaceList, listType); + Type curLatLngType = new TypeToken() {}.getType(); + Type gsonBoundaryCoordinatesType = new TypeToken() {}.getType(); + curLatLng = gson.fromJson(gsonLatLng, curLatLngType); + baseMarkerOptions = NearbyController + .loadAttractionsFromLocationToBaseMarkerOptions(curLatLng, + placeList, + getActivity()); + boundaryCoordinates = gson.fromJson(gsonBoundaryCoordinates, gsonBoundaryCoordinatesType); + } + mapboxMap.clear(); + addCurrentLocationMarker(mapboxMap); + updateMapToTrackPosition(); + addNearbyMarkerstoMapBoxMap(); + } + } + + // Only update current position marker and camera view + private void updateMapToTrackPosition() { + + if (currentLocationMarker != null) { + LatLng curMapBoxLatLng = new LatLng(curLatLng.getLatitude(),curLatLng.getLongitude()); + ValueAnimator markerAnimator = ObjectAnimator.ofObject(currentLocationMarker, "position", + new LatLngEvaluator(), currentLocationMarker.getPosition(), + curMapBoxLatLng); + markerAnimator.setDuration(1000); + markerAnimator.start(); + + List circle = createCircleArray(curLatLng.getLatitude(), curLatLng.getLongitude(), + curLatLng.getAccuracy() * 2, 100); + if (currentLocationPolygonOptions != null){ + mapboxMap.removePolygon(currentLocationPolygonOptions.getPolygon()); + currentLocationPolygonOptions = new PolygonOptions() + .addAll(circle) + .strokeColor(Color.parseColor("#55000000")) + .fillColor(Color.parseColor("#11000000")); + mapboxMap.addPolygon(currentLocationPolygonOptions); + } + + // Make camera to follow user on location change + CameraPosition position = new CameraPosition.Builder() + .target(isBottomListSheetExpanded ? + new LatLng(curMapBoxLatLng.getLatitude()- CAMERA_TARGET_SHIFT_FACTOR, + curMapBoxLatLng.getLongitude()) + : curMapBoxLatLng ) // Sets the new camera position + .zoom(mapboxMap.getCameraPosition().zoom) // Same zoom level + .build(); + + mapboxMap.animateCamera(CameraUpdateFactory + .newCameraPosition(position), 1000); + + } + } + + private void updateMapCameraAccordingToBottomSheet(boolean isBottomListSheetExpanded) { + CameraPosition position; + this.isBottomListSheetExpanded = isBottomListSheetExpanded; + if (mapboxMap != null && curLatLng != null) { + if (isBottomListSheetExpanded) { + // Make camera to follow user on location change + position = new CameraPosition.Builder() + .target(new LatLng(curLatLng.getLatitude() - CAMERA_TARGET_SHIFT_FACTOR, + curLatLng.getLongitude())) // Sets the new camera target above + // current to make it visible when sheet is expanded + .zoom(mapboxMap.getCameraPosition().zoom) // Same zoom level + .build(); + + } else { + // Make camera to follow user on location change + position = new CameraPosition.Builder() + .target(new LatLng(curLatLng.getLatitude(), + curLatLng.getLongitude())) // Sets the new camera target to curLatLng + .zoom(mapboxMap.getCameraPosition().zoom) // Same zoom level + .build(); + } + mapboxMap.animateCamera(CameraUpdateFactory + .newCameraPosition(position), 1000); + } + } + private void initViews() { bottomSheetList = getActivity().findViewById(R.id.bottom_sheet); bottomSheetListBehavior = BottomSheetBehavior.from(bottomSheetList); bottomSheetDetails = getActivity().findViewById(R.id.bottom_sheet_details); bottomSheetDetailsBehavior = BottomSheetBehavior.from(bottomSheetDetails); bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_HIDDEN); + bottomSheetDetails.setVisibility(View.VISIBLE); fabPlus = getActivity().findViewById(R.id.fab_plus); fabCamera = getActivity().findViewById(R.id.fab_camera); @@ -233,6 +360,9 @@ public class NearbyMapFragment extends DaggerFragment { public void onStateChanged(@NonNull View bottomSheet, int newState) { if (newState == BottomSheetBehavior.STATE_EXPANDED){ bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_HIDDEN); + updateMapCameraAccordingToBottomSheet(true); + } else { + updateMapCameraAccordingToBottomSheet(false); } } @@ -267,30 +397,13 @@ public class NearbyMapFragment extends DaggerFragment { // create map mapView = new MapView(getActivity(), options); mapView.onCreate(savedInstanceState); - mapView.getMapAsync(mapboxMap -> { - mapboxMap.addMarkers(baseMarkerOptions); - - mapboxMap.setOnInfoWindowCloseListener(marker -> { - if (marker == selected){ - bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_HIDDEN); - } - }); - - mapboxMap.setOnMarkerClickListener(marker -> { - if (marker instanceof NearbyMarker) { - this.selected = marker; - NearbyMarker nearbyMarker = (NearbyMarker) marker; - Place place = nearbyMarker.getNearbyBaseMarker().getPlace(); - passInfoToSheet(place); - bottomSheetListBehavior.setState(BottomSheetBehavior.STATE_HIDDEN); - bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED); - } - return false; - }); - - addCurrentLocationMarker(mapboxMap); + mapView.getMapAsync(new OnMapReadyCallback() { + @Override + public void onMapReady(MapboxMap mapboxMap) { + NearbyMapFragment.this.mapboxMap = mapboxMap; + updateMapSignificantly(); + } }); - mapView.setStyleUrl("asset://mapstyle.json"); } @@ -299,23 +412,55 @@ public class NearbyMapFragment extends DaggerFragment { * 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. */ private void addCurrentLocationMarker(MapboxMap mapboxMap) { - MarkerOptions currentLocationMarker = new MarkerOptions() + if (currentLocationMarker != null) { + currentLocationMarker.remove(); // Remove previous marker, we are not Hansel and Gretel + } + MarkerOptions currentLocationMarkerOptions = new MarkerOptions() .position(new LatLng(curLatLng.getLatitude(), curLatLng.getLongitude())); - mapboxMap.addMarker(currentLocationMarker); + currentLocationMarker = mapboxMap.addMarker(currentLocationMarkerOptions); + List circle = createCircleArray(curLatLng.getLatitude(), curLatLng.getLongitude(), curLatLng.getAccuracy() * 2, 100); - mapboxMap.addPolygon( - new PolygonOptions() - .addAll(circle) - .strokeColor(Color.parseColor("#55000000")) - .fillColor(Color.parseColor("#11000000")) - ); + currentLocationPolygonOptions = new PolygonOptions() + .addAll(circle) + .strokeColor(Color.parseColor("#55000000")) + .fillColor(Color.parseColor("#11000000")); + mapboxMap.addPolygon(currentLocationPolygonOptions); } + private void addNearbyMarkerstoMapBoxMap() { + + mapboxMap.addMarkers(baseMarkerOptions); + mapboxMap.setOnInfoWindowCloseListener(marker -> { + if (marker == selected){ + bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_HIDDEN); + } + }); + + mapboxMap.setOnMarkerClickListener(marker -> { + if (marker instanceof NearbyMarker) { + this.selected = marker; + NearbyMarker nearbyMarker = (NearbyMarker) marker; + Place place = nearbyMarker.getNearbyBaseMarker().getPlace(); + passInfoToSheet(place); + bottomSheetListBehavior.setState(BottomSheetBehavior.STATE_HIDDEN); + bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED); + } + return false; + }); + + } + + + /** * Creates a series of points that create a circle on the map. * Takes the center latitude, center longitude of the circle, @@ -401,6 +546,7 @@ public class NearbyMapFragment extends DaggerFragment { commonsButton.setOnClickListener(view -> openWebView(place.siteLinks.getCommonsLink())); icon.setImageResource(place.getLabel().getIcon()); + title.setText(place.name); distance.setText(place.distance); description.setText(place.getLongDescription()); @@ -410,6 +556,7 @@ public class NearbyMapFragment extends DaggerFragment { fabCamera.setOnClickListener(view -> { Timber.d("Camera button tapped. Image title: " + place.getName() + "Image desc: " + place.getLongDescription()); controller = new ContributionController(this); + DirectUpload directUpload = new DirectUpload(this, controller); storeSharedPrefs(); directUpload.initiateCameraUpload(); @@ -418,12 +565,14 @@ public class NearbyMapFragment extends DaggerFragment { fabGallery.setOnClickListener(view -> { Timber.d("Gallery button tapped. Image title: " + place.getName() + "Image desc: " + place.getLongDescription()); controller = new ContributionController(this); + DirectUpload directUpload = new DirectUpload(this, controller); storeSharedPrefs(); directUpload.initiateGalleryUpload(); -//TODO: App crashes after image upload completes -//TODO: Handle onRequestPermissionsResult + //TODO: App crashes after image upload completes + //TODO: Handle onRequestPermissionsResult + }); } @@ -523,10 +672,14 @@ public class NearbyMapFragment extends DaggerFragment { @Override public void onResume() { + super.onResume(); if (mapView != null) { mapView.onResume(); } - super.onResume(); + initViews(); + setListeners(); + transparentView.setClickable(false); + transparentView.setAlpha(0); } @Override @@ -544,4 +697,19 @@ public class NearbyMapFragment extends DaggerFragment { } super.onDestroyView(); } + + private static class LatLngEvaluator implements TypeEvaluator { + // Method is used to interpolate the marker animation. + private LatLng latLng = new LatLng(); + + @Override + public LatLng evaluate(float fraction, LatLng startValue, LatLng endValue) { + latLng.setLatitude(startValue.getLatitude() + + ((endValue.getLatitude() - startValue.getLatitude()) * fraction)); + latLng.setLongitude(startValue.getLongitude() + + ((endValue.getLongitude() - startValue.getLongitude()) * fraction)); + return latLng; + } + } } + diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/Place.java b/app/src/main/java/fr/free/nrw/commons/nearby/Place.java index 94ef9a637..eaab3e447 100644 --- a/app/src/main/java/fr/free/nrw/commons/nearby/Place.java +++ b/app/src/main/java/fr/free/nrw/commons/nearby/Place.java @@ -34,12 +34,12 @@ public class Place { this.siteLinks = siteLinks; } + public String getName() { return name; } + public Label getLabel() { return label; } - public String getName() { return name; } - public String getLongDescription() { return longDescription; } public void setDistance(String distance) { diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/PlaceRenderer.java b/app/src/main/java/fr/free/nrw/commons/nearby/PlaceRenderer.java index 85588ef92..0d432f90e 100644 --- a/app/src/main/java/fr/free/nrw/commons/nearby/PlaceRenderer.java +++ b/app/src/main/java/fr/free/nrw/commons/nearby/PlaceRenderer.java @@ -1,10 +1,11 @@ package fr.free.nrw.commons.nearby; import android.content.Intent; -import android.content.SharedPreferences; + import android.net.Uri; -import android.support.transition.TransitionManager; +import android.content.SharedPreferences; import android.support.v4.app.Fragment; +import android.support.transition.TransitionManager; import android.support.v7.widget.PopupMenu; import android.util.Log; import android.view.LayoutInflater; @@ -37,17 +38,20 @@ public class PlaceRenderer extends Renderer { @BindView(R.id.icon) ImageView icon; @BindView(R.id.buttonLayout) LinearLayout buttonLayout; @BindView(R.id.cameraButton) LinearLayout cameraButton; + @BindView(R.id.galleryButton) LinearLayout galleryButton; @BindView(R.id.directionsButton) LinearLayout directionsButton; @BindView(R.id.iconOverflow) LinearLayout iconOverflow; @BindView(R.id.cameraButtonText) TextView cameraButtonText; @BindView(R.id.galleryButtonText) TextView galleryButtonText; + @BindView(R.id.directionsButtonText) TextView directionsButtonText; @BindView(R.id.iconOverflowText) TextView iconOverflowText; private View view; private static ArrayList openedItems; private Place place; + private Fragment fragment; private ContributionController controller; @@ -83,9 +87,9 @@ public class PlaceRenderer extends Renderer { Log.d("Renderer", "clicked"); TransitionManager.beginDelayedTransition(buttonLayout); - if(buttonLayout.isShown()) { + if(buttonLayout.isShown()){ closeLayout(buttonLayout); - } else { + }else { openLayout(buttonLayout); } @@ -121,6 +125,7 @@ public class PlaceRenderer extends Renderer { editor.putString("Title", place.getName()); editor.putString("Desc", place.getLongDescription()); editor.apply(); + } private void closeLayout(LinearLayout buttonLayout){ @@ -133,10 +138,11 @@ public class PlaceRenderer extends Renderer { @Override public void render() { -// ((CommonsApplication) getContext().getApplicationContext()).injector().inject(this); + place = getContent(); tvName.setText(place.name); String descriptionText = place.getLongDescription(); + if (descriptionText.equals("?")) { descriptionText = getContext().getString(R.string.no_description_found); } @@ -154,6 +160,7 @@ public class PlaceRenderer extends Renderer { iconOverflow.setVisibility(showMenu() ? View.VISIBLE : View.GONE); iconOverflow.setOnClickListener(v -> popupMenuListener()); + } private void popupMenuListener() { diff --git a/app/src/main/java/fr/free/nrw/commons/upload/ShareActivity.java b/app/src/main/java/fr/free/nrw/commons/upload/ShareActivity.java index 1fa39328f..fa0dcedaa 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/ShareActivity.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/ShareActivity.java @@ -54,6 +54,7 @@ import fr.free.nrw.commons.modifications.ModificationsContentProvider; import fr.free.nrw.commons.modifications.ModifierSequence; import fr.free.nrw.commons.modifications.ModifierSequenceDao; import fr.free.nrw.commons.modifications.TemplateRemoveModifier; + import fr.free.nrw.commons.utils.ImageUtils; import fr.free.nrw.commons.mwapi.MediaWikiApi; import timber.log.Timber; @@ -111,8 +112,10 @@ public class ShareActivity private String description; private Snackbar snackbar; private boolean duplicateCheckPassed = false; + private boolean haveCheckedForOtherImages = false; private boolean isNearbyUpload = false; + /** * Called when user taps the submit button. */ diff --git a/app/src/main/java/fr/free/nrw/commons/upload/SingleUploadFragment.java b/app/src/main/java/fr/free/nrw/commons/upload/SingleUploadFragment.java index 4bf917fcc..395873c1d 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/SingleUploadFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/SingleUploadFragment.java @@ -2,6 +2,7 @@ package fr.free.nrw.commons.upload; import android.annotation.SuppressLint; import android.app.Activity; + import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; diff --git a/app/src/main/res/drawable/blue_location_dot.xml b/app/src/main/res/drawable/blue_location_dot.xml new file mode 100644 index 000000000..a8b409b1d --- /dev/null +++ b/app/src/main/res/drawable/blue_location_dot.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-land/activity_nearby.xml b/app/src/main/res/layout-land/activity_nearby.xml deleted file mode 100644 index f6cf248d0..000000000 --- a/app/src/main/res/layout-land/activity_nearby.xml +++ /dev/null @@ -1,148 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/activity_nearby.xml b/app/src/main/res/layout/activity_nearby.xml index a963636c0..55607b557 100644 --- a/app/src/main/res/layout/activity_nearby.xml +++ b/app/src/main/res/layout/activity_nearby.xml @@ -62,6 +62,7 @@ android:background="#aa969696" android:elevation="6dp" /> + diff --git a/app/src/main/res/layout/bottom_sheet_details.xml b/app/src/main/res/layout/bottom_sheet_details.xml index 1fb7e5ab1..2be0f6cf7 100644 --- a/app/src/main/res/layout/bottom_sheet_details.xml +++ b/app/src/main/res/layout/bottom_sheet_details.xml @@ -8,6 +8,8 @@ app:layout_behavior="@string/bottom_sheet_behavior" app:behavior_peekHeight="72dp" app:behavior_hideable="true" + android:visibility="gone" + > >>>>>> Rename Description to Label to prevent misunderstandings + private Place clickedPlace; @Test