Merge branch 'master' into notificationFixes

This commit is contained in:
Vivek Maskara 2018-03-24 12:44:09 +05:30 committed by GitHub
commit 6eaac1de40
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
80 changed files with 2625 additions and 555 deletions

View file

@ -1,6 +1,8 @@
package fr.free.nrw.commons;
import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
@ -10,6 +12,9 @@ import android.util.Log;
import android.support.customtabs.CustomTabsIntent;
import android.support.v4.content.ContextCompat;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.LinearLayout;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
@ -29,6 +34,16 @@ public class AboutActivity extends NavigationBaseActivity {
@BindView(R.id.about_license) HtmlTextView aboutLicenseText;
@BindView(R.id.about_faq) TextView faqText;
String language[] = { "Kazakh", "Afrikaans", "Arabic", "Bengali", "Asturianu", "azərbaycanca", "Bikol Central",
"Bulgarain", "বাংলা", "Bosanski", "Brezhoneg","català","کوردی", " čeština", " kaszëbsczi", "Cymraeg", "dansk", "Deutsch"
,"Zazaki", "डोटेली","Ελληνικά","euskara","español","فارسی","suomi", "français" ,"Nordfriisk", "galego", "Hawaiʻi"
,"हिन्दी","Hunsrik","עברית","hornjoserbsce","magyar","interlingua","Bahasa Indonesia", "íslenska","Italian","japanese",
"Basa Jawa", "ქართული", " ភាសាខ្មែរ","ಕನ್ನಡ", "한국어","къарачай-малкъар","Кыргызча", "latina", "Lëtzebuergesch", "lietuvių",
"latviešu", "Malagasy", "македонски"," മലയാളം","монгол","मराठी","Bahasa Melayu","Malti", "नेपाली", "norsk bokmål",
" Nederlands","occitan","ଓଡ଼ିଆ","ਪੰਜਾਬੀ","polsk","Piemontèis","پښتو","português","română","русский"," سنڌي", " සිංහල",
"slovenčina"," سرائیکی", "svenska", "தமிழ்", "ತುಳು"," తెలుగు"," ไทย", "Türkçe","українська", "اردو", "Tiếng Việt",
" მარგალური","ייִדיש",};
/**
* This method helps in the creation About screen
*
@ -87,8 +102,43 @@ public class AboutActivity extends NavigationBaseActivity {
Utils.handleWebUrl(this,Uri.parse("https://github.com/commons-app/apps-android-commons/wiki/Privacy-policy\\"));
}
@OnClick(R.id.about_faq)
public void launchFrequentlyAskedQuesions(View view) {
Utils.handleWebUrl(this,Uri.parse("https://github.com/commons-app/apps-android-commons/wiki/Frequently-Asked-Questions\\"));
}
@OnClick(R.id.about_translate)
public void launchTranslate(View view) {
final ArrayAdapter<String> languageAdapter = new ArrayAdapter<String>(AboutActivity.this,
android.R.layout.simple_spinner_item, language);
final Spinner spinner = new Spinner(AboutActivity.this);
spinner.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT));
spinner.setAdapter(languageAdapter);
spinner.setGravity(17);
AlertDialog.Builder builder = new AlertDialog.Builder(AboutActivity.this);
builder.setView(spinner);
builder.setTitle(R.string.about_translate_title)
.setMessage(R.string.about_translate_message)
.setPositiveButton(R.string.about_translate_proceed, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String languageSelected = spinner.getSelectedItem().toString();
TokensTranslations tokensTranslations = new TokensTranslations();
tokensTranslations.initailize();
String token = tokensTranslations.getTranslationToken(languageSelected);
Utils.handleWebUrl(AboutActivity.this,Uri.parse("https://translatewiki.net/w/i.php?title=Special:Translate&language="+token+"&group=commons-android-strings&filter=%21translated&action=translate ?"));
}
});
builder.setNegativeButton(R.string.about_translate_cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
finish();
}
});
builder.create().show();
}
}

View file

@ -0,0 +1,105 @@
package fr.free.nrw.commons;
import java.util.HashMap;
/**
* Created by Dell on 3/16/2018.
*/
public class TokensTranslations {
HashMap<String,String> translationToken = new HashMap<String,String>();
public void initailize() {
translationToken.put("Kazakh", "ab");
translationToken.put("Afrikaans", "af");
translationToken.put("Arabic", "ar");
translationToken.put("Bengali", "as");
translationToken.put("Asturianu", "ast");
translationToken.put("azərbaycanca", "az");
translationToken.put("Bikol Central", "bcl");
translationToken.put("Bulgarain","bg");
translationToken.put("বাংলা", "bn");
translationToken.put("Brezhoneg", "br");
translationToken.put("Bosanski", "bs");
translationToken.put("català", "ca");
translationToken.put("کوردی","ckb");
translationToken.put("čeština", "cs");
translationToken.put("kaszëbsczi", "csb");
translationToken.put("Cymraeg", "cy");
translationToken.put("dansk", "da");
translationToken.put("Deutsch", "de");
translationToken.put("Zazaki", "diq");
translationToken.put("डोटेली","diq");
translationToken.put("Ελληνικά","el");
translationToken.put("euskara","eu");
translationToken.put("español", "es");
translationToken.put("فارسی","fa");
translationToken.put("suomi", "fi");
translationToken.put("føroyskt", "fo");
translationToken.put("français", "fr");
translationToken.put("Nordfriisk", "frr");
translationToken.put("galego", "gr");
translationToken.put("Hawaiʻi", "haw");
translationToken.put("עברית","he");
translationToken.put("हिन्दी","hi");
translationToken.put("Hunsrik", "hrx");
translationToken.put("hornjoserbsce", "hsb");
translationToken.put("magyar","hu");
translationToken.put("interlingua","ia");
translationToken.put("Bahasa Indonesia", "id");
translationToken.put("íslenska","is");
translationToken.put("Italian","it");
translationToken.put("japanese","ja");
translationToken.put("Basa Jawa","jv");
translationToken.put("ქართული", "ka");
translationToken.put("Taqbaylit","kab");
translationToken.put(" ភាសាខ្មែរ","km");
translationToken.put("ಕನ್ನಡ", "kn");
translationToken.put("한국어", "ko");
translationToken.put("къарачай-малкъар","krc");
translationToken.put("Кыргызча","ky");
translationToken.put("latina","la");
translationToken.put("Lëtzebuergesch","lb");
translationToken.put("lietuvių", "lt");
translationToken.put("latviešu","lv");
translationToken.put("Malagasy","mg");
translationToken.put("македонски", "mk");
translationToken.put("മലയാളം","ml");
translationToken.put("монгол","mn");
translationToken.put("मराठी","mr");
translationToken.put("Bahasa Melayu","ms");
translationToken.put("Malti","mt");
translationToken.put("norsk bokmål", "nb");
translationToken.put("नेपाली","ne");
translationToken.put("Nederlands","nl");
translationToken.put("occitan","oc");
translationToken.put("ଓଡ଼ିଆ","or");
translationToken.put("ਪੰਜਾਬੀ","pa");
translationToken.put("polsk", "pl");
translationToken.put("Piemontèis","pms");
translationToken.put("پښتو","ps");
translationToken.put("português","pt");
translationToken.put("română","ro");
translationToken.put("русский","ru");
translationToken.put(" سنڌي","sd");
translationToken.put(" සිංහල","si");
translationToken.put("slovenčina","sk");
translationToken.put(" سرائیکی","skr");
translationToken.put("Basa Sunda","su");
translationToken.put("svenska","sv");
translationToken.put("தமிழ்", "ta");
translationToken.put("ತುಳು", "tcy");
translationToken.put(" తెలుగు","te");
translationToken.put(" ไทย","th");
translationToken.put("Türkçe","tr");
translationToken.put("українська","uk");
translationToken.put("اردو","ur");
translationToken.put("Tiếng Việt","vi");
translationToken.put(" მარგალური", "xmf");
translationToken.put("ייִדיש","yi");
}
public String getTranslationToken ( String language){
return translationToken.get(language);
}
}

View file

@ -73,6 +73,8 @@ public class CategorizationFragment extends CommonsDaggerSupportFragment {
@Inject MediaWikiApi mwApi;
@Inject @Named("default_preferences") SharedPreferences prefs;
@Inject @Named("prefs") SharedPreferences prefsPrefs;
@Inject @Named("direct_nearby_upload_prefs") SharedPreferences directPrefs;
@Inject CategoryDao categoryDao;
private RVRendererAdapter<CategoryItem> categoriesAdapter;
@ -80,6 +82,7 @@ public class CategorizationFragment extends CommonsDaggerSupportFragment {
private HashMap<String, ArrayList<String>> categoriesCache;
private List<CategoryItem> selectedCategories = new ArrayList<>();
private TitleTextWatcher textWatcher = new TitleTextWatcher();
private boolean hasDirectCategories = false;
private final CategoriesAdapterFactory adapterFactory = new CategoriesAdapterFactory(item -> {
if (item.isSelected()) {
@ -128,7 +131,7 @@ public class CategorizationFragment extends CommonsDaggerSupportFragment {
}
public void hideKeyboard(View view) {
InputMethodManager inputMethodManager =(InputMethodManager)getActivity().getSystemService(Activity.INPUT_METHOD_SERVICE);
InputMethodManager inputMethodManager = (InputMethodManager) getActivity().getSystemService(Activity.INPUT_METHOD_SERVICE);
inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
@ -223,7 +226,7 @@ public class CategorizationFragment extends CommonsDaggerSupportFragment {
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
s -> categoriesAdapter.add(s),
Timber::e,
Timber::e,
() -> {
categoriesAdapter.notifyDataSetChanged();
categoriesSearchInProgress.setVisibility(View.GONE);
@ -258,9 +261,34 @@ public class CategorizationFragment extends CommonsDaggerSupportFragment {
}
private Observable<CategoryItem> defaultCategories() {
return gpsCategories()
.concatWith(titleCategories())
.concatWith(recentCategories());
Observable<CategoryItem> directCat = directCategories();
if (hasDirectCategories) {
Timber.d("Image has direct Cat");
return directCat
.concatWith(gpsCategories())
.concatWith(titleCategories())
.concatWith(recentCategories());
}
else {
Timber.d("Image has no direct Cat");
return gpsCategories()
.concatWith(titleCategories())
.concatWith(recentCategories());
}
}
private Observable<CategoryItem> directCategories() {
String directCategory = directPrefs.getString("Category", "");
List<String> categoryList = new ArrayList<>();
Timber.d("Direct category found: " + directCategory);
if (!directCategory.equals("")) {
hasDirectCategories = true;
categoryList.add(directCategory);
Timber.d("DirectCat does not equal emptyString. Direct Cat list has " + categoryList);
}
return Observable.fromIterable(categoryList).map(name -> new CategoryItem(name, false));
}
private Observable<CategoryItem> gpsCategories() {

View file

@ -25,14 +25,14 @@ import static fr.free.nrw.commons.contributions.Contribution.SOURCE_CAMERA;
import static fr.free.nrw.commons.contributions.Contribution.SOURCE_GALLERY;
import static fr.free.nrw.commons.upload.UploadService.EXTRA_SOURCE;
class ContributionController {
public class ContributionController {
private static final int SELECT_FROM_GALLERY = 1;
private static final int SELECT_FROM_CAMERA = 2;
private Fragment fragment;
ContributionController(Fragment fragment) {
public ContributionController(Fragment fragment) {
this.fragment = fragment;
}
@ -61,7 +61,7 @@ class ContributionController {
}
}
void startCameraCapture() {
public void startCameraCapture() {
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
lastGeneratedCaptureUri = reGenerateImageCaptureUriInCache();
@ -70,6 +70,9 @@ class ContributionController {
requestWritePermission(fragment.getContext(), takePictureIntent, lastGeneratedCaptureUri);
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, lastGeneratedCaptureUri);
if (!fragment.isAdded()) {
return;
}
fragment.startActivityForResult(takePictureIntent, SELECT_FROM_CAMERA);
}
@ -77,11 +80,19 @@ class ContributionController {
//FIXME: Starts gallery (opens Google Photos)
Intent pickImageIntent = new Intent(ACTION_GET_CONTENT);
pickImageIntent.setType("image/*");
// See https://stackoverflow.com/questions/22366596/android-illegalstateexception-fragment-not-attached-to-activity-webview
if (!fragment.isAdded()) {
Timber.d("Fragment is not added, startActivityForResult cannot be called");
return;
}
Timber.d("startGalleryPick() called with pickImageIntent");
fragment.startActivityForResult(pickImageIntent, SELECT_FROM_GALLERY);
}
void handleImagePicked(int requestCode, Intent data) {
public void handleImagePicked(int requestCode, Intent data, boolean isDirectUpload) {
FragmentActivity activity = fragment.getActivity();
Timber.d("handleImagePicked() called with onActivityResult()");
Intent shareIntent = new Intent(activity, ShareActivity.class);
shareIntent.setAction(ACTION_SEND);
switch (requestCode) {
@ -91,6 +102,9 @@ class ContributionController {
shareIntent.setType(activity.getContentResolver().getType(imageData));
shareIntent.putExtra(EXTRA_STREAM, imageData);
shareIntent.putExtra(EXTRA_SOURCE, SOURCE_GALLERY);
if (isDirectUpload) {
shareIntent.putExtra("isDirectUpload", true);
}
break;
case SELECT_FROM_CAMERA:
//FIXME: Find out appropriate mime type
@ -99,6 +113,10 @@ class ContributionController {
shareIntent.setType("image/jpeg");
shareIntent.putExtra(EXTRA_STREAM, lastGeneratedCaptureUri);
shareIntent.putExtra(EXTRA_SOURCE, SOURCE_CAMERA);
if (isDirectUpload) {
shareIntent.putExtra("isDirectUpload", true);
}
break;
default:
break;
@ -122,5 +140,4 @@ class ContributionController {
lastGeneratedCaptureUri = savedInstanceState.getParcelable("lastGeneratedCaptureURI");
}
}
}

View file

@ -117,7 +117,7 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment {
if (resultCode == RESULT_OK) {
Timber.d("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s",
requestCode, resultCode, data);
controller.handleImagePicked(requestCode, data);
controller.handleImagePicked(requestCode, data, false);
} else {
Timber.e("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s",
requestCode, resultCode, data);

View file

@ -15,6 +15,7 @@ import fr.free.nrw.commons.contributions.ContributionsSyncAdapter;
import fr.free.nrw.commons.delete.DeleteTask;
import fr.free.nrw.commons.modifications.ModificationsSyncAdapter;
import fr.free.nrw.commons.settings.SettingsFragment;
import fr.free.nrw.commons.nearby.PlaceRenderer;
@Singleton
@Component(modules = {
@ -44,6 +45,8 @@ public interface CommonsApplicationComponent extends AndroidInjector<Application
@Override
void inject(ApplicationlessInjection instance);
void inject(PlaceRenderer placeRenderer);
@Component.Builder
@SuppressWarnings({"WeakerAccess", "unused"})
interface Builder {

View file

@ -33,7 +33,6 @@ public class CommonsApplicationModule {
public static final String CATEGORY_AUTHORITY = "fr.free.nrw.commons.categories.contentprovider";
public static final long OK_HTTP_CACHE_SIZE = 10 * 1024 * 1024;
private CommonsApplication application;
private Context applicationContext;
public CommonsApplicationModule(Context applicationContext) {
@ -87,9 +86,13 @@ public class CommonsApplicationModule {
}
@Provides
public UploadController providesUploadController(Context context,
SessionManager sessionManager,
@Named("default_preferences") SharedPreferences sharedPreferences) {
@Named("direct_nearby_upload_prefs")
public SharedPreferences providesDirectNearbyUploadPreferences(Context context) {
return context.getSharedPreferences("direct_nearby_upload_prefs", MODE_PRIVATE);
}
@Provides
public UploadController providesUploadController(SessionManager sessionManager, @Named("default_preferences") SharedPreferences sharedPreferences, Context context) {
return new UploadController(sessionManager, context, sharedPreferences);
}

View file

@ -7,6 +7,7 @@ import fr.free.nrw.commons.contributions.ContributionsListFragment;
import fr.free.nrw.commons.media.MediaDetailFragment;
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
import fr.free.nrw.commons.nearby.NearbyListFragment;
import fr.free.nrw.commons.nearby.NearbyMapFragment;
import fr.free.nrw.commons.nearby.NoPermissionsFragment;
import fr.free.nrw.commons.settings.SettingsFragment;
import fr.free.nrw.commons.upload.MultipleUploadListFragment;
@ -31,6 +32,9 @@ public abstract class FragmentBuilderModule {
@ContributesAndroidInjector
abstract NearbyListFragment bindNearbyListFragment();
@ContributesAndroidInjector
abstract NearbyMapFragment bindNearbyMapFragment();
@ContributesAndroidInjector
abstract NoPermissionsFragment bindNoPermissionsFragment();

View file

@ -1,6 +1,7 @@
package fr.free.nrw.commons.location;
import android.location.Location;
import android.net.Uri;
import android.support.annotation.NonNull;
/**
@ -11,15 +12,15 @@ public class LatLng {
private final double latitude;
private final double longitude;
private final float accuracy;
/**
* Accepts latitude and longitude.
* North and South values are cut off at 90°
*
*
* @param latitude the latitude
* @param longitude the longitude
* @param accuracy the accuracy
*
*
* Examples:
* the Statue of Liberty is located at 40.69° N, 74.04° W
* The Statue of Liberty could be constructed as LatLng(40.69, -74.04, 1.0)
@ -43,7 +44,7 @@ public class LatLng {
public static LatLng from(@NonNull Location location) {
return new LatLng(location.getLatitude(), location.getLongitude(), location.getAccuracy());
}
/**
* creates a hash code for the longitude and longitude
*/
@ -153,4 +154,9 @@ public class LatLng {
public double getLatitude() {
return latitude;
}
public Uri getGmmIntentUri() {
return Uri.parse("geo:0,0?q=" + latitude + "," + longitude);
}
}

View file

@ -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
}
}

View file

@ -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);
}

View file

@ -0,0 +1,75 @@
package fr.free.nrw.commons.nearby;
import android.content.SharedPreferences;
import android.os.Build;
import android.support.v4.app.Fragment;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.contributions.ContributionController;
import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
class DirectUpload {
private ContributionController controller;
private Fragment fragment;
DirectUpload(Fragment fragment, ContributionController controller) {
this.fragment = fragment;
this.controller = controller;
}
void initiateCameraUpload() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (ContextCompat.checkSelfPermission(fragment.getActivity(), WRITE_EXTERNAL_STORAGE) != PERMISSION_GRANTED) {
if (fragment.getActivity().shouldShowRequestPermissionRationale(WRITE_EXTERNAL_STORAGE)) {
new AlertDialog.Builder(fragment.getActivity())
.setMessage(fragment.getActivity().getString(R.string.write_storage_permission_rationale))
.setPositiveButton("OK", (dialog, which) -> {
fragment.getActivity().requestPermissions(new String[]{WRITE_EXTERNAL_STORAGE}, 3);
dialog.dismiss();
})
.setNegativeButton("Cancel", null)
.create()
.show();
} else {
fragment.getActivity().requestPermissions(new String[]{WRITE_EXTERNAL_STORAGE}, 3);
}
} else {
controller.startCameraCapture();
}
} else {
controller.startCameraCapture();
}
}
void initiateGalleryUpload() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (ContextCompat.checkSelfPermission(fragment.getActivity(), READ_EXTERNAL_STORAGE) != PERMISSION_GRANTED) {
if (fragment.getActivity().shouldShowRequestPermissionRationale(READ_EXTERNAL_STORAGE)) {
new AlertDialog.Builder(fragment.getActivity())
.setMessage(fragment.getActivity().getString(R.string.read_storage_permission_rationale))
.setPositiveButton("OK", (dialog, which) -> {
fragment.getActivity().requestPermissions(new String[]{READ_EXTERNAL_STORAGE}, 1);
dialog.dismiss();
})
.setNegativeButton("Cancel", null)
.create()
.show();
} else {
fragment.getActivity().requestPermissions(new String[]{READ_EXTERNAL_STORAGE},
1);
}
} else {
controller.startGalleryPick();
}
}
else {
controller.startGalleryPick();
}
}
}

View file

@ -1,21 +1,20 @@
package fr.free.nrw.commons.nearby;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
import android.support.design.widget.BottomSheetBehavior;
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;
import android.view.MenuItem;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.Toast;
@ -28,28 +27,37 @@ 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;
public class NearbyActivity extends NavigationBaseActivity implements LocationUpdateListener {
private static final int LOCATION_REQUEST = 1;
private static final String MAP_LAST_USED_PREFERENCE = "mapLastUsed";
@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;
@Inject
LocationServiceManager locationManager;
@Inject
@ -57,35 +65,57 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
private LatLng curLatLang;
private Bundle bundle;
private SharedPreferences sharedPreferences;
private NearbyActivityMode viewMode;
private Disposable placesDisposable;
private boolean lockNearbyView; //Determines if the nearby places needs to be refreshed
@BindView(R.id.swipe_container) SwipeRefreshLayout swipeLayout;
private BottomSheetBehavior bottomSheetBehavior; // Behavior for list bottom sheet
private BottomSheetBehavior bottomSheetBehaviorForDetails; // Behavior for details bottom sheet
private 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();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
setContentView(R.layout.activity_nearby);
ButterKnife.bind(this);
resumeFragment();
bundle = new Bundle();
initBottomSheetBehaviour();
initDrawer();
initViewState();
swipeLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
lockNearbyView(false);
refreshView(true);
}
});
}
private void initViewState() {
if (sharedPreferences.getBoolean(MAP_LAST_USED_PREFERENCE, false)) {
viewMode = NearbyActivityMode.MAP;
} else {
viewMode = NearbyActivityMode.LIST;
}
private void resumeFragment() {
// Find the retained fragment on activity restarts
nearbyMapFragment = getMapFragment();
nearbyListFragment = getListFragment();
}
private void initBottomSheetBehaviour() {
transparentView.setAlpha(0);
bottomSheet.getLayoutParams().height = getWindowManager()
.getDefaultDisplay().getHeight() / 16 * 9;
bottomSheetBehavior = BottomSheetBehavior.from(bottomSheet);
// TODO initProperBottomSheetBehavior();
bottomSheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
@Override
public void onStateChanged(View bottomSheet, int newState) {
prepareViewsForSheetPosition(newState);
}
@Override
public void onSlide(View bottomSheet, float slideOffset) {
}
});
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
bottomSheetBehaviorForDetails = BottomSheetBehavior.from(bottomSheetDetails);
bottomSheetBehaviorForDetails.setState(BottomSheetBehavior.STATE_HIDDEN);
}
@Override
@ -93,11 +123,6 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu_nearby, menu);
if (viewMode.isMap()) {
MenuItem item = menu.findItem(R.id.action_toggle_view);
item.setIcon(viewMode.getIcon());
}
return super.onCreateOptionsMenu(menu);
}
@ -105,14 +130,9 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
public boolean onOptionsItemSelected(MenuItem item) {
// Handle item selection
switch (item.getItemId()) {
case R.id.action_refresh:
lockNearbyView(false);
refreshView(true);
return true;
case R.id.action_toggle_view:
viewMode = viewMode.toggle();
item.setIcon(viewMode.getIcon());
toggleView();
case R.id.action_display_list:
bottomSheetBehaviorForDetails.setState(BottomSheetBehavior.STATE_HIDDEN);
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
return true;
default:
return super.onOptionsItemSelected(item);
@ -130,7 +150,7 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
switch (requestCode) {
case LOCATION_REQUEST: {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
refreshView(false);
refreshView(LocationServiceManager.LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED);
} else {
//If permission not granted, go to page that says Nearby Places cannot be displayed
hideProgressBar();
@ -185,7 +205,7 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
private void checkLocationPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (locationManager.isLocationPermissionGranted()) {
refreshView(false);
refreshView(LocationServiceManager.LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED);
} else {
// Should we show an explanation?
if (locationManager.isPermissionExplanationRequired(this)) {
@ -211,7 +231,7 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
}
}
} else {
refreshView(false);
refreshView(LocationServiceManager.LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED);
}
}
@ -220,23 +240,15 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 1) {
Timber.d("User is back from Settings page");
refreshView(false);
refreshView(LocationServiceManager.LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED);
}
}
private void toggleView() {
if (viewMode.isMap()) {
setMapFragment();
} else {
setListFragment();
}
sharedPreferences.edit().putBoolean(MAP_LAST_USED_PREFERENCE, viewMode.isMap()).apply();
}
@Override
protected void onStart() {
super.onStart();
locationManager.addLocationListener(this);
locationManager.registerLocationManager();
}
@Override
@ -261,21 +273,34 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
checkGps();
}
@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(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();
}
}
/**
* This method should be the single point to load/refresh nearby places
*
* @param isHardRefresh Should display a toast if the location hasn't changed
* @param locationChangeType defines if location shanged significantly or slightly
*/
private void refreshView(boolean isHardRefresh) {
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
if (isHardRefresh) {
ViewUtil.showSnackbar(findViewById(R.id.container), R.string.nearby_location_has_not_changed);
}
return;
}
curLatLang = lastLocation;
@ -285,20 +310,34 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
return;
}
progressBar.setVisibility(View.VISIBLE);
placesDisposable = Observable.fromCallable(() -> nearbyController
.loadAttractionsFromLocation(curLatLang, this))
.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<Place> placeList) {
private void populatePlaces(NearbyController.NearbyPlacesInfo nearbyPlacesInfo) {
List<Place> 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) {
ViewUtil.showSnackbar(findViewById(R.id.container), R.string.no_nearby);
@ -307,16 +346,20 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
bundle.clear();
bundle.putString("PlaceList", gsonPlaceList);
bundle.putString("CurLatLng", gsonCurLatLng);
bundle.putString("BoundaryCoord", gsonBoundaryCoordinates);
lockNearbyView(true);
// Begin the transaction
if (viewMode.isMap()) {
// First time to init fragments
if (nearbyMapFragment == null) {
lockNearbyView(true);
setMapFragment();
} else {
setListFragment();
hideProgressBar();
lockNearbyView(false);
} else {
// There are fragments, just update the map and list
updateMapFragment(false);
updateListFragment();
}
swipeLayout.setRefreshing(false);
hideProgressBar();
}
private void lockNearbyView(boolean lock) {
@ -337,14 +380,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();
}
@ -353,14 +474,25 @@ 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, fragment, fragment.getClass().getSimpleName());
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(false);
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) {
// TODO
}
}

View file

@ -1,30 +0,0 @@
package fr.free.nrw.commons.nearby;
import android.support.annotation.DrawableRes;
import fr.free.nrw.commons.R;
enum NearbyActivityMode {
MAP(R.drawable.ic_list_white_24dp),
LIST(R.drawable.ic_map_white_24dp);
@DrawableRes
private final int icon;
NearbyActivityMode(int icon) {
this.icon = icon;
}
@DrawableRes
public int getIcon() {
return icon;
}
public NearbyActivityMode toggle() {
return isMap() ? LIST : MAP;
}
public boolean isMap() {
return MAP.equals(this);
}
}

View file

@ -1,6 +1,7 @@
package fr.free.nrw.commons.nearby;
import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
import com.pedrogomez.renderers.ListAdapteeCollection;
import com.pedrogomez.renderers.RVRendererAdapter;
@ -9,18 +10,32 @@ import com.pedrogomez.renderers.RendererBuilder;
import java.util.Collections;
import java.util.List;
class NearbyAdapterFactory {
private PlaceRenderer.PlaceClickedListener listener;
import fr.free.nrw.commons.contributions.ContributionController;
NearbyAdapterFactory(@NonNull PlaceRenderer.PlaceClickedListener listener) {
this.listener = listener;
class NearbyAdapterFactory {
private Fragment fragment;
private ContributionController controller;
NearbyAdapterFactory(){
}
NearbyAdapterFactory(Fragment fragment, ContributionController controller) {
this.fragment = fragment;
this.controller = controller;
}
public RVRendererAdapter<Place> create(List<Place> placeList) {
RendererBuilder<Place> builder = new RendererBuilder<Place>()
.bind(Place.class, new PlaceRenderer(listener));
.bind(Place.class, new PlaceRenderer(fragment, controller));
ListAdapteeCollection<Place> collection = new ListAdapteeCollection<>(
placeList != null ? placeList : Collections.<Place>emptyList());
placeList != null ? placeList : Collections.emptyList());
return new RVRendererAdapter<>(builder, collection);
}
}
public void updateAdapterData(List<Place> newPlaceList, RVRendererAdapter<Place> rendererAdapter) {
rendererAdapter.notifyDataSetChanged();
rendererAdapter.diffUpdate(newPlaceList);
}
}

View file

@ -37,7 +37,7 @@ public class NearbyBaseMarker extends BaseMarkerOptions<NearbyMarker, NearbyBase
.registerTypeAdapter(Uri.class, new UriDeserializer())
.create();
position((LatLng) in.readParcelable(LatLng.class.getClassLoader()));
position(in.readParcelable(LatLng.class.getClassLoader()));
snippet(in.readString());
String iconId = in.readString();
Bitmap iconBitmap = in.readParcelable(Bitmap.class.getClassLoader());

View file

@ -41,30 +41,54 @@ public class NearbyController {
* Prepares Place list to make their distance information update later.
*
* @param curLatLng current location for user
* @param context context
* @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<Place> loadAttractionsFromLocation(LatLng curLatLng, Context context) {
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<Place> places = prefs.getBoolean("useWikidata", true)
? nearbyPlaces.getFromWikidataQuery(curLatLng, Locale.getDefault().getLanguage())
: nearbyPlaces.getFromWikiNeedsPictures();
Timber.d("Sorting places by distance...");
final Map<Place, Double> distances = new HashMap<>();
for (Place place : places) {
distances.put(place, computeDistanceBetween(place.location, curLatLng));
}
Collections.sort(places,
(lhs, rhs) -> {
double lhsDistance = distances.get(lhs);
double rhsDistance = distances.get(rhs);
return (int) (lhsDistance - rhsDistance);
List<Place> 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<Place, Double> 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;
}
);
return places;
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) -> {
double lhsDistance = distances.get(lhs);
double rhsDistance = distances.get(rhs);
return (int) (lhsDistance - rhsDistance);
}
);
}
nearbyPlacesInfo.placeList = places;
nearbyPlacesInfo.boundaryCoordinates = boundaryCoordinates;
return nearbyPlacesInfo;
}
/**
@ -129,4 +153,9 @@ public class NearbyController {
}
return baseMarkerOptions;
}
public class NearbyPlacesInfo {
List<Place> placeList; // List of nearby places
LatLng[] boundaryCoordinates; // Corners of nearby area
}
}

View file

@ -1,154 +0,0 @@
package fr.free.nrw.commons.nearby;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.customtabs.CustomTabsIntent;
import android.support.v4.app.FragmentActivity;
import android.support.v4.content.ContextCompat;
import android.support.v7.widget.PopupMenu;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import butterknife.Unbinder;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.location.LatLng;
import fr.free.nrw.commons.ui.widget.OverlayDialog;
import fr.free.nrw.commons.utils.DialogUtil;
public class NearbyInfoDialog extends OverlayDialog {
private final static String ARG_TITLE = "placeTitle";
private final static String ARG_DESC = "placeDesc";
private final static String ARG_LATITUDE = "latitude";
private final static String ARG_LONGITUDE = "longitude";
private final static String ARG_SITE_LINK = "sitelink";
@BindView(R.id.link_preview_title) TextView placeTitle;
@BindView(R.id.link_preview_extract) TextView placeDescription;
@BindView(R.id.link_preview_go_button) TextView goToButton;
@BindView(R.id.link_preview_overflow_button) ImageView overflowButton;
private Unbinder unbinder;
private LatLng location;
private Sitelinks sitelinks;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.dialog_nearby_info, container, false);
unbinder = ButterKnife.bind(this, view);
initUi();
return view;
}
private void initUi() {
Bundle bundle = getArguments();
placeTitle.setText(bundle.getString(ARG_TITLE));
placeDescription.setText(bundle.getString(ARG_DESC));
location = new LatLng(bundle.getDouble(ARG_LATITUDE), bundle.getDouble(ARG_LONGITUDE), 0);
getArticleLink(bundle);
}
private void getArticleLink(Bundle bundle) {
this.sitelinks = bundle.getParcelable(ARG_SITE_LINK);
if (sitelinks == null || Uri.EMPTY.equals(sitelinks.getWikipediaLink())) {
goToButton.setVisibility(View.GONE);
}
overflowButton.setVisibility(showMenu() ? View.VISIBLE : View.GONE);
overflowButton.setOnClickListener(v -> popupMenuListener());
}
private void popupMenuListener() {
PopupMenu popupMenu = new PopupMenu(getActivity(), overflowButton);
popupMenu.inflate(R.menu.nearby_info_dialog_options);
MenuItem commonsArticle = popupMenu.getMenu()
.findItem(R.id.nearby_info_menu_commons_article);
MenuItem wikiDataArticle = popupMenu.getMenu()
.findItem(R.id.nearby_info_menu_wikidata_article);
commonsArticle.setEnabled(!sitelinks.getCommonsLink().equals(Uri.EMPTY));
wikiDataArticle.setEnabled(!sitelinks.getWikidataLink().equals(Uri.EMPTY));
popupMenu.setOnMenuItemClickListener(menuListener);
popupMenu.show();
}
private boolean showMenu() {
return !sitelinks.getCommonsLink().equals(Uri.EMPTY)
|| !sitelinks.getWikidataLink().equals(Uri.EMPTY);
}
private final PopupMenu.OnMenuItemClickListener menuListener = new PopupMenu
.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case R.id.nearby_info_menu_commons_article:
openWebView(sitelinks.getCommonsLink());
return true;
case R.id.nearby_info_menu_wikidata_article:
openWebView(sitelinks.getWikidataLink());
return true;
default:
break;
}
return false;
}
};
public static void showYourself(FragmentActivity fragmentActivity, Place place) {
NearbyInfoDialog mDialog = new NearbyInfoDialog();
Bundle bundle = new Bundle();
bundle.putString(ARG_TITLE, place.name);
bundle.putString(ARG_DESC, place.getLongDescription());
bundle.putDouble(ARG_LATITUDE, place.location.getLatitude());
bundle.putDouble(ARG_LONGITUDE, place.location.getLongitude());
bundle.putParcelable(ARG_SITE_LINK, place.siteLinks);
mDialog.setArguments(bundle);
DialogUtil.showSafely(fragmentActivity, mDialog);
}
@Override
public void onDestroyView() {
super.onDestroyView();
unbinder.unbind();
}
@OnClick(R.id.link_preview_directions_button)
void onDirectionsClick() {
//Open map app at given position
Uri gmmIntentUri = Uri.parse(
"geo:0,0?q=" + location.getLatitude() + "," + location.getLongitude());
Intent mapIntent = new Intent(Intent.ACTION_VIEW, gmmIntentUri);
if (mapIntent.resolveActivity(getActivity().getPackageManager()) != null) {
startActivity(mapIntent);
}
}
@OnClick(R.id.link_preview_go_button)
void onReadArticleClick() {
openWebView(sitelinks.getWikipediaLink());
}
private void openWebView(Uri link) {
Utils.handleWebUrl(getContext(),link);
}
@OnClick(R.id.emptyLayout)
void onCloseClicked() {
dismissAllowingStateLoss();
}
}

View file

@ -1,9 +1,11 @@
package fr.free.nrw.commons.nearby;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.annotation.NonNull;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
@ -13,18 +15,24 @@ 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;
import java.util.List;
import dagger.android.support.AndroidSupportInjection;
import dagger.android.support.DaggerFragment;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.contributions.ContributionController;
import fr.free.nrw.commons.location.LatLng;
import fr.free.nrw.commons.utils.UriDeserializer;
import timber.log.Timber;
public class NearbyListFragment extends Fragment {
import static android.app.Activity.RESULT_OK;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
public class NearbyListFragment extends DaggerFragment {
private static final Type LIST_TYPE = new TypeToken<List<Place>>() {
}.getType();
private static final Type CUR_LAT_LNG_TYPE = new TypeToken<LatLng>() {
@ -35,6 +43,7 @@ public class NearbyListFragment extends Fragment {
private NearbyAdapterFactory adapterFactory;
private RecyclerView recyclerView;
private ContributionController controller;
@Override
public void onCreate(Bundle savedInstanceState) {
@ -54,9 +63,11 @@ public class NearbyListFragment extends Fragment {
Bundle savedInstanceState) {
Timber.d("NearbyListFragment created");
View view = inflater.inflate(R.layout.fragment_nearby, container, false);
recyclerView = (RecyclerView) view.findViewById(R.id.listView);
recyclerView = view.findViewById(R.id.listView);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
adapterFactory = new NearbyAdapterFactory(place -> NearbyInfoDialog.showYourself(getActivity(), place));
controller = new ContributionController(this);
adapterFactory = new NearbyAdapterFactory(this, controller);
return view;
}
@ -64,9 +75,19 @@ public class NearbyListFragment extends Fragment {
public void onViewCreated(View view, Bundle savedInstanceState) {
// Check that this is the first time view is created,
// to avoid double list when screen orientation changed
Bundle bundle = this.getArguments();
recyclerView.setAdapter(adapterFactory.create(getPlaceListFromBundle(bundle)));
}
public void updateNearbyListSignificantly() {
Bundle bundle = this.getArguments();
adapterFactory.updateAdapterData(getPlaceListFromBundle(bundle),
(RVRendererAdapter<Place>) recyclerView.getAdapter());
}
private List<Place> getPlaceListFromBundle(Bundle bundle) {
List<Place> placeList = Collections.emptyList();
Bundle bundle = this.getArguments();
if (bundle != null) {
String gsonPlaceList = bundle.getString("PlaceList", "[]");
placeList = gson.fromJson(gsonPlaceList, LIST_TYPE);
@ -77,6 +98,46 @@ public class NearbyListFragment extends Fragment {
placeList = NearbyController.loadAttractionsFromLocationToPlaces(curLatLng, placeList);
}
recyclerView.setAdapter(adapterFactory.create(placeList));
return placeList;
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
Timber.d("onRequestPermissionsResult: req code = " + " perm = " + permissions + " grant =" + grantResults);
switch (requestCode) {
// 1 = "Read external storage" allowed when gallery selected
case 1: {
if (grantResults.length > 0 && grantResults[0] == PERMISSION_GRANTED) {
Timber.d("Call controller.startGalleryPick()");
controller.startGalleryPick();
}
}
break;
// 3 = "Write external storage" allowed when camera selected
case 3: {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Timber.d("Call controller.startCameraCapture()");
controller.startCameraCapture();
}
}
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK) {
Timber.d("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s",
requestCode, resultCode, data);
controller.handleImagePicked(requestCode, data, true);
} else {
Timber.e("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s",
requestCode, resultCode, data);
}
}
}

View file

@ -1,37 +1,118 @@
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;
import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.NonNull;
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.Gravity;
import android.view.KeyEvent;
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.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import com.mapbox.mapboxsdk.Mapbox;
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.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;
import java.util.ArrayList;
import java.util.List;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.utils.UriDeserializer;
import javax.inject.Inject;
import javax.inject.Named;
import dagger.android.support.DaggerFragment;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.contributions.ContributionController;
import fr.free.nrw.commons.utils.UriDeserializer;
import timber.log.Timber;
import static android.app.Activity.RESULT_OK;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
public class NearbyMapFragment extends DaggerFragment {
public class NearbyMapFragment extends android.support.v4.app.Fragment {
private MapView mapView;
private List<NearbyBaseMarker> baseMarkerOptions;
private fr.free.nrw.commons.location.LatLng curLatLng;
public fr.free.nrw.commons.location.LatLng[] boundaryCoordinates;
private View bottomSheetList;
private View bottomSheetDetails;
private BottomSheetBehavior bottomSheetListBehavior;
private BottomSheetBehavior bottomSheetDetailsBehavior;
private LinearLayout wikipediaButton;
private LinearLayout wikidataButton;
private LinearLayout directionsButton;
private LinearLayout commonsButton;
private FloatingActionButton fabPlus;
private FloatingActionButton fabCamera;
private FloatingActionButton fabGallery;
private FloatingActionButton fabRecenter;
private View transparentView;
private TextView description;
private TextView title;
private TextView distance;
private ImageView icon;
private TextView wikipediaButtonText;
private TextView wikidataButtonText;
private TextView commonsButtonText;
private TextView directionsButtonText;
private boolean isFabOpen = false;
private Animation rotate_backward;
private Animation fab_close;
private Animation fab_open;
private Animation rotate_forward;
private ContributionController controller;
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;
public NearbyMapFragment() {
}
@ -46,18 +127,24 @@ public class NearbyMapFragment extends android.support.v4.app.Fragment {
if (bundle != null) {
String gsonPlaceList = bundle.getString("PlaceList");
String gsonLatLng = bundle.getString("CurLatLng");
Type listType = new TypeToken<List<Place>>() {}.getType();
Type listType = new TypeToken<List<Place>>() {
}.getType();
String gsonBoundaryCoordinates = bundle.getString("BoundaryCoord");
List<Place> placeList = gson.fromJson(gsonPlaceList, listType);
Type curLatLngType = new TypeToken<fr.free.nrw.commons.location.LatLng>() {}.getType();
Type curLatLngType = new TypeToken<fr.free.nrw.commons.location.LatLng>() {
}.getType();
Type gsonBoundaryCoordinatesType = new TypeToken<fr.free.nrw.commons.location.LatLng[]>() {}.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));
MapboxTelemetry.getInstance().setTelemetryEnabled(false);
setRetainInstance(true);
}
@Override
@ -73,6 +160,252 @@ public class NearbyMapFragment extends android.support.v4.app.Fragment {
return mapView;
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
this.getView().setFocusableInTouchMode(true);
this.getView().requestFocus();
this.getView().setOnKeyListener((v, keyCode, event) -> {
if (keyCode == KeyEvent.KEYCODE_BACK) {
if (bottomSheetDetailsBehavior.getState() == BottomSheetBehavior
.STATE_EXPANDED) {
bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
return true;
} else if (bottomSheetDetailsBehavior.getState() == BottomSheetBehavior
.STATE_COLLAPSED) {
bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
mapView.getMapAsync(MapboxMap::deselectMarkers);
selected = null;
return true;
}
}
return false;
});
}
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<fr.free.nrw.commons.location.LatLng>() {}.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<List<Place>>() {}.getType();
List<Place> placeList = gson.fromJson(gsonPlaceList, listType);
Type curLatLngType = new TypeToken<fr.free.nrw.commons.location.LatLng>() {}.getType();
Type gsonBoundaryCoordinatesType = new TypeToken<fr.free.nrw.commons.location.LatLng[]>() {}.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<LatLng> 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(11) // 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);
fabGallery = getActivity().findViewById(R.id.fab_galery);
fabRecenter = getActivity().findViewById(R.id.fab_recenter);
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);
transparentView = getActivity().findViewById(R.id.transparentView);
description = getActivity().findViewById(R.id.description);
title = getActivity().findViewById(R.id.title);
distance = getActivity().findViewById(R.id.category);
icon = getActivity().findViewById(R.id.icon);
wikidataButton = getActivity().findViewById(R.id.wikidataButton);
wikipediaButton = getActivity().findViewById(R.id.wikipediaButton);
directionsButton = getActivity().findViewById(R.id.directionsButton);
commonsButton = getActivity().findViewById(R.id.commonsButton);
wikidataButtonText = getActivity().findViewById(R.id.wikidataButtonText);
wikipediaButtonText = getActivity().findViewById(R.id.wikipediaButtonText);
directionsButtonText = getActivity().findViewById(R.id.directionsButtonText);
commonsButtonText = getActivity().findViewById(R.id.commonsButtonText);
}
private void setListeners() {
fabPlus.setOnClickListener(view -> animateFAB(isFabOpen));
bottomSheetDetails.setOnClickListener(view -> {
if (bottomSheetDetailsBehavior.getState() == BottomSheetBehavior.STATE_COLLAPSED) {
bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
} else {
bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
}
});
fabRecenter.setOnClickListener(view -> {
if (curLatLng != null) {
mapView.getMapAsync(mapboxMap -> {
CameraPosition position = new CameraPosition.Builder()
.target(new LatLng(curLatLng.getLatitude(), curLatLng.getLongitude())) // Sets the new camera position
.zoom(11) // Sets the zoom
.build(); // Creates a CameraPosition from the builder
mapboxMap.animateCamera(CameraUpdateFactory
.newCameraPosition(position), 1000);
});
}
});
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) {
if (slideOffset >= 0) {
transparentView.setAlpha(slideOffset);
if (slideOffset == 1) {
transparentView.setClickable(true);
} else if (slideOffset == 0) {
transparentView.setClickable(false);
}
}
}
});
bottomSheetListBehavior.setBottomSheetCallback(new BottomSheetBehavior
.BottomSheetCallback() {
@Override
public void onStateChanged(@NonNull View bottomSheet, int newState) {
if (newState == BottomSheetBehavior.STATE_EXPANDED) {
bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
updateMapCameraAccordingToBottomSheet(true);
} else {
updateMapCameraAccordingToBottomSheet(false);
}
}
@Override
public void onSlide(@NonNull View bottomSheet, float slideOffset) {
}
});
// Remove texts if it doesnt fit
if (wikipediaButtonText.getLineCount() > 1
|| wikidataButtonText.getLineCount() > 1
|| commonsButtonText.getLineCount() > 1
|| directionsButtonText.getLineCount() > 1) {
wikipediaButtonText.setVisibility(View.GONE);
wikidataButtonText.setVisibility(View.GONE);
commonsButtonText.setVisibility(View.GONE);
directionsButtonText.setVisibility(View.GONE);
}
}
private void setupMapView(Bundle savedInstanceState) {
MapboxMapOptions options = new MapboxMapOptions()
.styleUrl(Style.OUTDOORS)
@ -86,21 +419,13 @@ public class NearbyMapFragment extends android.support.v4.app.Fragment {
// create map
mapView = new MapView(getActivity(), options);
mapView.onCreate(savedInstanceState);
mapView.getMapAsync(mapboxMap -> {
mapboxMap.addMarkers(baseMarkerOptions);
mapboxMap.setOnMarkerClickListener(marker -> {
if (marker instanceof NearbyMarker) {
NearbyMarker nearbyMarker = (NearbyMarker) marker;
Place place = nearbyMarker.getNearbyBaseMarker().getPlace();
NearbyInfoDialog.showYourself(getActivity(), place);
}
return false;
});
addCurrentLocationMarker(mapboxMap);
mapView.getMapAsync(new OnMapReadyCallback() {
@Override
public void onMapReady(MapboxMap mapboxMap) {
NearbyMapFragment.this.mapboxMap = mapboxMap;
updateMapSignificantly();
}
});
mapView.setStyleUrl("asset://mapstyle.json");
}
@ -109,23 +434,67 @@ public class NearbyMapFragment extends android.support.v4.app.Fragment {
* 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
}
Icon icon = IconFactory.getInstance(getContext()).fromResource(R.drawable.current_location_marker);
MarkerOptions currentLocationMarkerOptions = new MarkerOptions()
.position(new LatLng(curLatLng.getLatitude(), curLatLng.getLongitude()));
mapboxMap.addMarker(currentLocationMarker);
currentLocationMarkerOptions.setIcon(icon); // Set custom icon
currentLocationMarker = mapboxMap.addMarker(currentLocationMarkerOptions);
List<LatLng> 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);
}
});
mapView.getMapAsync(mapboxMap -> {
mapboxMap.addMarkers(baseMarkerOptions);
fabRecenter.setVisibility(View.VISIBLE);
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,
@ -147,10 +516,230 @@ public class NearbyMapFragment extends android.support.v4.app.Fragment {
double nodeLatitude = centerLat + radiusLat * Math.sin(theta);
circle.add(new LatLng(nodeLatitude, nodeLongitude));
}
return circle;
}
public void prepareViewsForSheetPosition(int bottomSheetState) {
switch (bottomSheetState) {
case (BottomSheetBehavior.STATE_COLLAPSED):
closeFabs(isFabOpen);
if (!fabPlus.isShown()) showFAB();
this.getView().requestFocus();
break;
case (BottomSheetBehavior.STATE_EXPANDED):
this.getView().requestFocus();
break;
case (BottomSheetBehavior.STATE_HIDDEN):
mapView.getMapAsync(MapboxMap::deselectMarkers);
transparentView.setClickable(false);
transparentView.setAlpha(0);
closeFabs(isFabOpen);
hideFAB();
this.getView().requestFocus();
break;
}
}
private void hideFAB() {
removeAnchorFromFABs(fabPlus);
fabPlus.hide();
removeAnchorFromFABs(fabCamera);
fabCamera.hide();
removeAnchorFromFABs(fabGallery);
fabGallery.hide();
}
/*
* We are not able to hide FABs without removing anchors, this method removes anchors
* */
private void removeAnchorFromFABs(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);
}
private void showFAB() {
addAnchorToBigFABs(fabPlus, getActivity().findViewById(R.id.bottom_sheet_details).getId());
fabPlus.show();
addAnchorToSmallFABs(fabGallery, getActivity().findViewById(R.id.empty_view).getId());
addAnchorToSmallFABs(fabCamera, getActivity().findViewById(R.id.empty_view1).getId());
}
/*
* Add amnchors back before making them visible again.
* */
private 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 amnchors back before making them visible again. Big and small fabs have different anchor
* gravities, therefore the are two methods.
* */
private 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);
}
private void passInfoToSheet(Place place) {
this.place = place;
wikipediaButton.setEnabled(place.hasWikipediaLink());
wikipediaButton.setOnClickListener(view -> openWebView(place.siteLinks.getWikipediaLink()));
wikidataButton.setEnabled(place.hasWikidataLink());
wikidataButton.setOnClickListener(view -> openWebView(place.siteLinks.getWikidataLink()));
directionsButton.setOnClickListener(view -> {
//Open map app at given position
Intent mapIntent = new Intent(Intent.ACTION_VIEW, place.location.getGmmIntentUri());
if (mapIntent.resolveActivity(getActivity().getPackageManager()) != null) {
startActivity(mapIntent);
}
});
commonsButton.setEnabled(place.hasCommonsLink());
commonsButton.setOnClickListener(view -> openWebView(place.siteLinks.getCommonsLink()));
icon.setImageResource(place.getLabel().getIcon());
title.setText(place.name);
distance.setText(place.distance);
description.setText(place.getLongDescription());
title.setText(place.name.toString());
distance.setText(place.distance.toString());
fabCamera.setOnClickListener(view -> {
if (fabCamera.isShown()) {
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();
}
});
fabGallery.setOnClickListener(view -> {
if (fabGallery.isShown()) {
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
}
});
}
void storeSharedPrefs() {
SharedPreferences.Editor editor = directPrefs.edit();
editor.putString("Title", place.getName());
editor.putString("Desc", place.getLongDescription());
editor.putString("Category", place.getCategory());
editor.apply();
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
Timber.d("onRequestPermissionsResult: req code = " + " perm = " + permissions + " grant =" + grantResults);
switch (requestCode) {
// 1 = "Read external storage" allowed when gallery selected
case 1: {
if (grantResults.length > 0 && grantResults[0] == PERMISSION_GRANTED) {
Timber.d("Call controller.startGalleryPick()");
controller.startGalleryPick();
}
}
break;
// 3 = "Write external storage" allowed when camera selected
case 3: {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Timber.d("Call controller.startCameraCapture()");
controller.startCameraCapture();
}
}
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK) {
Timber.d("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s",
requestCode, resultCode, data);
controller.handleImagePicked(requestCode, data, true);
} else {
Timber.e("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s",
requestCode, resultCode, data);
}
}
private void openWebView(Uri link) {
Intent browserIntent = new Intent(Intent.ACTION_VIEW, link);
startActivity(browserIntent);
}
private void animateFAB(boolean isFabOpen) {
this.isFabOpen = !isFabOpen;
if (fabPlus.isShown()){
if (isFabOpen) {
fabPlus.startAnimation(rotate_backward);
fabCamera.startAnimation(fab_close);
fabGallery.startAnimation(fab_close);
fabCamera.hide();
fabGallery.hide();
} else {
fabPlus.startAnimation(rotate_forward);
fabCamera.startAnimation(fab_open);
fabGallery.startAnimation(fab_open);
fabCamera.show();
fabGallery.show();
}
this.isFabOpen=!isFabOpen;
}
}
private void closeFabs ( boolean isFabOpen){
if (isFabOpen) {
fabPlus.startAnimation(rotate_backward);
fabCamera.startAnimation(fab_close);
fabGallery.startAnimation(fab_close);
fabCamera.hide();
fabGallery.hide();
this.isFabOpen = !isFabOpen;
}
}
@Override
public void onStart() {
if (mapView != null) {
@ -169,10 +758,14 @@ public class NearbyMapFragment extends android.support.v4.app.Fragment {
@Override
public void onResume() {
super.onResume();
if (mapView != null) {
mapView.onResume();
}
super.onResume();
initViews();
setListeners();
transparentView.setClickable(false);
transparentView.setAlpha(0);
}
@Override
@ -190,4 +783,19 @@ public class NearbyMapFragment extends android.support.v4.app.Fragment {
}
super.onDestroyView();
}
private static class LatLngEvaluator implements TypeEvaluator<LatLng> {
// 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;
}
}
}

