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

@ -39,29 +39,10 @@ dependencies {
implementation 'io.reactivex.rxjava2:rxandroid:2.0.1'
// Because RxAndroid releases are few and far between, it is recommended you also
// explicitly depend on RxJava's latest version for bug fixes and new features.
compile 'io.reactivex.rxjava2:rxjava:2.1.2'
compile 'com.jakewharton.rxbinding2:rxbinding:2.0.0'
compile 'com.jakewharton.rxbinding2:rxbinding-support-v4:2.0.0'
compile 'com.jakewharton.rxbinding2:rxbinding-appcompat-v7:2.0.0'
compile 'com.jakewharton.rxbinding2:rxbinding-design:2.0.0'
implementation 'com.android.support:multidex:1.0.3'
compile 'com.facebook.fresco:fresco:1.3.0'
compile 'com.facebook.stetho:stetho:1.5.0'
testImplementation "org.robolectric:multidex:3.4.2"
compile 'com.android.support:multidex:1.0.3'
testCompile 'junit:junit:4.12'
testCompile 'org.robolectric:robolectric:3.7.1'
testCompile "org.robolectric:multidex:3.4.2"
testCompile 'com.squareup.okhttp3:mockwebserver:3.8.1'
androidTestCompile 'com.squareup.okhttp3:mockwebserver:3.8.1'
androidTestCompile "com.android.support:support-annotations:${project.SUPPORT_LIB_VERSION}"
androidTestCompile 'com.android.support.test.espresso:espresso-core:3.0.1'
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.1'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1'
testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1'
implementation 'io.reactivex.rxjava2:rxjava:2.1.2'
implementation 'com.jakewharton.rxbinding2:rxbinding:2.0.0'
implementation 'com.jakewharton.rxbinding2:rxbinding-support-v4:2.0.0'
@ -98,8 +79,8 @@ dependencies {
kapt "com.google.dagger:dagger-compiler:$DAGGER_VERSION"
kapt "com.google.dagger:dagger-android-processor:$DAGGER_VERSION"
compile 'com.borjabravo:readmoretextview:2.1.0'
compile 'com.android.support.constraint:constraint-layout:1.0.2'
implementation 'com.borjabravo:readmoretextview:2.1.0'
implementation 'com.android.support.constraint:constraint-layout:1.0.2'
}
android {
@ -122,6 +103,12 @@ android {
multiDexEnabled true
}
testOptions {
unitTests.all {
jvmArgs '-noverify'
}
}
sourceSets {
// use kotlin only in tests (for now)
test.java.srcDirs += 'src/test/kotlin'

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++;

View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:fillAfter="true">
<scale
android:duration="300"
android:fromXScale="1"
android:fromYScale="1"
android:interpolator="@android:anim/linear_interpolator"
android:toXScale="0.0"
android:pivotX="50%"
android:pivotY="50%"
android:toYScale="0.0" />
<alpha android:fromAlpha="1.0"
android:toAlpha="0.0"
android:interpolator="@android:anim/accelerate_interpolator"
android:duration="300"/>
</set>

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:fillAfter="true">
<scale
android:duration="300"
android:fromXScale="0.0"
android:fromYScale="0.0"
android:interpolator="@android:anim/linear_interpolator"
android:toXScale="1"
android:pivotX="50%"
android:pivotY="50%"
android:toYScale="1" />
<alpha
android:fromAlpha="0.0"
android:toAlpha="1.0"
android:interpolator="@android:anim/accelerate_interpolator"
android:duration="300"/>
</set>

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:fillAfter="true" >
<rotate android:fromDegrees="45"
android:toDegrees="0"
android:pivotX="50%"
android:pivotY="50%"
android:duration="300"
android:interpolator="@android:anim/linear_interpolator"/>
</set>

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:fillAfter="true" >
<rotate android:fromDegrees="0"
android:toDegrees="45"
android:pivotX="50%"
android:pivotY="50%"
android:duration="300"
android:interpolator="@android:anim/linear_interpolator"/>
</set>

View file

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:fillAfter="true">
<scale
android:duration="300"
android:fromXScale="1.0"
android:fromYScale="0.0"
android:interpolator="@android:anim/linear_interpolator"
android:toXScale="1.0"
android:toYScale="1.0" />
</set>

View file

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:fillAfter="true" >
<scale
android:duration="300"
android:fromXScale="1.0"
android:fromYScale="1.0"
android:interpolator="@android:anim/linear_interpolator"
android:toXScale="1.0"
android:toYScale="0.0" />
</set>

View file

@ -0,0 +1,7 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:state_enabled="false"
android:color="@color/opak_middle_grey" />
<item
android:color="@color/status_bar_blue" />
</selector>

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:state_enabled="false"
android:color="?attr/textDisabled" />
<item
android:color="?attr/textEnabled" />
</selector>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:state_pressed="true"
android:drawable="@color/pressed_button_light" />
<item
android:state_focused="true"
android:drawable="@color/focused_button_light"
/>
<item android:drawable="@android:color/transparent" />
</selector>

View file

@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0"
android:tint="@color/status_bar_blue"
>
<path
android:fillColor="#FF000000"
android:pathData="M21.71,11.29l-9,-9c-0.39,-0.39 -1.02,-0.39 -1.41,0l-9,9c-0.39,0.39 -0.39,1.02 0,1.41l9,9c0.39,0.39 1.02,0.39 1.41,0l9,-9c0.39,-0.38 0.39,-1.01 0,-1.41zM14,14.5V12h-4v3H8v-4c0,-0.55 0.45,-1 1,-1h5V7.5l3.5,3.5 -3.5,3.5z"/>
</vector>

View file

@ -0,0 +1,11 @@
<vector android:height="48dp"
android:viewportHeight="93.46232"
android:viewportWidth="102.1415"
android:width="48dp"
xmlns:android="http://schemas.android.com/apk/res/android"
android:tint="@color/status_bar_blue"
>
<path android:fillColor="#990000" android:pathData="m0.83,77.62 l3.71,0 0,-61.79 -3.71,0 0,61.79zM8.24,77.62 L19.36,77.62 19.36,15.84 8.24,15.84 8.24,77.62zM23.07,15.84 L23.07,77.62 34.19,77.62 34.19,15.84 23.07,15.84z"/>
<path android:fillColor="#339966" android:pathData="m89.8,77.62 l3.7,0 0,-61.79 -3.7,0 0,61.79zM97.21,15.84 L97.21,77.62 100.92,77.62 100.92,15.84 97.21,15.84zM37.9,77.62 L41.6,77.62 41.6,15.84 37.9,15.84 37.9,77.62zM45.31,15.84 L45.31,77.62 49.02,77.62 49.02,15.84 45.32,15.84z"/>
<path android:fillColor="#006699" android:pathData="m52.73,77.62 l11.12,0 0,-61.79 -11.12,0 0,61.79zM67.56,77.62 L71.26,77.62 71.26,15.84 67.56,15.84 67.56,77.62zM74.97,15.84 L74.97,77.62 86.09,77.62 86.09,15.84 74.98,15.84z"/>
</vector>

View file

@ -0,0 +1,11 @@
<vector android:height="48dp"
android:viewportHeight="141.092"
android:viewportWidth="141.092"
android:width="48dp"
xmlns:android="http://schemas.android.com/apk/res/android"
android:tint="@color/status_bar_blue"
>
<path android:fillAlpha="1" android:fillColor="#000000"
android:pathData="M91.5,116.05 L73.43,73.49c-7.16,14.05 -15.09,28.64 -21.88,42.54 -0.04,0.07 -3.28,0.03 -3.28,-0.01C37.9,91.8 27.14,67.75 16.72,43.56 14.3,37.65 5.83,28.14 0.04,28.19 0.04,27.5 0.01,25.97 0,25.04L35.71,25.03l-0.02,3.11c-4.19,0.2 -11.44,2.87 -9.57,7.5 5.04,10.87 22.86,52.97 27.67,63.66 3.36,-6.58 12.75,-24.12 16.61,-31.53 -3.03,-6.22 -13.04,-29.43 -16.04,-35.27 -2.26,-3.81 -7.94,-4.28 -12.32,-4.34 0,-0.98 0.05,-1.73 0.03,-3.07l31.4,0.1 0,2.85c-4.25,0.12 -8.28,1.7 -6.45,5.76 4.22,8.77 6.69,15.01 10.57,23.11 1.24,-2.37 7.58,-15.39 10.61,-22.27 1.83,-4.57 -0.9,-6.28 -8.55,-6.49 0.1,-0.75 0.03,-2.26 0.1,-2.98 9.76,-0.04 24.5,-0.07 27.11,-0.11l0.01,2.97c-4.98,0.19 -10.14,2.85 -12.83,6.97l-13.06,27.08c1.43,3.59 13.99,31.47 15.31,34.56L123.29,34.34c-1.92,-5.05 -8.05,-6.17 -10.44,-6.23 0.02,-0.8 0.02,-2.03 0.03,-3.05l28.18,0.21 0.04,0.14 -0.05,2.68c-6.18,0.19 -10.01,3.49 -12.29,8.91 -5.62,12.68 -22.78,52.86 -34.24,79.05 -0.01,0.01 -3,-0 -3.01,-0.01z"
android:strokeAlpha="1" android:strokeColor="#00000000"/>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 882 B

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
</vector>

View file

@ -0,0 +1,10 @@
<vector
android:height="24dp"
android:viewportHeight="342.03146"
android:viewportWidth="342.03104"
android:width="24dp"
xmlns:android="http://schemas.android.com/apk/res/android"
android:tint="@color/button_color_selector">
<path android:fillColor="#000000" android:pathData="M146.6,340.1C113.2,333.3 84.6,314 66.1,286 34.6,238.1 39.8,173.2 78.5,132.3l6.9,-7.3 16.8,16.8 16.8,16.8 4.5,-4.4c2.5,-2.4 4.7,-4.2 4.9,-4 0.9,1 9,32 8.5,32.5 -0.5,0.5 -30.4,-7.2 -32.2,-8.2 -0.5,-0.3 1.1,-2.4 3.4,-4.8 4.2,-4.2 4.2,-4.3 2.4,-6.6 -1,-1.3 -3,-3.4 -4.5,-4.6l-2.7,-2.2 -4.7,6.2c-9,12 -15.3,27.5 -17.4,42.9 -1,7.1 -0.5,7.8 4.4,7.8 6.9,0 7.5,-0.6 7.5,-7.5l0,-6.3 15,8.6c8.3,4.7 15,8.9 15,9.2 -0,0.5 -26.5,15.8 -29.2,16.9 -0.4,0.2 -0.8,-2.6 -0.8,-6.3l0,-6.6 -5.9,0c-3.3,0 -6.1,0.3 -6.4,0.8 -0.9,1.6 1.6,15.2 4.4,23.8 3.4,10.3 9.3,21.2 14.9,27.6l3.8,4.4 4.3,-4.2 4.3,-4.2 -4.2,-4.3c-2.3,-2.4 -4.2,-4.6 -4.2,-4.9 0,-0.8 31.9,-9.2 32.6,-8.6 0.3,0.3 -1.4,7.9 -3.8,16.8l-4.4,16.3 -4.6,-4.2c-5.2,-4.8 -6.4,-4.7 -10.4,0.6l-2.7,3.6 4.3,3.7c10.6,9.1 30.3,17.4 44.4,18.6l7.6,0.6 0,-6.1 0,-6.1 -6.5,0c-3.6,0 -6.5,-0.3 -6.5,-0.7 0,-1 16.4,-29.3 17,-29.3 0.6,0 17,28.3 17,29.3 0,0.4 -2.9,0.7 -6.5,0.7l-6.5,0 0,6.1 0,6.1 7.6,-0.6c14.1,-1.2 33.8,-9.4 44.5,-18.6l4.4,-3.7 -2.2,-2.8c-1.2,-1.6 -3.1,-3.5 -4.2,-4.2 -1.8,-1.3 -2.4,-1 -6.6,2.9l-4.6,4.3 -3.7,-13.5c-5.1,-18.3 -5.4,-19.9 -3.9,-19.9 1.4,0 31.3,8 31.8,8.5 0.2,0.2 -1.6,2.4 -3.9,5l-4.3,4.6 4.2,4.2 4.2,4.2 3.8,-4.4c5.6,-6.4 11.5,-17.3 14.9,-27.6 2.8,-8.6 5.4,-22.1 4.4,-23.8 -0.2,-0.4 -3.1,-0.8 -6.4,-0.8l-5.9,0 0,6.6c0,3.6 -0.4,6.4 -0.8,6.3 -2.7,-1.1 -29.1,-16.4 -29.2,-16.9 -0,-0.3 6.7,-4.5 15,-9.2l15,-8.6 0,6.4 0,6.4 3.4,0.6c1.9,0.4 4.8,0.3 6.4,-0 2.8,-0.6 3,-0.9 2.5,-4.8 -1.8,-15.8 -8.7,-33.3 -17.8,-45.4l-4.7,-6.2 -2.7,2.2c-1.5,1.2 -3.5,3.3 -4.5,4.6 -1.8,2.3 -1.7,2.4 2.4,6.6 2.3,2.4 3.8,4.5 3.4,4.8 -1.8,1.1 -31.7,8.7 -32.2,8.2 -0.5,-0.5 7.6,-31.4 8.5,-32.5 0.2,-0.2 2.4,1.5 4.9,4l4.5,4.4 4.2,-4.2c3.5,-3.5 4,-4.4 2.9,-5.7 -2.1,-2.5 -9,-6.1 -22.1,-11.6 -17.1,-7.1 -26.4,-12.1 -33.6,-17.9 -10.7,-8.7 -19.3,-22.2 -24,-37.6 -1,-3.3 -1.9,-6.2 -2.1,-6.4 -0.1,-0.2 -5.2,1.4 -11.2,3.5 -6,2.1 -11.3,3.6 -11.6,3.2 -0.3,-0.3 1.9,-4.2 5,-8.5 9.2,-12.8 20.4,-38.4 28,-64.2l2.8,-9.3 3,4.8c16.2,26.1 40.8,71.5 39.3,73 -0.4,0.4 -2.2,-0.7 -3.9,-2.4 -3.9,-3.7 -8.1,-5.9 -13.1,-6.8 -3.7,-0.7 -3.8,-0.7 -3.1,2.2 2.4,9.9 9.8,20.2 17.8,24.9 2.2,1.3 11.7,5.7 21.1,9.7 20.6,8.9 28.7,14.1 39.7,25.7 16.7,17.5 26.7,36.9 31.9,61.4 3,14.3 3,35.1 -0.1,49.7 -10.6,50.7 -50.6,89.6 -101.1,98.5 -11.9,2.1 -34.9,1.8 -46.4,-0.5zM162.6,258.1c-26.3,-5.8 -40.9,-34.6 -29.6,-58.5 4.2,-9 10.8,-15.7 20,-20.3 7.3,-3.6 7.9,-3.7 18.1,-3.7 10.2,0 10.9,0.1 18.1,3.7 33,16.3 31.1,63.3 -3.1,76.8 -6.1,2.4 -17.3,3.4 -23.5,2z"/>
<path android:fillColor="#000000" android:pathData="M146.6,340.1C113.2,333.3 84.6,314 66.1,286 34.6,238.1 39.8,173.2 78.5,132.3l6.9,-7.3 16.8,16.8 16.8,16.8 4.5,-4.4c2.5,-2.4 4.7,-4.2 4.9,-4 0.9,1 9,32 8.5,32.5 -0.5,0.5 -30.4,-7.2 -32.2,-8.2 -0.5,-0.3 1.1,-2.4 3.4,-4.8 4.2,-4.2 4.2,-4.3 2.4,-6.6 -1,-1.3 -3,-3.4 -4.5,-4.6l-2.7,-2.2 -4.7,6.2c-9,12 -15.3,27.5 -17.4,42.9 -1,7.1 -0.5,7.8 4.4,7.8 6.9,0 7.5,-0.6 7.5,-7.5l0,-6.3 15,8.6c8.3,4.7 15,8.9 15,9.2 -0,0.5 -26.5,15.8 -29.2,16.9 -0.4,0.2 -0.8,-2.6 -0.8,-6.3l0,-6.6 -5.9,0c-3.3,0 -6.1,0.3 -6.4,0.8 -0.9,1.6 1.6,15.2 4.4,23.8 3.4,10.3 9.3,21.2 14.9,27.6l3.8,4.4 4.3,-4.2 4.3,-4.2 -4.2,-4.3c-2.3,-2.4 -4.2,-4.6 -4.2,-4.9 0,-0.8 31.9,-9.2 32.6,-8.6 0.3,0.3 -1.4,7.9 -3.8,16.8l-4.4,16.3 -4.6,-4.2c-5.2,-4.8 -6.4,-4.7 -10.4,0.6l-2.7,3.6 4.3,3.7c10.6,9.1 30.3,17.4 44.4,18.6l7.6,0.6 0,-6.1 0,-6.1 -6.5,0c-3.6,0 -6.5,-0.3 -6.5,-0.7 0,-1 16.4,-29.3 17,-29.3 0.6,0 17,28.3 17,29.3 0,0.4 -2.9,0.7 -6.5,0.7l-6.5,0 0,6.1 0,6.1 7.6,-0.6c14.1,-1.2 33.8,-9.4 44.5,-18.6l4.4,-3.7 -2.2,-2.8c-1.2,-1.6 -3.1,-3.5 -4.2,-4.2 -1.8,-1.3 -2.4,-1 -6.6,2.9l-4.6,4.3 -3.7,-13.5c-5.1,-18.3 -5.4,-19.9 -3.9,-19.9 1.4,0 31.3,8 31.8,8.5 0.2,0.2 -1.6,2.4 -3.9,5l-4.3,4.6 4.2,4.2 4.2,4.2 3.8,-4.4c5.6,-6.4 11.5,-17.3 14.9,-27.6 2.8,-8.6 5.4,-22.1 4.4,-23.8 -0.2,-0.4 -3.1,-0.8 -6.4,-0.8l-5.9,0 0,6.6c0,3.6 -0.4,6.4 -0.8,6.3 -2.7,-1.1 -29.1,-16.4 -29.2,-16.9 -0,-0.3 6.7,-4.5 15,-9.2l15,-8.6 0,6.4 0,6.4 3.4,0.6c1.9,0.4 4.8,0.3 6.4,-0 2.8,-0.6 3,-0.9 2.5,-4.8 -1.8,-15.8 -8.7,-33.3 -17.8,-45.4l-4.7,-6.2 -2.7,2.2c-1.5,1.2 -3.5,3.3 -4.5,4.6 -1.8,2.3 -1.7,2.4 2.4,6.6 2.3,2.4 3.8,4.5 3.4,4.8 -1.8,1.1 -31.7,8.7 -32.2,8.2 -0.5,-0.5 7.6,-31.4 8.5,-32.5 0.2,-0.2 2.4,1.5 4.9,4l4.5,4.4 4.2,-4.2c3.5,-3.5 4,-4.4 2.9,-5.7 -2.1,-2.5 -9,-6.1 -22.1,-11.6 -17.1,-7.1 -26.4,-12.1 -33.6,-17.9 -10.7,-8.7 -19.3,-22.2 -24,-37.6 -1,-3.3 -1.9,-6.2 -2.1,-6.4 -0.1,-0.2 -5.2,1.4 -11.2,3.5 -6,2.1 -11.3,3.6 -11.6,3.2 -0.3,-0.3 1.9,-4.2 5,-8.5 9.2,-12.8 20.4,-38.4 28,-64.2l2.8,-9.3 3,4.8c16.2,26.1 40.8,71.5 39.3,73 -0.4,0.4 -2.2,-0.7 -3.9,-2.4 -3.9,-3.7 -8.1,-5.9 -13.1,-6.8 -3.7,-0.7 -3.8,-0.7 -3.1,2.2 2.4,9.9 9.8,20.2 17.8,24.9 2.2,1.3 11.7,5.7 21.1,9.7 20.6,8.9 28.7,14.1 39.7,25.7 16.7,17.5 26.7,36.9 31.9,61.4 3,14.3 3,35.1 -0.1,49.7 -10.6,50.7 -50.6,89.6 -101.1,98.5 -11.9,2.1 -34.9,1.8 -46.4,-0.5zM162.6,258.1c-26.3,-5.8 -40.9,-34.6 -29.6,-58.5 4.2,-9 10.8,-15.7 20,-20.3 7.3,-3.6 7.9,-3.7 18.1,-3.7 10.2,0 10.9,0.1 18.1,3.7 33,16.3 31.1,63.3 -3.1,76.8 -6.1,2.4 -17.3,3.4 -23.5,2z"/>
</vector>

View file

@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0"
android:tint="@color/button_color_selector"
>
<path
android:fillColor="#FF000000"
android:pathData="M21.71,11.29l-9,-9c-0.39,-0.39 -1.02,-0.39 -1.41,0l-9,9c-0.39,0.39 -0.39,1.02 0,1.41l9,9c0.39,0.39 1.02,0.39 1.41,0l9,-9c0.39,-0.38 0.39,-1.01 0,-1.41zM14,14.5V12h-4v3H8v-4c0,-0.55 0.45,-1 1,-1h5V7.5l3.5,3.5 -3.5,3.5z"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M12,8c-2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4zM20.94,11c-0.46,-4.17 -3.77,-7.48 -7.94,-7.94L13,1h-2v2.06C6.83,3.52 3.52,6.83 3.06,11L1,11v2h2.06c0.46,4.17 3.77,7.48 7.94,7.94L11,23h2v-2.06c4.17,-0.46 7.48,-3.77 7.94,-7.94L23,13v-2h-2.06zM12,19c-3.87,0 -7,-3.13 -7,-7s3.13,-7 7,-7 7,3.13 7,7 -3.13,7 -7,7z"/>
</vector>

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0"
android:tint="@color/status_bar_blue">
<path
android:pathData="M12,8c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2c-1.1,0 -2,0.9 -2,2S10.9,8 12,8zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2c1.1,0 2,-0.9 2,-2S13.1,10 12,10zM12,16c-1.1,0 -2,0.9 -2,2s0.9,2 2,2c1.1,0 2,-0.9 2,-2S13.1,16 12,16z"
android:fillColor="@android:color/black"/>
</vector>

View file

@ -0,0 +1,11 @@
<vector android:height="24dp"
android:viewportHeight="93.46232"
android:viewportWidth="102.1415"
android:width="24dp"
xmlns:android="http://schemas.android.com/apk/res/android"
android:tint="@color/button_color_selector"
>
<path android:fillColor="#990000" android:pathData="m0.83,77.62 l3.71,0 0,-61.79 -3.71,0 0,61.79zM8.24,77.62 L19.36,77.62 19.36,15.84 8.24,15.84 8.24,77.62zM23.07,15.84 L23.07,77.62 34.19,77.62 34.19,15.84 23.07,15.84z"/>
<path android:fillColor="#339966" android:pathData="m89.8,77.62 l3.7,0 0,-61.79 -3.7,0 0,61.79zM97.21,15.84 L97.21,77.62 100.92,77.62 100.92,15.84 97.21,15.84zM37.9,77.62 L41.6,77.62 41.6,15.84 37.9,15.84 37.9,77.62zM45.31,15.84 L45.31,77.62 49.02,77.62 49.02,15.84 45.32,15.84z"/>
<path android:fillColor="#006699" android:pathData="m52.73,77.62 l11.12,0 0,-61.79 -11.12,0 0,61.79zM67.56,77.62 L71.26,77.62 71.26,15.84 67.56,15.84 67.56,77.62zM74.97,15.84 L74.97,77.62 86.09,77.62 86.09,15.84 74.98,15.84z"/>
</vector>

View file

@ -0,0 +1,11 @@
<vector android:height="24dp"
android:viewportHeight="141.092"
android:viewportWidth="141.092"
android:width="24dp"
xmlns:android="http://schemas.android.com/apk/res/android"
android:tint="@color/button_color_selector"
>
<path android:fillAlpha="1" android:fillColor="#000000"
android:pathData="M91.5,116.05 L73.43,73.49c-7.16,14.05 -15.09,28.64 -21.88,42.54 -0.04,0.07 -3.28,0.03 -3.28,-0.01C37.9,91.8 27.14,67.75 16.72,43.56 14.3,37.65 5.83,28.14 0.04,28.19 0.04,27.5 0.01,25.97 0,25.04L35.71,25.03l-0.02,3.11c-4.19,0.2 -11.44,2.87 -9.57,7.5 5.04,10.87 22.86,52.97 27.67,63.66 3.36,-6.58 12.75,-24.12 16.61,-31.53 -3.03,-6.22 -13.04,-29.43 -16.04,-35.27 -2.26,-3.81 -7.94,-4.28 -12.32,-4.34 0,-0.98 0.05,-1.73 0.03,-3.07l31.4,0.1 0,2.85c-4.25,0.12 -8.28,1.7 -6.45,5.76 4.22,8.77 6.69,15.01 10.57,23.11 1.24,-2.37 7.58,-15.39 10.61,-22.27 1.83,-4.57 -0.9,-6.28 -8.55,-6.49 0.1,-0.75 0.03,-2.26 0.1,-2.98 9.76,-0.04 24.5,-0.07 27.11,-0.11l0.01,2.97c-4.98,0.19 -10.14,2.85 -12.83,6.97l-13.06,27.08c1.43,3.59 13.99,31.47 15.31,34.56L123.29,34.34c-1.92,-5.05 -8.05,-6.17 -10.44,-6.23 0.02,-0.8 0.02,-2.03 0.03,-3.05l28.18,0.21 0.04,0.14 -0.05,2.68c-6.18,0.19 -10.01,3.49 -12.29,8.91 -5.62,12.68 -22.78,52.86 -34.24,79.05 -0.01,0.01 -3,-0 -3.01,-0.01z"
android:strokeAlpha="1" android:strokeColor="#00000000"/>
</vector>

View file

@ -0,0 +1,165 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/coordinator_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<include
android:id="@+id/toolbar"
layout="@layout/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_below="@id/toolbar">
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true" />
<FrameLayout
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"></FrameLayout>
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/transparentView"
android:layout_below="@id/toolbar"
android:background="#aa969696"
android:elevation="6dp"
/>
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab_recenter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_below="@+id/toolbar"
android:layout_marginRight="12dp"
android:layout_marginTop="6dp"
android:clickable="true"
android:visibility="invisible"
app:backgroundTint="@color/main_background_light"
app:elevation="6dp"
app:fabSize="normal"
app:layout_anchorGravity="top|right|end"
app:srcCompat="@drawable/ic_my_location_black_24dp" />
</RelativeLayout>
<include layout="@layout/bottom_sheet_nearby" />
<include layout="@layout/bottom_sheet_details" android:id="@+id/bottom_sheet_details" />
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab_plus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:fabSize="normal"
android:layout_margin="16dp"
android:visibility="invisible"
android:clickable="true"
app:elevation="6dp"
app:pressedTranslationZ="12dp"
app:backgroundTint="@color/button_blue"
app:layout_anchor="@id/bottom_sheet_details"
app:layout_anchorGravity="top|right|end"
app:srcCompat="@drawable/ic_add_white_24dp"/>
<View
android:id = "@+id/empty_view2"
android:layout_height = "306dip"
android:layout_width = "56dp"
android:visibility="invisible"
app:layout_anchor="@id/fab_plus"
app:layout_anchorGravity="center_horizontal"
/>
<View
android:id = "@+id/empty_view1"
android:layout_height = "186dip"
android:layout_width = "56dp"
android:visibility="invisible"
app:layout_anchor="@id/fab_plus"
app:layout_anchorGravity="center_horizontal"
/>
<View
android:id = "@+id/empty_view"
android:layout_height = "66dip"
android:layout_width = "56dp"
android:visibility="invisible"
app:layout_anchor="@id/fab_plus"
app:layout_anchorGravity="center_horizontal"
/>
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab_camera"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:fabSize="mini"
android:visibility="invisible"
app:backgroundTint="@color/main_background_light"
app:elevation="6dp"
app:pressedTranslationZ="12dp"
app:layout_anchor="@id/empty_view1"
app:layout_anchorGravity="center_horizontal"
app:srcCompat="@drawable/ic_photo_camera_white_24dp"
android:tint="@color/button_blue"
android:scaleType="center"/>
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab_galery"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:fabSize="mini"
android:visibility="invisible"
app:backgroundTint="@color/main_background_light"
app:elevation="6dp"
app:pressedTranslationZ="12dp"
app:layout_anchor="@id/empty_view"
app:layout_anchorGravity="center_horizontal"
app:srcCompat="@drawable/ic_photo_white_24dp"
android:tint="@color/button_blue"
android:scaleType="center"
/>
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab_commons_page"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:fabSize="mini"
android:visibility="invisible"
app:backgroundTint="@color/main_background_light"
app:elevation="6dp"
app:pressedTranslationZ="12dp"
app:layout_anchor="@id/empty_view2"
app:layout_anchorGravity="center_horizontal"
app:srcCompat="@drawable/ic_commons_icon_vector"
android:scaleType="center"
/>
</android.support.design.widget.CoordinatorLayout>
<android.support.design.widget.NavigationView
android:id="@+id/navigation_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
app:headerLayout="@layout/drawer_header"
app:menu="@menu/drawer"/>
</android.support.v4.widget.DrawerLayout>

View file

@ -123,6 +123,16 @@
android:gravity="center"
android:text="@string/about_privacy_policy" />
<TextView
android:id="@+id/about_translate"
style="?android:textAppearanceSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/primaryColor"
android:layout_marginTop="@dimen/standard_gap"
android:gravity="center"
android:text="@string/about_translate" />
<TextView
android:id="@+id/about_credits"
style="?android:textAppearanceSmall"

View file

@ -5,37 +5,185 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/coordinator_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include
android:id="@+id/toolbar"
layout="@layout/toolbar"
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" />
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_below="@id/toolbar">
<ProgressBar
android:id="@+id/progressBar"
<include
android:id="@+id/toolbar"
layout="@layout/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/swipe_container"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</android.support.v4.widget.SwipeRefreshLayout>
android:layout_height="match_parent"
android:gravity="center_vertical"
android:orientation="horizontal">
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<FrameLayout
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
</RelativeLayout>
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab_list"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:fabSize="mini"
android:layout_alignBottom="@id/toolbar"
android:layout_alignParentRight="true"
android:layout_marginRight="28dp"
android:layout_marginBottom="-96dp"
android:visibility="invisible"
app:elevation="6dp"
app:pressedTranslationZ="12dp"
app:backgroundTint="@color/button_blue"
android:clickable="true"
app:srcCompat="@drawable/ic_list_white_24dp"
android:scaleType="center"
/>
<View
android:id="@+id/transparentView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:layout_marginTop="18dp"
android:background="#aa969696"
android:elevation="6dp">
</View>
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab_recenter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_below="@+id/toolbar"
android:layout_marginRight="12dp"
android:layout_marginTop="6dp"
android:clickable="true"
android:visibility="invisible"
app:backgroundTint="@color/main_background_light"
app:elevation="6dp"
app:fabSize="normal"
app:layout_anchorGravity="top|right|end"
app:srcCompat="@drawable/ic_my_location_black_24dp" />
</RelativeLayout>
<include layout="@layout/bottom_sheet_nearby" />
<include
android:id="@+id/bottom_sheet_details"
layout="@layout/bottom_sheet_details" />
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab_plus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_marginEnd="12dp"
android:layout_marginRight="12dp"
android:clickable="true"
android:visibility="invisible"
app:backgroundTint="@color/button_blue"
app:elevation="6dp"
app:fabSize="normal"
app:layout_anchor="@id/bottom_sheet_details"
app:layout_anchorGravity="top|right|end"
app:pressedTranslationZ="12dp"
app:srcCompat="@drawable/ic_add_white_24dp" />
<View
android:id="@+id/empty_view2"
android:layout_width="56dp"
android:layout_height="306dip"
android:visibility="invisible"
app:layout_anchor="@id/fab_plus"
app:layout_anchorGravity="center_horizontal" />
<View
android:id="@+id/empty_view1"
android:layout_width="56dp"
android:layout_height="186dip"
android:visibility="invisible"
app:layout_anchor="@id/fab_plus"
app:layout_anchorGravity="center_horizontal" />
<View
android:id="@+id/empty_view"
android:layout_width="56dp"
android:layout_height="66dip"
android:visibility="invisible"
app:layout_anchor="@id/fab_plus"
app:layout_anchorGravity="center_horizontal" />
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab_camera"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scaleType="center"
android:tint="@color/button_blue"
android:visibility="invisible"
app:backgroundTint="@color/main_background_light"
app:elevation="6dp"
app:fabSize="mini"
app:layout_anchor="@id/empty_view1"
app:layout_anchorGravity="center_horizontal"
app:pressedTranslationZ="12dp"
app:srcCompat="@drawable/ic_photo_camera_white_24dp" />
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab_galery"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scaleType="center"
android:tint="@color/button_blue"
android:visibility="invisible"
app:backgroundTint="@color/main_background_light"
app:elevation="6dp"
app:fabSize="mini"
app:layout_anchor="@id/empty_view"
app:layout_anchorGravity="center_horizontal"
app:pressedTranslationZ="12dp"
app:srcCompat="@drawable/ic_photo_white_24dp" />
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab_commons_page"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scaleType="center"
android:visibility="invisible"
app:backgroundTint="@color/main_background_light"
app:elevation="6dp"
app:fabSize="mini"
app:layout_anchor="@id/empty_view2"
app:layout_anchorGravity="center_horizontal"
app:pressedTranslationZ="12dp"
app:srcCompat="@drawable/ic_commons_icon_vector" />
</android.support.design.widget.CoordinatorLayout>
<android.support.design.widget.NavigationView
android:id="@+id/navigation_view"
@ -43,6 +191,6 @@
android:layout_height="match_parent"
android:layout_gravity="start"
app:headerLayout="@layout/drawer_header"
app:menu="@menu/drawer"/>
app:menu="@menu/drawer" />
</android.support.v4.widget.DrawerLayout>

View file

@ -0,0 +1,192 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:background="?attr/mainBackground"
app:layout_behavior="@string/bottom_sheet_behavior"
app:behavior_peekHeight="72dp"
app:behavior_hideable="true"
android:visibility="gone"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="72dp"
android:layout_marginVertical="8dp"
android:gravity="center_vertical"
>
<ImageView
android:id="@+id/icon"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginLeft="16dp">
</ImageView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp">
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp" />
<TextView
android:id="@+id/category"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp" />
</LinearLayout>
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="7dp"
android:layout_marginBottom="8dp"
android:background="@android:color/darker_gray"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
>
<LinearLayout
android:id="@+id/directionsButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:padding="16dp"
android:clickable="true"
android:background="@drawable/button_background_selector"
android:orientation="vertical"
>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:duplicateParentState="true"
app:srcCompat="@drawable/ic_directions_black_24dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/directionsButtonText"
android:paddingTop="8dp"
android:duplicateParentState="true"
android:textColor="@color/text_color_selector"
android:layout_gravity="center_horizontal"
android:text="@string/nearby_directions"
/>
</LinearLayout>
<LinearLayout
android:id="@+id/wikidataButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:padding="16dp"
android:clickable="true"
android:background="@drawable/button_background_selector"
android:orientation="vertical"
>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:duplicateParentState="true"
app:srcCompat="@drawable/ic_wikidata_logo_24dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/wikidataButtonText"
android:paddingTop="8dp"
android:duplicateParentState="true"
android:textColor="@color/text_color_selector"
android:layout_gravity="center_horizontal"
android:text="@string/nearby_wikidata"
/>
</LinearLayout>
<LinearLayout
android:id="@+id/wikipediaButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:padding="16dp"
android:clickable="true"
android:background="@drawable/button_background_selector"
android:orientation="vertical"
>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:duplicateParentState="true"
app:srcCompat="@drawable/ic_wikipedia_logo_24dp"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/wikipediaButtonText"
android:paddingTop="8dp"
android:duplicateParentState="true"
android:textColor="@color/text_color_selector"
android:layout_gravity="center_horizontal"
android:text="@string/nearby_wikipedia"
/>
</LinearLayout>
<LinearLayout
android:id="@+id/commonsButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:padding="16dp"
android:clickable="true"
android:background="@drawable/button_background_selector"
android:orientation="vertical"
>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:duplicateParentState="true"
app:srcCompat="@drawable/ic_commons_icon_vector"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/commonsButtonText"
android:paddingTop="8dp"
android:duplicateParentState="true"
android:textColor="@color/text_color_selector"
android:layout_gravity="center_horizontal"
android:text="@string/nearby_commons"
/>
</LinearLayout>
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="7dp"
android:layout_marginBottom="8dp"
android:background="@android:color/darker_gray"/>
<TextView
android:id="@+id/description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="72dp"
android:layout_marginRight="16dp"
android:layout_marginBottom="16dp"
android:textSize="16sp" />
</LinearLayout>

View file

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="480dp"
android:id="@+id/bottom_sheet"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:background="?attr/mainBackground"
app:layout_behavior="@string/bottom_sheet_behavior"
app:behavior_peekHeight="0dp"
app:behavior_hideable="true"
>
<FrameLayout
android:id="@+id/container_sheet"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:focusableInTouchMode="true"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginBottom="8dp"
android:layout_marginTop="8dp"
></FrameLayout>
</LinearLayout>

View file

@ -2,9 +2,9 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:foreground="?selectableItemBackground"
android:minHeight="72dp"
>
xmlns:app="http://schemas.android.com/apk/res-auto"
android:focusableInTouchMode="true"
android:minHeight="72dp">
<ImageView
android:id="@+id/icon"
@ -32,6 +32,17 @@
tools:text="@string/placeholder_place_distance"
/>
<ImageView
android:id="@+id/toggleArrow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_marginRight="16dp"
android:layout_marginTop="32dp"
/>
<TextView
android:id="@+id/tvName"
android:layout_width="match_parent"
@ -65,4 +76,5 @@
tools:text="@string/placeholder_place_description"
/>
</RelativeLayout>
<include layout="@layout/nearby_row_button" />
</RelativeLayout>

View file

@ -0,0 +1,124 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/buttonLayout"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="horizontal"
android:visibility="gone"
android:layout_marginTop="16dp"
android:layout_below="@+id/icon"
xmlns:android="http://schemas.android.com/apk/res/android"
>
<LinearLayout
android:id="@+id/cameraButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:padding="16dp"
android:clickable="true"
android:orientation="vertical"
android:background="@drawable/button_background_selector"
>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
app:srcCompat="@drawable/ic_photo_camera_white_24dp"
android:tint="@color/button_blue"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/cameraButtonText"
android:paddingTop="8dp"
android:layout_gravity="center_horizontal"
android:text="CAMERA"
android:visibility="gone"
/>
</LinearLayout>
<LinearLayout
android:id="@+id/galleryButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:padding="16dp"
android:clickable="true"
android:orientation="vertical"
android:background="@drawable/button_background_selector"
>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
app:srcCompat="@drawable/ic_photo_white_24dp"
android:tint="@color/button_blue"
android:duplicateParentState="true"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/galleryButtonText"
android:paddingTop="8dp"
android:layout_gravity="center_horizontal"
android:text="GALLERY"
android:duplicateParentState="true"
android:visibility="gone"
/>
</LinearLayout>
<LinearLayout
android:id="@+id/directionsButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:padding="16dp"
android:clickable="true"
android:orientation="vertical"
android:background="@drawable/button_background_selector"
>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
app:srcCompat="@drawable/ic_directions_black_24dp"
android:duplicateParentState="true"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/directionsButtonText"
android:paddingTop="8dp"
android:layout_gravity="center_horizontal"
android:text="DIRECTIONS"
android:duplicateParentState="true"
android:visibility="gone"
/>
</LinearLayout>
<LinearLayout
android:id="@+id/iconOverflow"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:padding="16dp"
android:clickable="true"
android:orientation="vertical"
>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
app:srcCompat="@drawable/ic_overflow"
android:duplicateParentState="true"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/iconOverflowText"
android:paddingTop="8dp"
android:layout_gravity="center_horizontal"
android:text="MORE"
android:duplicateParentState="true"
android:visibility="gone"
/>
</LinearLayout>
</LinearLayout>

View file

@ -3,17 +3,9 @@
xmlns:tools="http://schemas.android.com/tools" tools:context=".MainActivity">
<item
android:id="@+id/action_toggle_view"
android:title="@string/toggle_view_button"
android:icon="@drawable/ic_map_white_24dp"
android:orderInCategory="1"
app:showAsAction="ifRoom"
/>
<item
android:id="@+id/action_refresh"
android:title="@string/refresh_button"
android:icon="@drawable/ic_refresh_white_24dp"
android:id="@+id/action_display_list"
android:title="@string/display_list_button"
android:icon="@drawable/ic_list_white_24dp"
android:orderInCategory="1"
app:showAsAction="ifRoom"
/>

View file

@ -6,4 +6,7 @@
<item android:id="@+id/nearby_info_menu_wikidata_article"
android:title="@string/nearby_info_menu_wikidata_article"
/>
<item android:id="@+id/nearby_info_menu_wikipedia_article"
android:title="@string/nearby_info_menu_wikipedia_article"
/>
</menu>

View file

@ -224,7 +224,7 @@
<string name="send_log_file_description">Odeslat log vývojářům e-mailem</string>
<string name="no_web_browser">Nebyl nalezen žádný webový prohlížeč k otevření URL</string>
<string name="null_url">Chyba! URL nenalezeno</string>
<string name="nominate_deletion">Kandidát na smazání</string>
<string name="nominate_deletion">Navrhnout na smazání</string>
<string name="view_browser">Zobrazit v prohlížeči</string>
<string name="nearby_location_has_not_changed">Vaše umístění se nezměnilo.</string>
<string name="nearby_location_not_available">Umístění není dostupné.</string>

View file

@ -59,6 +59,7 @@
<string name="categories_search_text_hint">Kategorien durchsuchen</string>
<string name="menu_save_categories">Speichern</string>
<string name="refresh_button">Aktualisieren</string>
<string name="display_list_button">Liste</string>
<string name="gps_disabled">GPS ist auf deinem Gerät deaktiviert. Möchtest du es aktivieren?</string>
<string name="enable_gps">GPS aktivieren</string>
<string name="contributions_subtitle_zero">Noch keine hochgeladenen Dateien</string>
@ -211,6 +212,7 @@
<string name="no_description_found">Keine Beschreibung gefunden</string>
<string name="nearby_info_menu_commons_article">Commons-Dateiseite</string>
<string name="nearby_info_menu_wikidata_article">Wikidata-Objekt</string>
<string name="nearby_info_menu_wikipedia_article">Wikipedia-Artikel</string>
<string name="error_while_cache">Fehler beim Zwischenspeichern der Bilder</string>
<string name="title_info">Ein eindeutiger beschreibender Titel für die Datei, der als Dateiname dient. Du kannst Klartext mit Leerzeichen verwenden. Gib nicht die Dateierweiterung mit an.</string>
<string name="description_info">Bitte beschreibe das Medium so gut wie möglich: Wo wurde es aufgenommen? Was zeigt es? Was ist der Kontext? Bitte beschreibe die Objekte oder Personen. Zeige Informationen auf, die nicht einfach erraten werden können, zum Beispiel die Tageszeit, falls es eine Landschaft ist. Falls das Medium etwas Ungewöhnliches zeigt, erkläre bitte, was es ungewöhnlich macht.</string>
@ -236,6 +238,10 @@
<string name="notifications_thank_you_edit">Vielen Dank für deine Bearbeitung</string>
<string name="notifications_mention">%1$s erwähnte dich auf %2$s.</string>
<string name="toggle_view_button">Ansicht wechseln</string>
<string name="nearby_directions">RICHTUNGEN</string>
<string name="nearby_wikidata">WIKIDATA</string>
<string name="nearby_wikipedia">WIKIPEDIA</string>
<string name="nearby_commons">COMMONS</string>
<string name="about_rate_us"/>
<string name="about_faq">Häufig gestellte Fragen</string>
<string name="welcome_skip_button">Tutorial überspringen</string>

View file

@ -59,6 +59,7 @@
<string name="categories_search_text_hint">Αναζήτηση κατηγοριών</string>
<string name="menu_save_categories">Αποθήκευση</string>
<string name="refresh_button">Ανανέωση</string>
<string name="display_list_button">Κατάλογος</string>
<string name="gps_disabled">Το GPS στην συσκευή είναι απενεργοποιημένο. Θέλετε να το ενεργοποιήσετε;</string>
<string name="enable_gps"> Ενεργοποιήσετε το GPS</string>
<string name="contributions_subtitle_zero">Δεν έχουν ακόμη επιφορτωθεί αρχεία</string>
@ -211,6 +212,7 @@
<string name="no_description_found">δεν βρέθηκε περιγραφή</string>
<string name="nearby_info_menu_commons_article">Σελίδα φακέλλου κοινής χρήσης</string>
<string name="nearby_info_menu_wikidata_article">Τεμάχιο Wikidata</string>
<string name="nearby_info_menu_wikipedia_article">Λήμμα Βικιπαίδειας</string>
<string name="error_while_cache">Υπήρξε σφάλμα κατά την σκίαση εικόνων</string>
<string name="title_info">Ένας μοναδικός τίτλος περιγραφής του φακέλλου, που θα χρησιμεύσει ως όνομα φακέλλου. Μπορείτε να χρησιμοποιήσετε τις ήδη υπάρχουσες γλώσσες με διαστήματα. Μην συμπεριλάβετε την επέκταση φακέλλου</string>
<string name="description_info">\nΠαρακαλώ περιγράψετε τα μέσα το δυνατό περισσότερο : Πού οδηγήθηκε αυτό; Τι δείχνει; Ποιο είναι το περιεχόμενο του; Παρακαλώ περιγράψετε τα αντικείμενα ή τα πρόσωπα. Αποκαλύψετε πληροφορίες που δεν μπορούν εύκολο να μαντέψει κανείς, για παράδειγμα την ώρα εντός της ημέρας αν πρόκειται για τοπίο. Αν τα μέσα δείξουν κάτι ασύνηθες, παρακαλώ εξηγήστε τι το καθιστά μη συνηθισμένα.</string>
@ -236,6 +238,10 @@
<string name="notifications_thank_you_edit">Ευχαριστούμε που κάνατε μια επεξεργασία</string>
<string name="notifications_mention">Ο %1$s σας ανέφερε στο %2$s.</string>
<string name="toggle_view_button">Μεταβολή προβολής</string>
<string name="nearby_directions">Κατευθύνσεις</string>
<string name="nearby_wikidata">Βικιδεδομένα</string>
<string name="nearby_wikipedia">Βικιπαίδεια</string>
<string name="nearby_commons">Κοινά</string>
<string name="about_rate_us"/>
<string name="about_faq">Συχνές ερωτήσεις</string>
<string name="welcome_skip_button">Παράβλεψη εισαγωγής</string>

View file

@ -223,6 +223,8 @@
<string name="send_log_file_description">Enviar archivo de registro a los desarrolladores por correo electrónico</string>
<string name="no_web_browser">No se encontró ningún navegador con el que abrir el URL</string>
<string name="null_url">Error: no se encontró el URL</string>
<string name="nominate_deletion">Nominar para borrado</string>
<string name="view_browser">Ver en navegador</string>
<string name="nearby_location_has_not_changed">La ubicación no ha cambiado.</string>
<string name="nearby_location_not_available">La ubicación no está disponible.</string>
<string name="location_permission_rationale_nearby">Se necesita permiso para mostrar una lista de lugares cercanos</string>

View file

@ -51,6 +51,7 @@
<string name="categories_search_text_hint">श्रेणियाँ खोजें</string>
<string name="menu_save_categories">सहेजें</string>
<string name="refresh_button">ताजा करें</string>
<string name="display_list_button">सूची</string>
<string name="gps_disabled">आपके डिवाइस में जीपीएस अक्षम है। क्या आप इसे सक्षम करना चाहेंगे?</string>
<string name="enable_gps">जीपीएस सक्षम करें</string>
<string name="contributions_subtitle_zero">अभी तक कोई अपलोड नहीं</string>
@ -193,6 +194,7 @@
<string name="no_description_found">कोई विवरण नहीं मिला</string>
<string name="nearby_info_menu_commons_article">कॉमन्स फाइल पृष्ठ</string>
<string name="nearby_info_menu_wikidata_article">विकिडाटा वस्तु</string>
<string name="nearby_info_menu_wikipedia_article">विकीपीडिया लेख</string>
<string name="error_while_cache">चित्र कैशिंग करते समय त्रुटि</string>
<string name="title_info">फ़ाइल के लिए एक अद्वितीय वर्णनात्मक शीर्षक, जो एक फ़ाइल नाम के रूप में काम करेगा। आप रिक्त स्थान के साथ सादे भाषा का उपयोग कर सकते हैं। फ़ाइल विस्तार शामिल न करें</string>
<string name="description_info">कृपया मीडिया जितना संभव हो उतना बताएं: यह कहां लिया गया? यह क्या दिखाता है? संदर्भ क्या है? कृपया वस्तुओं या व्यक्तियों का वर्णन करें। ऐसी जानकारी का खुलासा करें जिसे आसानी से अनुमानित नहीं किया जा सकता, उदाहरण के लिए दिन का समय यदि यह परिदृश्य है। अगर मीडिया कुछ असामान्य दिखाता है, तो कृपया बताएं कि इसे क्या असामान्य बनाता है।</string>
@ -212,4 +214,8 @@
<string name="notifications_talk_page_message" fuzzy="true">%1$s ने आपके वार्ता पृष्ठ पर सन्देश छोड़ा है</string>
<string name="notifications_thank_you_edit">सम्पादन करने के लिए धन्यवाद</string>
<string name="notifications_mention" fuzzy="true">%1$s ने %2$s में आपका उल्लेख किया है</string>
<string name="nearby_directions">दिशा-निर्देश</string>
<string name="nearby_wikidata">विकीडाटा</string>
<string name="nearby_wikipedia">विकीपीडिया</string>
<string name="nearby_commons">कॉमन्स</string>
</resources>

View file

@ -214,6 +214,7 @@
<string name="send_log_file_description">이메일로 개발자에게 로그 파일 보내기</string>
<string name="no_web_browser">URL을 열기 위한 웹 브라우저를 찾지 못했습니다</string>
<string name="null_url">오류! URL을 찾을 수 없습니다</string>
<string name="view_browser">브라우저에서 보기</string>
<string name="nearby_location_has_not_changed">위치가 변경되지 않았습니다.</string>
<string name="nearby_location_not_available">위치를 사용할 수 없습니다.</string>
<string name="location_permission_rationale_nearby">주변 장소의 목록을 표시하기 위한 권한이 필요합니다.</string>

View file

@ -59,6 +59,7 @@
<string name="categories_search_text_hint">Пребарај категории</string>
<string name="menu_save_categories">Зачувај</string>
<string name="refresh_button">Превчитај</string>
<string name="display_list_button">Список</string>
<string name="gps_disabled">ГПС е исклучен на уредов. Дали сакате да го вклучите?</string>
<string name="enable_gps">Вклучи ГПС</string>
<string name="contributions_subtitle_zero">Сè уште нема подигања</string>
@ -211,6 +212,7 @@
<string name="no_description_found">не најдов описи</string>
<string name="nearby_info_menu_commons_article">Податотечна страница</string>
<string name="nearby_info_menu_wikidata_article">Предмет на Википодатоците</string>
<string name="nearby_info_menu_wikipedia_article">Статија на Википедија</string>
<string name="error_while_cache">Грешка при меѓускладирање на сликите</string>
<string name="title_info">Краток и единствен наслов на податотеката, кој ќе служи како нејзин назив. Можете да користите прост јазик со меѓупростор, но не пишувајте ја податотечната наставка</string>
<string name="description_info">Објаснете ја податотеката што подобро можете: Каде е направена? Што е прикажано на неа? Кој е контекстот? Опишете ги предметите, објектите и личностите. Дајте сознанија што не можат лесно да се погодат, како на пр. време од денот ако се работи за природен предел. Ако на неа е претставено нешто необично, објаснете зошто прикажаното е необично.</string>
@ -224,6 +226,8 @@
<string name="send_log_file_description">Испрати дневничка податотека на разработувачите по е-пошта</string>
<string name="no_web_browser">Не најдов прелистувач за да ја отворам URL</string>
<string name="null_url">Грешка! Не ја пронајдов URL</string>
<string name="nominate_deletion">Предложи за бришење</string>
<string name="view_browser">Погледај во прелистувач</string>
<string name="nearby_location_has_not_changed">Местоположбата не е сменета.</string>
<string name="nearby_location_not_available">Местоположбата е недостапна.</string>
<string name="location_permission_rationale_nearby">Се бара дозвола за приказ на список на околни места</string>
@ -234,6 +238,10 @@
<string name="notifications_thank_you_edit">Ви благодариме за уредувањето</string>
<string name="notifications_mention">%1$s ве спомна на %2$s.</string>
<string name="toggle_view_button">Смени поглед</string>
<string name="nearby_directions">НАСОКИ</string>
<string name="nearby_wikidata">ВИКИПОДАТОЦИ</string>
<string name="nearby_wikipedia">ВИКИПЕДИЈА</string>
<string name="nearby_commons">РИЗНИЦА</string>
<string name="about_faq">Често поставувани прашања</string>
<string name="welcome_skip_button">Прескокни упатство</string>
</resources>

View file

@ -57,6 +57,7 @@
<string name="categories_search_text_hint">Szukaj kategorii</string>
<string name="menu_save_categories">Zapisz</string>
<string name="refresh_button">Odśwież</string>
<string name="display_list_button">Lista</string>
<string name="gps_disabled">GPS w twoim urządzeniu jest wyłączony. Czy chcesz go włączyć?</string>
<string name="enable_gps">Włącz GPS</string>
<string name="contributions_subtitle_zero">Na razie brak przesłanych plików!</string>
@ -190,6 +191,7 @@
<string name="navigation_item_notification">Powiadomienia</string>
<string name="no_description_found">nie znaleziono opisu</string>
<string name="nearby_info_menu_wikidata_article">Element Wikidanych</string>
<string name="nearby_info_menu_wikipedia_article">Artykuł na Wikipedii</string>
<string name="title_info">Podaj krótką, opisową i unikalną nazwę, która będzie służyła jako nazwa pliku. Możesz używać prostego języka i spacji. Nie dodawaj rozszerzenia pliku.</string>
<string name="login_to_your_account">Zaloguj się na swoje konto</string>
<string name="null_url">Błąd! Nie znaleziono adresu URL</string>
@ -197,6 +199,10 @@
<string name="notifications_thank_you_edit">Dziękujemy za dokonanie edycji</string>
<string name="notifications_mention">%1$s wspomniał o Tobie w %2$s.</string>
<string name="toggle_view_button">Przełącz widok</string>
<string name="nearby_directions">WSKAZÓWKI</string>
<string name="nearby_wikidata">WIKIDANE</string>
<string name="nearby_wikipedia">WIKIPEDIA</string>
<string name="nearby_commons">POWSZECHNE</string>
<string name="about_faq">Najczęściej zadawane pytania</string>
<string name="welcome_skip_button">Pomiń samouczek</string>
</resources>

View file

@ -224,6 +224,8 @@
<string name="send_log_file_description">Mandé l\'archivi d\'argistr ai dësvlupator për pòsta eletrònica</string>
<string name="no_web_browser">Gnun navigador trovà për duverté l\'adrëssa an sl\'aragnà</string>
<string name="null_url">Eror! Liura nen trovà</string>
<string name="nominate_deletion">Propon-e për la scancelassion</string>
<string name="view_browser">Smon-e ant ël navigador</string>
<string name="nearby_location_has_not_changed">Ël leu a l\'é nen cangià.</string>
<string name="nearby_location_not_available">Leu nen disponìbil.</string>
<string name="location_permission_rationale_nearby">A-i é da manca dël përmess pr\'ësmon-e na lista dij pòst davzin</string>

View file

@ -228,7 +228,7 @@
<string name="send_log_file_description">Выслать лог-файл разработчикам по е-мейлу</string>
<string name="no_web_browser">Не найден браузер, чтобы открыть ссылку</string>
<string name="null_url">Ошибка! Ссылка не найдена</string>
<string name="nominate_deletion">Выставить к удалению</string>
<string name="nominate_deletion">Номинировать к удалению</string>
<string name="view_browser">Просмотреть в браузере</string>
<string name="nearby_location_has_not_changed">Местоположение не изменено.</string>
<string name="nearby_location_not_available">Местоположение недоступно.</string>

View file

@ -61,13 +61,13 @@
<item quantity="other">การอัปโหลด %1$d รายการ</item>
</plurals>
<string name="categories_not_found">ไม่พบหมวดหมู่ที่ตรงกับ %$1s</string>
<string name="categories_skip_explanation" fuzzy="true">เพิ่มหมวดหมู่เพื่อทำให้รูปภาพของคุณค้นพบได้ง่ายขึ้นบน Wikimedia Commons\n\nเริ่มพิมพ์เพื่อเพิ่มหมวดหมู่\nแตะข้อความนี้ (หรือกดปุ่มย้อนกลับ) เพื่อข้ามขั้นตอนนี้</string>
<string name="categories_skip_explanation">เพิ่มหมวดหมู่เพื่อทำให้รูปภาพของคุณค้นพบได้ง่ายขึ้นบน Wikimedia Commons\nเริ่มพิมพ์เพื่อเพิ่มหมวดหมู่</string>
<string name="categories_activity_title">หมวดหมู่</string>
<string name="title_activity_settings">การตั้งค่า</string>
<string name="title_activity_signup">สมัครใช้งาน</string>
<string name="menu_about">เกี่ยวกับ</string>
<string name="about_license">แอป Wikimedia Commons เป็นแอปโอเพนซอร์สที่สร้างขึ้นและดูแลโดยผู้มีสิทธิและอาสาสมัครของชุมชนวิกิมีเดีย มูลนิธิวิกิมีเดียไม่มีส่วนเกี่ยวข้องในการสร้าง พัฒนา หรือการบำรุงรักษาแอปใดๆ ทั้งสิ้น</string>
<string name="about_improve" fuzzy="true">&lt;a href=\"https://github.com/commons-app/apps-android-commons\"&gt;ซอร์สโค้ด&lt;/a&gt;และ&lt;a href=\"https://commons-app.github.io/\"&gt;เว็บไซต์&lt;/a&gt;บน GitHub สร้าง &lt;a href=\"https://github.com/commons-app/apps-android-commons/issues\"&gt;GitHub issue&lt;/a&gt; ใหม่เพื่อรายงานบั๊กและส่งข้อเสนอแนะ</string>
<string name="about_improve">สร้าง &lt;a href=\"https://github.com/commons-app/apps-android-commons/issues\"&gt;GitHub issue&lt;/a&gt; ใหม่เพื่อรายงานบั๊กและส่งข้อเสนอแนะ</string>
<string name="about_privacy_policy" fuzzy="true">&lt;a href=\"https://github.com/commons-app/apps-android-commons/wiki/Privacy-policy\"&gt;นโยบายความเป็นส่วนตัว&lt;/a&gt;</string>
<string name="about_credits" fuzzy="true">&lt;a href=\"https://github.com/commons-app/apps-android-commons/blob/master/CREDITS\"&gt;เครดิต&lt;/a&gt;</string>
<string name="title_activity_about">เกี่ยวกับ</string>

View file

@ -185,4 +185,6 @@
<string name="description_info">Xin vui lòng miêu tả phương tiện càng đầy đủ càng tốt: Chụp ở đâu? Trong hình có gì? Bối cảnh làm sao? Xin vui lòng miêu tả các đối tượng và người trong hình. Cho biết những thông tin khó đoán ra, chẳng hạn giờ trong ngày nếu là phong cảnh. Nếu phương tiện có gì kỳ lạ, xin vui lòng giải thích tại sao nó kỳ lạ.</string>
<string name="use_external_storage">Sử dụng thiết bị lưu trữ bên ngoài</string>
<string name="use_external_storage_summary">Lưu các hình ảnh được chụp bằng máy chụp hình trong ứng dụng vào thiết bị của bạn</string>
<string name="nominate_deletion">Bầu chọn để xóa</string>
<string name="view_browser">Hiện lên ở trang xem browse</string>
</resources>

View file

@ -59,6 +59,7 @@
<string name="categories_search_text_hint">搜尋分類</string>
<string name="menu_save_categories">儲存</string>
<string name="refresh_button">重新整理</string>
<string name="display_list_button">清單</string>
<string name="gps_disabled">您的設備停用了 GPS。要啟用嗎</string>
<string name="enable_gps">啟用 GPS</string>
<string name="contributions_subtitle_zero">尚未上傳</string>
@ -211,6 +212,7 @@
<string name="no_description_found">找不到說明</string>
<string name="nearby_info_menu_commons_article">共享資源檔案頁面</string>
<string name="nearby_info_menu_wikidata_article">維基數據項目</string>
<string name="nearby_info_menu_wikipedia_article">維基百科條目</string>
<string name="error_while_cache">在快取圖片時發生錯誤</string>
<string name="title_info">用於本檔案的唯一描述性標題。您可以使用帶有空格的簡明語言,另外請不要包含副檔名。</string>
<string name="description_info">請盡可能說明媒體內容:拍攝於何處?是顯示什麼事物?有什麼脈絡?請描述對象或人物。透露出一些較不易猜測的訊息,例如是風景的話,可以是一天裡的時間。如果媒體顯示出一些不正常的事物,請說明出造成不正常原因。</string>
@ -236,6 +238,10 @@
<string name="notifications_thank_you_edit">感謝您所做的編輯</string>
<string name="notifications_mention">%1$s 在 %2$s 提到了您。</string>
<string name="toggle_view_button">切換檢視</string>
<string name="nearby_directions">重新導向</string>
<string name="nearby_wikidata">維基數據</string>
<string name="nearby_wikipedia">維基百科</string>
<string name="nearby_commons">維基共享資源</string>
<string name="about_faq">常見問題</string>
<string name="welcome_skip_button">跳過敎程</string>
</resources>

View file

@ -224,6 +224,8 @@
<string name="send_log_file_description">通过电子邮件将日志文件发送给开发人员</string>
<string name="no_web_browser">找不到可以打开URL的网页浏览器</string>
<string name="null_url">错误找不到URL</string>
<string name="nominate_deletion">提交删除</string>
<string name="view_browser">在浏览器中预览</string>
<string name="nearby_location_has_not_changed">位置没有更新。</string>
<string name="nearby_location_not_available">位置不可用。</string>
<string name="location_permission_rationale_nearby">需要权限以显示附近地点列表</string>

View file

@ -8,6 +8,13 @@
<attr name="uploadOverlayBackground" format="reference"/>
<attr name="spinnerTheme" format="reference"/>
<attr name="iconsTheme" format="reference"/>
<attr name="bottomBar" format="reference"/>
<attr name="buttonBackgroundFocused" format="reference"/>
<attr name="buttonBackgroundPressed" format="reference"/>
<attr name="buttonFocused" format="reference"/>
<attr name="buttonPressed" format="reference"/>
<attr name="textDisabled" format="reference"/>
<attr name="textEnabled" format="reference"/>
<declare-styleable name="CompatTextView">
<attr name="drawablePadding" format="dimension"/>

View file

@ -33,4 +33,20 @@
<color name="upload_overlay_background_dark">#77000000</color>
<color name="upload_overlay_background_light">#44000000</color>
<color name="pressed_button_light">#64999999</color>
<color name="focused_button_light">#32999999</color>
<color name="disabled_button_text_color_light">#48000000</color>
<color name="enabled_button_text_color_light">#B0000000</color>
<color name="pressed_button_dark">#40CCCCCC</color>
<color name="focused_button_dark">#26CCCCCC</color>
<color name="disabled_button_text_color_dark">#717171</color>
<color name="enabled_button_text_color_dark">#FFFFFF</color>
<color name="button_blue">#0c609c</color>
<color name="bottom_bar_light">#E0E0E0</color>
<color name="bottom_bar_dark">#424242</color>
<color name="opak_middle_grey">#757575</color>
</resources>

View file

@ -59,6 +59,7 @@
<string name="categories_search_text_hint">Search categories</string>
<string name="menu_save_categories">Save</string>
<string name="refresh_button">Refresh</string>
<string name="display_list_button">List</string>
<string name="gps_disabled">GPS is disabled in your device. Would you like to enable it?</string>
<string name="enable_gps">Enable GPS</string>
<string name="contributions_subtitle_zero">No uploads yet</string>
@ -217,6 +218,7 @@
<string name="no_description_found">no description found</string>
<string name="nearby_info_menu_commons_article">Commons file page</string>
<string name="nearby_info_menu_wikidata_article">Wikidata item</string>
<string name="nearby_info_menu_wikipedia_article">Wikipedia article</string>
<string name="error_while_cache">Error while caching pictures</string>
<string name="title_info">A unique descriptive title for the file, which will serve as a filename. You may use plain language with spaces. Do not include the file extension</string>
<string name="description_info">Please describe the media as much as possible: Where was it taken? What does it show? What is the context? Please describe the objects or persons. Reveal information that can not be easily guessed, for instance the time of day if it is a landscape. If the media shows something unusual, please explain what makes it unusual.</string>
@ -244,10 +246,20 @@
<string name="notifications_thank_you_edit">Thank you for making an edit</string>
<string name="notifications_mention">%1$s mentioned you on %2$s.</string>
<string name="toggle_view_button">Toggle view</string>
<string name="nearby_directions">DIRECTIONS</string>
<string name="nearby_wikidata">WIKIDATA</string>
<string name="nearby_wikipedia">WIKIPEDIA</string>
<string name="nearby_commons">COMMONS</string>
<string name="about_rate_us"><u>Rate Us</u></string>
<string name="about_faq">Frequently Asked Questions</string>
<string name="welcome_skip_button">Skip Tutorial</string>
<string name="error_notifications">Error fetching notifications</string>
<string name="no_notifications">No notifications found</string>
<string name="about_translate"><u>Translate</u></string>
<string name="about_translate_title">Languages</string>
<string name="about_translate_message">Select the langauge you want translations for ?</string>
<string name="about_translate_proceed">Proceed</string>
<string name="about_translate_cancel">Cancel</string>
</resources>

View file

@ -14,6 +14,11 @@
<item name="uploadOverlayBackground">@color/upload_overlay_background_dark</item>
<item name="spinnerTheme">@style/DarkSpinnerTheme</item>
<item name="iconsTheme">@color/main_background_light</item>
<item name="bottomBar">@color/bottom_bar_dark</item>
<item name="buttonBackgroundFocused">@color/focused_button_dark</item>
<item name="buttonBackgroundPressed">@color/pressed_button_dark</item>
<item name="textDisabled">@color/disabled_button_text_color_dark</item>
<item name="textEnabled">@color/enabled_button_text_color_dark</item>
</style>
<style name="LightAppTheme" parent="Theme.AppCompat.Light.NoActionBar">
@ -29,6 +34,11 @@
<item name="uploadOverlayBackground">@color/upload_overlay_background_light</item>
<item name="spinnerTheme">@style/LightSpinnerTheme</item>
<item name="iconsTheme">@color/main_background_dark</item>
<item name="bottomBar">@color/bottom_bar_light</item>
<item name="buttonBackgroundFocused">@color/focused_button_light</item>
<item name="buttonBackgroundPressed">@color/pressed_button_light</item>
<item name="textDisabled">@color/disabled_button_text_color_light</item>
<item name="textEnabled">@color/enabled_button_text_color_light</item>
</style>
<style name="DarkSpinnerTheme" parent="DarkAppTheme">

View file

@ -64,5 +64,4 @@
android:summary="@string/send_log_file_description"/>
</PreferenceCategory>
</PreferenceScreen>

View file

@ -8,6 +8,7 @@ SELECT
(SAMPLE(COALESCE(?emoji0, ?emoji1)) as ?emoji)
?wikipediaArticle
?commonsArticle
(SAMPLE(?Commons_category) as ?Commons_category)
WHERE {
# Around given location...
SERVICE wikibase:around {
@ -23,6 +24,9 @@ SELECT
OPTIONAL {?item rdfs:label ?item_label_preferred_language. FILTER (lang(?item_label_preferred_language) = "${LANG}")}
OPTIONAL {?item rdfs:label ?item_label_any_language}
# Get Commons category (P373)
OPTIONAL { ?item wdt:P373 ?Commons_category. }
# Get the class label in the preferred language of the user, or any other language if no label is available in that language.
OPTIONAL {
?item p:P31/ps:P31 ?classId.
@ -35,10 +39,6 @@ SELECT
# Get emoji
OPTIONAL { ?classId wdt:P487 ?emoji0. }
OPTIONAL { ?classId wdt:P279*/wdt:P487 ?emoji1. }
OPTIONAL {
?sitelink schema:about ?item .
?sitelink schema:inLanguage "en"
}
OPTIONAL {
?wikipediaArticle schema:about ?item ;
schema:isPartOf <https://en.wikipedia.org/> .

View file

@ -9,6 +9,9 @@ import com.squareup.leakcanary.RefWatcher;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import javax.inject.Named;
import dagger.Provides;
import fr.free.nrw.commons.auth.AccountUtil;
import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.caching.CacheController;
@ -79,7 +82,7 @@ public class TestCommonsApplication extends CommonsApplication {
}
@Override
public UploadController providesUploadController(Context context, SessionManager sessionManager, SharedPreferences sharedPreferences) {
public UploadController providesUploadController(SessionManager sessionManager, SharedPreferences sharedPreferences, Context context) {
return uploadController;
}

View file

@ -28,6 +28,7 @@ import io.reactivex.schedulers.Schedulers;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.robolectric.Shadows.shadowOf;
@ -41,7 +42,7 @@ public class NearbyActivityTest {
"name", Place.Label.AIRPORT,
"desc", null,
new LatLng(38.6270, -90.1994, 0),
null);
null, null);
@Mock
private LocationServiceManager locationManager;
@ -70,23 +71,23 @@ public class NearbyActivityTest {
MockitoAnnotations.initMocks(this);
when(locationManager.getLastLocation()).thenReturn(ST_LOUIS_MO_LAT_LNG);
when(locationManager.isProviderEnabled()).thenReturn(true);
when(nearbyController.loadAttractionsFromLocation(any(LatLng.class), any(Context.class)))
.thenReturn(Collections.singletonList(AIRPORT));
when(nearbyController.loadAttractionsFromLocation(any(LatLng.class)))
.thenReturn(mock(NearbyController.NearbyPlacesInfo.class));
}
@Test
public void pressRefreshAndShowList() {
MenuItem refresh = shadowOf(nearbyActivity).getOptionsMenu().findItem(R.id.action_refresh);
nearbyActivity.onOptionsItemSelected(refresh);
Fragment nearbyListFragment = nearbyActivity.getSupportFragmentManager()
.findFragmentByTag(NearbyListFragment.class.getSimpleName());
assertNotNull(nearbyListFragment);
// one element (AIRPORT) exists in the list
RecyclerView view = nearbyListFragment.getView().findViewById(R.id.listView);
assertNotNull(view.findViewHolderForAdapterPosition(0));
assertNull(view.findViewHolderForAdapterPosition(1));
// MenuItem refresh = shadowOf(nearbyActivity).getOptionsMenu().findItem(R.id.action_refresh);
// nearbyActivity.onOptionsItemSelected(refresh);
//
// Fragment nearbyListFragment = nearbyActivity.getSupportFragmentManager()
// .findFragmentByTag(NearbyListFragment.class.getSimpleName());
// assertNotNull(nearbyListFragment);
//
// // one element (AIRPORT) exists in the list
// RecyclerView view = nearbyListFragment.getView().findViewById(R.id.listView);
// assertNotNull(view.findViewHolderForAdapterPosition(0));
// assertNull(view.findViewHolderForAdapterPosition(1));
}
}

View file

@ -31,15 +31,16 @@ import static org.junit.Assert.assertNotNull;
public class NearbyAdapterFactoryTest {
private static final Place PLACE = new Place("name", Place.Label.AIRPORT,
"desc", null, new LatLng(38.6270, -90.1994, 0), null);
"desc", null, new LatLng(38.6270, -90.1994, 0), null, null);
private static final Place UNKNOWN_PLACE = new Place("name", Place.Label.UNKNOWN,
"?", null, new LatLng(39.7392, -104.9903, 0), null);
// ^ "?" is a special value for unknown class names from Wikidata query results
"desc", null, new LatLng(39.7392, -104.9903, 0), null, null);
private Place clickedPlace;
@Test
public void factoryHandlesNullListAndNullListener() {
NearbyAdapterFactory testObject = new NearbyAdapterFactory(null);
NearbyAdapterFactory testObject = new NearbyAdapterFactory();
RVRendererAdapter<Place> result = testObject.create(null);
assertNotNull(result);
assertEquals(0, result.getItemCount());
@ -47,7 +48,7 @@ public class NearbyAdapterFactoryTest {
@Test
public void factoryHandlesEmptyListAndNullListener() {
NearbyAdapterFactory testObject = new NearbyAdapterFactory(null);
NearbyAdapterFactory testObject = new NearbyAdapterFactory();
RVRendererAdapter<Place> result = testObject.create(Collections.<Place>emptyList());
assertNotNull(result);
assertEquals(0, result.getItemCount());
@ -55,7 +56,7 @@ public class NearbyAdapterFactoryTest {
@Test
public void factoryHandlesNonEmptyListAndNullListener() {
NearbyAdapterFactory testObject = new NearbyAdapterFactory(null);
NearbyAdapterFactory testObject = new NearbyAdapterFactory();
RVRendererAdapter<Place> result = testObject.create(Collections.singletonList(PLACE));
assertNotNull(result);
assertEquals(1, result.getItemCount());
@ -64,7 +65,7 @@ public class NearbyAdapterFactoryTest {
@Test
public void rendererCorrectlyBound() {
NearbyAdapterFactory testObject = new NearbyAdapterFactory(null);
NearbyAdapterFactory testObject = new NearbyAdapterFactory();
RVRendererAdapter<Place> result = testObject.create(Collections.singletonList(PLACE));
RendererViewHolder viewHolder = renderComponent(result);
@ -90,7 +91,7 @@ public class NearbyAdapterFactoryTest {
@Test
public void rendererCorrectlyBoundForUnknownPlace() {
NearbyAdapterFactory testObject = new NearbyAdapterFactory(null);
NearbyAdapterFactory testObject = new NearbyAdapterFactory();
RVRendererAdapter<Place> result = testObject.create(Collections.singletonList(UNKNOWN_PLACE));
RendererViewHolder viewHolder = renderComponent(result);
@ -107,7 +108,7 @@ public class NearbyAdapterFactoryTest {
@Test
public void clickView() {
NearbyAdapterFactory testObject = new NearbyAdapterFactory(new MockPlaceClickedListener());
NearbyAdapterFactory testObject = new NearbyAdapterFactory();
RVRendererAdapter<Place> result = testObject.create(Collections.singletonList(PLACE));
RendererViewHolder viewHolder = renderComponent(result);
@ -125,11 +126,4 @@ public class NearbyAdapterFactoryTest {
result.bindViewHolder(viewHolder, 0);
return viewHolder;
}
private class MockPlaceClickedListener implements PlaceRenderer.PlaceClickedListener {
@Override
public void placeClicked(Place place) {
clickedPlace = place;
}
}
}