View file

@ -1,7 +1,6 @@
package fr.free.nrw.commons.nearby;
import android.net.Uri;
import android.os.StrictMode;
import java.io.BufferedReader;
import java.io.IOException;
@ -9,6 +8,7 @@ import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
@ -30,7 +30,6 @@ public class NearbyPlaces {
private static final Uri WIKIDATA_QUERY_UI_URL = Uri.parse("https://query.wikidata.org/");
private final String wikidataQuery;
private double radius = INITIAL_RADIUS;
private List<Place> places;
public NearbyPlaces() {
try {
@ -102,13 +101,17 @@ public class NearbyPlaces {
}
String[] fields = line.split("\t");
Timber.v("Fields: " + Arrays.toString(fields));
String point = fields[0];
String wikiDataLink = Utils.stripLocalizedString(fields[1]);
String name = Utils.stripLocalizedString(fields[2]);
String type = Utils.stripLocalizedString(fields[4]);
String icon = fields[5];
String wikipediaSitelink = Utils.stripLocalizedString(fields[7]);
String commonsSitelink = Utils.stripLocalizedString(fields[8]);
String wikiDataLink = Utils.stripLocalizedString(fields[1]);
String icon = fields[5];
String category = Utils.stripLocalizedString(fields[9]);
Timber.v("Name: " + name + ", type: " + type + ", category: " + category + ", wikipediaSitelink: " + wikipediaSitelink + ", commonsSitelink: " + commonsSitelink);
double latitude;
double longitude;
@ -130,6 +133,7 @@ public class NearbyPlaces {
type, // details
Uri.parse(icon),
new LatLng(latitude, longitude, 0),
category,
new Sitelinks.Builder()
.setWikipediaLink(wikipediaSitelink)
.setCommonsLink(commonsSitelink)
@ -141,66 +145,4 @@ public class NearbyPlaces {
return places;
}
List<Place> getFromWikiNeedsPictures() {
if (places != null) {
return places;
} else {
try {
places = new ArrayList<>();
StrictMode.ThreadPolicy policy
= new StrictMode.ThreadPolicy.Builder().permitAll().build();
StrictMode.setThreadPolicy(policy);
URL file = new URL("https://tools.wmflabs.org/wiki-needs-pictures/data/data.csv");
BufferedReader in = new BufferedReader(new InputStreamReader(file.openStream()));
boolean firstLine = true;
String line;
Timber.d("Reading from CSV file...");
while ((line = in.readLine()) != null) {
// Skip CSV header.
if (firstLine) {
firstLine = false;
continue;
}
String[] fields = line.split(",");
String name = Utils.stripLocalizedString(fields[0]);
double latitude;
double longitude;
try {
latitude = Double.parseDouble(fields[1]);
} catch (NumberFormatException e) {
latitude = 0;
}
try {
longitude = Double.parseDouble(fields[2]);
} catch (NumberFormatException e) {
longitude = 0;
}
String type = fields[3];
places.add(new Place(
name,
Place.Label.fromText(type), // list
type, // details
null,
new LatLng(latitude, longitude, 0),
new Sitelinks.Builder().build()
));
}
in.close();
} catch (IOException e) {
Timber.d(e.toString());
}
}
return places;
}
}

View file

@ -17,6 +17,7 @@ public class Place {
private final String longDescription;
private final Uri secondaryImageUrl;
public final LatLng location;
private final String category;
public Bitmap image;
public Bitmap secondaryImage;
@ -25,27 +26,42 @@ public class Place {
public Place(String name, Label label, String longDescription,
Uri secondaryImageUrl, LatLng location, Sitelinks siteLinks) {
Uri secondaryImageUrl, LatLng location, String category, Sitelinks siteLinks) {
this.name = name;
this.label = label;
this.longDescription = longDescription;
this.secondaryImageUrl = secondaryImageUrl;
this.location = location;
this.category = category;
this.siteLinks = siteLinks;
}
public String getName() { return name; }
public Label getLabel() {
return label;
}
public String getLongDescription() {
return longDescription;
}
public String getLongDescription() { return longDescription; }
public String getCategory() {return category; }
public void setDistance(String distance) {
this.distance = distance;
}
public boolean hasWikipediaLink() {
return !(siteLinks == null || Uri.EMPTY.equals(siteLinks.getWikipediaLink()));
}
public boolean hasWikidataLink() {
return !(siteLinks == null || Uri.EMPTY.equals(siteLinks.getWikidataLink()));
}
public boolean hasCommonsLink() {
return !(siteLinks == null || Uri.EMPTY.equals(siteLinks.getCommonsLink()));
}
@Override
public boolean equals(Object o) {
if (o instanceof Place) {

View file

@ -1,32 +1,79 @@
package fr.free.nrw.commons.nearby;
import android.support.annotation.NonNull;
import android.content.Intent;
import android.net.Uri;
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;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.pedrogomez.renderers.Renderer;
import java.util.ArrayList;
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.contributions.ContributionController;
import fr.free.nrw.commons.di.ApplicationlessInjection;
import timber.log.Timber;
public class PlaceRenderer extends Renderer<Place> {
class PlaceRenderer extends Renderer<Place> {
@BindView(R.id.tvName) TextView tvName;
@BindView(R.id.tvDesc) TextView tvDesc;
@BindView(R.id.distance) TextView distance;
@BindView(R.id.icon) ImageView icon;
private final PlaceClickedListener listener;
@BindView(R.id.buttonLayout) LinearLayout buttonLayout;
@BindView(R.id.cameraButton) LinearLayout cameraButton;
PlaceRenderer(@NonNull PlaceClickedListener listener) {
this.listener = listener;
@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<LinearLayout> openedItems;
private Place place;
private Fragment fragment;
private ContributionController controller;
@Inject @Named("prefs") SharedPreferences prefs;
@Inject @Named("direct_nearby_upload_prefs") SharedPreferences directPrefs;
public PlaceRenderer(){
openedItems = new ArrayList<>();
}
public PlaceRenderer(Fragment fragment, ContributionController controller) {
this.fragment = fragment;
this.controller = controller;
openedItems = new ArrayList<>();
}
@Override
protected View inflate(LayoutInflater layoutInflater, ViewGroup viewGroup) {
return layoutInflater.inflate(R.layout.item_place, viewGroup, false);
view = layoutInflater.inflate(R.layout.item_place, viewGroup, false);
return view;
}
@Override
@ -36,15 +83,67 @@ class PlaceRenderer extends Renderer<Place> {
@Override
protected void hookListeners(View view) {
view.setOnClickListener(v -> listener.placeClicked(getContent()));
final View.OnClickListener listener = view12 -> {
Log.d("Renderer", "clicked");
TransitionManager.beginDelayedTransition(buttonLayout);
if(buttonLayout.isShown()){
closeLayout(buttonLayout);
}else {
openLayout(buttonLayout);
}
};
view.setOnClickListener(listener);
view.requestFocus();
view.setOnFocusChangeListener((view1, hasFocus) -> {
if (!hasFocus && buttonLayout.isShown()) {
closeLayout(buttonLayout);
} else if (hasFocus && !buttonLayout.isShown()) {
listener.onClick(view1);
}
});
cameraButton.setOnClickListener(view2 -> {
Timber.d("Camera button tapped. Image title: " + place.getName() + "Image desc: " + place.getLongDescription());
DirectUpload directUpload = new DirectUpload(fragment, controller);
storeSharedPrefs();
directUpload.initiateCameraUpload();
});
galleryButton.setOnClickListener(view3 -> {
Timber.d("Gallery button tapped. Image title: " + place.getName() + "Image desc: " + place.getLongDescription());
DirectUpload directUpload = new DirectUpload(fragment, controller);
storeSharedPrefs();
directUpload.initiateGalleryUpload();
});
}
private void storeSharedPrefs() {
SharedPreferences.Editor editor = directPrefs.edit();
Timber.d("directPrefs stored");
editor.putString("Title", place.getName());
editor.putString("Desc", place.getLongDescription());
editor.putString("Category", place.getCategory());
editor.apply();
}
private void closeLayout(LinearLayout buttonLayout){
buttonLayout.setVisibility(View.GONE);
}
private void openLayout(LinearLayout buttonLayout){
buttonLayout.setVisibility(View.VISIBLE);
}
@Override
public void render() {
Place place = getContent();
ApplicationlessInjection.getInstance(getContext().getApplicationContext())
.getCommonsApplicationComponent().inject(this);
place = getContent();
tvName.setText(place.name);
String descriptionText = place.getLongDescription();
tvDesc.setVisibility(View.VISIBLE);
if (descriptionText.equals("?")) {
descriptionText = getContext().getString(R.string.no_description_found);
tvDesc.setVisibility(View.INVISIBLE);
@ -52,9 +151,61 @@ class PlaceRenderer extends Renderer<Place> {
tvDesc.setText(descriptionText);
distance.setText(place.distance);
icon.setImageResource(place.getLabel().getIcon());
directionsButton.setOnClickListener(view -> {
//Open map app at given position
Intent mapIntent = new Intent(Intent.ACTION_VIEW, place.location.getGmmIntentUri());
if (mapIntent.resolveActivity(view.getContext().getPackageManager()) != null) {
view.getContext().startActivity(mapIntent);
}
});
iconOverflow.setVisibility(showMenu() ? View.VISIBLE : View.GONE);
iconOverflow.setOnClickListener(v -> popupMenuListener());
}
interface PlaceClickedListener {
void placeClicked(Place place);
private void popupMenuListener() {
PopupMenu popupMenu = new PopupMenu(view.getContext(), iconOverflow);
popupMenu.inflate(R.menu.nearby_info_dialog_options);
MenuItem commonsArticle = popupMenu.getMenu()
.findItem(R.id.nearby_info_menu_commons_article);
MenuItem wikiDataArticle = popupMenu.getMenu()
.findItem(R.id.nearby_info_menu_wikidata_article);
MenuItem wikipediaArticle = popupMenu.getMenu()
.findItem(R.id.nearby_info_menu_wikipedia_article);
commonsArticle.setEnabled(place.hasCommonsLink());
wikiDataArticle.setEnabled(place.hasWikidataLink());
wikipediaArticle.setEnabled(place.hasWikipediaLink());
popupMenu.setOnMenuItemClickListener(item -> {
switch (item.getItemId()) {
case R.id.nearby_info_menu_commons_article:
openWebView(place.siteLinks.getCommonsLink());
return true;
case R.id.nearby_info_menu_wikidata_article:
openWebView(place.siteLinks.getWikidataLink());
return true;
case R.id.nearby_info_menu_wikipedia_article:
openWebView(place.siteLinks.getWikipediaLink());
return true;
default:
break;
}
return false;
});
popupMenu.show();
}
}
private void openWebView(Uri link) {
Intent browserIntent = new Intent(Intent.ACTION_VIEW, link);
view.getContext().startActivity(browserIntent);
}
private boolean showMenu() {
return place.hasCommonsLink() || place.hasWikidataLink();
}
}

View file

@ -58,7 +58,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.mwapi.EventLog;
import fr.free.nrw.commons.utils.ImageUtils;
import fr.free.nrw.commons.mwapi.MediaWikiApi;
import timber.log.Timber;
@ -116,7 +116,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.
*/
@ -214,6 +217,10 @@ public class ShareActivity
finish();
}
protected boolean isNearbyUpload() {
return isNearbyUpload;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -240,6 +247,10 @@ public class ShareActivity
} else {
source = Contribution.SOURCE_EXTERNAL;
}
if (intent.hasExtra("isDirectUpload")) {
Timber.d("This was initiated by a direct upload from Nearby");
isNearbyUpload = true;
}
mimeType = intent.getType();
}

View file

@ -1,6 +1,8 @@
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;
@ -58,6 +60,7 @@ public class SingleUploadFragment extends CommonsDaggerSupportFragment {
@BindView(R.id.licenseSpinner) Spinner licenseSpinner;
@Inject @Named("default_preferences") SharedPreferences prefs;
@Inject @Named("direct_nearby_upload_prefs") SharedPreferences directPrefs;
private String license;
private OnUploadActionInitiated uploadActionInitiatedHandler;
@ -90,7 +93,6 @@ public class SingleUploadFragment extends CommonsDaggerSupportFragment {
uploadActionInitiatedHandler.uploadActionInitiated(title, desc);
return true;
}
return super.onOptionsItemSelected(item);
}
@ -118,6 +120,18 @@ public class SingleUploadFragment extends CommonsDaggerSupportFragment {
license = prefs.getString(Prefs.DEFAULT_LICENSE, Prefs.Licenses.CC_BY_SA_3);
// If this is a direct upload from Nearby, autofill title and desc fields with the Place's values
boolean isNearbyUpload = ((ShareActivity) getActivity()).isNearbyUpload();
if (isNearbyUpload) {
String imageTitle = directPrefs.getString("Title", "");
String imageDesc = directPrefs.getString("Desc", "");
String imageCats = directPrefs.getString("Category", "");
Timber.d("Image title: " + imageTitle + ", image desc: " + imageDesc + ", image categories: " + imageCats);
titleEdit.setText(imageTitle);
descEdit.setText(imageDesc);
}
// check if this is the first time we have uploaded
if (prefs.getString("Title", "").trim().length() == 0
&& prefs.getString("Desc", "").trim().length() == 0) {
@ -278,6 +292,7 @@ public class SingleUploadFragment extends CommonsDaggerSupportFragment {
return false;
}
@SuppressLint("StringFormatInvalid")
private void setLicenseSummary(String license) {
licenseSummaryView.setText(getString(R.string.share_license_summary, getString(Utils.licenseNameFor(license))));
}

View file

@ -54,7 +54,7 @@ public class ImageUtils {
while ((checkImageRightPosition <= loadImageWidth) && (checkImageLeftPosition < checkImageRightPosition)) {
while ((checkImageBottomPosition <= loadImageHeight) && (checkImageTopPosition < checkImageBottomPosition)) {
Timber.d("left: " + checkImageLeftPosition + " right: " + checkImageRightPosition + " top: " + checkImageTopPosition + " bottom: " + checkImageBottomPosition);
Timber.v("left: " + checkImageLeftPosition + " right: " + checkImageRightPosition + " top: " + checkImageTopPosition + " bottom: " + checkImageBottomPosition);
Rect rect = new Rect(checkImageLeftPosition,checkImageTopPosition,checkImageRightPosition,checkImageBottomPosition);
totalDividedRectangles++;