Merge branch 'master' into master

This commit is contained in:
Hassan 2018-02-17 12:07:58 -06:00 committed by GitHub
commit b03e67dcd6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
66 changed files with 535 additions and 325 deletions

35
ISSUE_TEMPLATE.md Normal file
View file

@ -0,0 +1,35 @@
_Before creating an issue, please search the existing issues to see if a similar one has already been created. You can search issues by specific labels (e.g. `label:nearby `) or just by typing keywords into the search filter._
**Summary:**
Summarize your issue in one sentence (what goes wrong, what did you expect to happen)
**Steps to reproduce:**
How can we reproduce the issue?
**Add System logs:**
Add logcat files here (if possible).
**Expected behavior:**
What did you expect the App to do?
**Observed behavior:**
What did you see instead? Describe your issue in detail here.
**Device and Android version:**
What make and model device (e.g., Samsung J7) did you encounter this on? What Android
version (e.g., Android 4.0 Ice Cream Sandwich or Android 6.0 Marshmallow) are you running? Is it
the stock version from the manufacturer or a custom ROM ?
**Commons app version:**
You can find this information by going to the navigation drawer in the app and tapping 'About'
**Screen-shots:**
Can be created by pressing the Volume Down and Power Button at the same time on Android 4.0 and higher.

View file

@ -34,7 +34,7 @@ public class AboutActivity extends NavigationBaseActivity {
setContentView(R.layout.activity_about); setContentView(R.layout.activity_about);
ButterKnife.bind(this); ButterKnife.bind(this);
String aboutText = getString(R.string.about_license, getString(R.string.trademarked_name)); String aboutText = getString(R.string.about_license);
aboutLicenseText.setHtmlText(aboutText); aboutLicenseText.setHtmlText(aboutText);
versionText.setText(BuildConfig.VERSION_NAME); versionText.setText(BuildConfig.VERSION_NAME);

View file

@ -57,7 +57,6 @@ public class CommonsApplication extends Application {
public static final String FEEDBACK_EMAIL_SUBJECT = "Commons Android App (%s) Feedback"; public static final String FEEDBACK_EMAIL_SUBJECT = "Commons Android App (%s) Feedback";
private CommonsApplicationComponent component;
private RefWatcher refWatcher; private RefWatcher refWatcher;
@ -136,9 +135,10 @@ public class CommonsApplication extends Application {
.subscribe(() -> { .subscribe(() -> {
Timber.d("All accounts have been removed"); Timber.d("All accounts have been removed");
//TODO: fix preference manager //TODO: fix preference manager
defaultPrefs.edit().clear().commit(); defaultPrefs.edit().clear().apply();
applicationPrefs.edit().clear().commit(); applicationPrefs.edit().clear().apply();
applicationPrefs.edit().putBoolean("firstrun", false).apply();otherPrefs.edit().clear().commit(); applicationPrefs.edit().putBoolean("firstrun", false).apply();
otherPrefs.edit().clear().apply();
updateAllDatabases(); updateAllDatabases();
logoutListener.onLogoutComplete(); logoutListener.onLogoutComplete();

View file

@ -283,7 +283,7 @@ public class MediaDataExtractor {
/** /**
* Take our metadata and inject it into a live Media object. * Take our metadata and inject it into a live Media object.
* Media object might contain stale or cached data, or emptiness. * Media object might contain stale or cached data, or emptiness.
* @param media * @param media Media object to inject into
*/ */
public void fill(Media media) { public void fill(Media media) {
if (!fetched) { if (!fetched) {

View file

@ -65,7 +65,7 @@ public class Utils {
/** /**
* Capitalizes the first character of a string. * Capitalizes the first character of a string.
* *
* @param string * @param string String to alter
* @return string with capitalized first character * @return string with capitalized first character
*/ */
public static String capitalize(String string) { public static String capitalize(String string) {

View file

@ -32,7 +32,6 @@ import javax.inject.Named;
import butterknife.BindView; import butterknife.BindView;
import butterknife.ButterKnife; import butterknife.ButterKnife;
import dagger.android.AndroidInjection;
import fr.free.nrw.commons.BuildConfig; import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.PageTitle; import fr.free.nrw.commons.PageTitle;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;

View file

@ -1,128 +0,0 @@
package fr.free.nrw.commons.auth;
import android.accounts.AccountAuthenticatorResponse;
import android.app.ProgressDialog;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.os.Bundle;
import java.io.IOException;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.mwapi.MediaWikiApi;
import timber.log.Timber;
import static android.accounts.AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE;
import static android.accounts.AccountManager.KEY_ACCOUNT_NAME;
import static android.accounts.AccountManager.KEY_ACCOUNT_TYPE;
import static fr.free.nrw.commons.auth.AccountUtil.ACCOUNT_TYPE;
class LoginTask extends AsyncTask<String, String, String> {
private LoginActivity loginActivity;
private String username;
private String password;
private String twoFactorCode = "";
private AccountUtil accountUtil;
private MediaWikiApi mwApi;
public LoginTask(LoginActivity loginActivity, String username, String password,
String twoFactorCode, AccountUtil accountUtil,
MediaWikiApi mwApi, SharedPreferences prefs) {
this.loginActivity = loginActivity;
this.username = username;
this.password = password;
this.twoFactorCode = twoFactorCode;
this.accountUtil = accountUtil;
this.mwApi = mwApi;
}
@Override
protected void onPreExecute() {
super.onPreExecute();
loginActivity.progressDialog = new ProgressDialog(loginActivity);
loginActivity.progressDialog.setIndeterminate(true);
loginActivity.progressDialog.setTitle(loginActivity.getString(R.string.logging_in_title));
loginActivity.progressDialog.setMessage(loginActivity.getString(R.string.logging_in_message));
loginActivity.progressDialog.setCanceledOnTouchOutside(false);
loginActivity.progressDialog.show();
}
@Override
protected String doInBackground(String... params) {
try {
if (twoFactorCode.isEmpty()) {
return mwApi.login(username, password);
} else {
return mwApi.login(username, password, twoFactorCode);
}
} catch (IOException e) {
// Do something better!
return "NetworkFailure";
}
}
@Override
protected void onPostExecute(String result) {
super.onPostExecute(result);
Timber.d("Login done!");
if (result.equals("PASS")) {
handlePassResult();
} else {
handleOtherResults(result);
}
}
private void handlePassResult() {
loginActivity.showSuccessAndDismissDialog();
AccountAuthenticatorResponse response = null;
Bundle extras = loginActivity.getIntent().getExtras();
if (extras != null) {
Timber.d("Bundle of extras: %s", extras);
response = extras.getParcelable(KEY_ACCOUNT_AUTHENTICATOR_RESPONSE);
if (response != null) {
Bundle authResult = new Bundle();
authResult.putString(KEY_ACCOUNT_NAME, username);
authResult.putString(KEY_ACCOUNT_TYPE, ACCOUNT_TYPE);
response.onResult(authResult);
}
}
accountUtil.createAccount(response, username, password);
loginActivity.startMainActivity();
}
/**
* Match known failure message codes and provide messages.
* @param result String
*/
private void handleOtherResults(String result) {
if (result.equals("NetworkFailure")) {
// Matches NetworkFailure which is created by the doInBackground method
loginActivity.showMessageAndCancelDialog(R.string.login_failed_network);
} else if (result.toLowerCase().contains("nosuchuser".toLowerCase()) || result.toLowerCase().contains("noname".toLowerCase())) {
// Matches nosuchuser, nosuchusershort, noname
loginActivity.showMessageAndCancelDialog(R.string.login_failed_username);
loginActivity.emptySensitiveEditFields();
} else if (result.toLowerCase().contains("wrongpassword".toLowerCase())) {
// Matches wrongpassword, wrongpasswordempty
loginActivity.showMessageAndCancelDialog(R.string.login_failed_password);
loginActivity.emptySensitiveEditFields();
} else if (result.toLowerCase().contains("throttle".toLowerCase())) {
// Matches unknown throttle error codes
loginActivity.showMessageAndCancelDialog(R.string.login_failed_throttled);
} else if (result.toLowerCase().contains("userblocked".toLowerCase())) {
// Matches login-userblocked
loginActivity.showMessageAndCancelDialog(R.string.login_failed_blocked);
} else if (result.equals("2FA")) {
loginActivity.askUserForTwoFactorAuth();
} else {
// Occurs with unhandled login failure codes
Timber.d("Login failed with reason: %s", result);
loginActivity.showMessageAndCancelDialog(R.string.login_failed_generic);
}
}
}

View file

@ -1,6 +1,5 @@
package fr.free.nrw.commons.category; package fr.free.nrw.commons.category;
import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.Bundle; import android.os.Bundle;
import android.support.v7.app.AlertDialog; import android.support.v7.app.AlertDialog;
@ -298,8 +297,10 @@ public class CategorizationFragment extends CommonsDaggerSupportFragment {
//Check if item contains a 4-digit word anywhere within the string (.* is wildcard) //Check if item contains a 4-digit word anywhere within the string (.* is wildcard)
//And that item does not equal the current year or previous year //And that item does not equal the current year or previous year
//And if it is an irrelevant category such as Media_needing_categories_as_of_16_June_2017(Issue #750) //And if it is an irrelevant category such as Media_needing_categories_as_of_16_June_2017(Issue #750)
//Check if the year in the form of XX(X)0s is relevant, i.e. in the 2000s or 2010s as stated in Issue #1029
return ((item.matches(".*(19|20)\\d{2}.*") && !item.contains(yearInString) && !item.contains(prevYearInString)) return ((item.matches(".*(19|20)\\d{2}.*") && !item.contains(yearInString) && !item.contains(prevYearInString))
|| item.matches("(.*)needing(.*)") || item.matches("(.*)taken on(.*)")); || item.matches("(.*)needing(.*)") || item.matches("(.*)taken on(.*)")
|| (item.matches(".*0s.*") && !item.matches(".*(200|201)0s.*")));
} }
private void updateCategoryCount(CategoryItem item) { private void updateCategoryCount(CategoryItem item) {

View file

@ -93,10 +93,15 @@ class ContributionController {
shareIntent.putExtra(EXTRA_SOURCE, SOURCE_GALLERY); shareIntent.putExtra(EXTRA_SOURCE, SOURCE_GALLERY);
break; break;
case SELECT_FROM_CAMERA: case SELECT_FROM_CAMERA:
shareIntent.setType("image/jpeg"); //FIXME: Find out appropriate mime type //FIXME: Find out appropriate mime type
// AFAIK this is the right type for a JPEG image
// https://developer.android.com/training/sharing/send.html#send-binary-content
shareIntent.setType("image/jpeg");
shareIntent.putExtra(EXTRA_STREAM, lastGeneratedCaptureUri); shareIntent.putExtra(EXTRA_STREAM, lastGeneratedCaptureUri);
shareIntent.putExtra(EXTRA_SOURCE, SOURCE_CAMERA); shareIntent.putExtra(EXTRA_SOURCE, SOURCE_CAMERA);
break; break;
default:
break;
} }
Timber.i("Image selected"); Timber.i("Image selected");
try { try {

View file

@ -86,7 +86,7 @@ public class ContributionsContentProvider extends CommonsDaggerContentProvider {
public Uri insert(@NonNull Uri uri, ContentValues contentValues) { public Uri insert(@NonNull Uri uri, ContentValues contentValues) {
int uriType = uriMatcher.match(uri); int uriType = uriMatcher.match(uri);
SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase(); SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
long id = 0; long id;
switch (uriType) { switch (uriType) {
case CONTRIBUTIONS: case CONTRIBUTIONS:
id = sqlDB.insert(TABLE_NAME, null, contentValues); id = sqlDB.insert(TABLE_NAME, null, contentValues);
@ -158,7 +158,7 @@ public class ContributionsContentProvider extends CommonsDaggerContentProvider {
*/ */
int uriType = uriMatcher.match(uri); int uriType = uriMatcher.match(uri);
SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase(); SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
int rowsUpdated = 0; int rowsUpdated;
switch (uriType) { switch (uriType) {
case CONTRIBUTIONS: case CONTRIBUTIONS:
rowsUpdated = sqlDB.update(TABLE_NAME, contentValues, selection, selectionArgs); rowsUpdated = sqlDB.update(TABLE_NAME, contentValues, selection, selectionArgs);

View file

@ -20,6 +20,8 @@ import android.widget.ListAdapter;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import android.widget.TextView; import android.widget.TextView;
import java.util.Arrays;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named; import javax.inject.Named;
@ -45,8 +47,12 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment {
@BindView(R.id.loadingContributionsProgressBar) @BindView(R.id.loadingContributionsProgressBar)
ProgressBar progressBar; ProgressBar progressBar;
@Inject @Named("prefs") SharedPreferences prefs; @Inject
@Inject @Named("default_preferences") SharedPreferences defaultPrefs; @Named("prefs")
SharedPreferences prefs;
@Inject
@Named("default_preferences")
SharedPreferences defaultPrefs;
private ContributionController controller; private ContributionController controller;
@ -208,7 +214,7 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment {
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) { @NonNull int[] grantResults) {
Timber.d("onRequestPermissionsResult: req code = " + " perm = " Timber.d("onRequestPermissionsResult: req code = " + " perm = "
+ permissions + " grant =" + grantResults); + Arrays.toString(permissions) + " grant =" + Arrays.toString(grantResults));
switch (requestCode) { switch (requestCode) {
// 1 = Storage allowed when gallery selected // 1 = Storage allowed when gallery selected

View file

@ -23,7 +23,6 @@ import java.util.TimeZone;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named; import javax.inject.Named;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.Utils; import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.di.ApplicationlessInjection; import fr.free.nrw.commons.di.ApplicationlessInjection;
import fr.free.nrw.commons.mwapi.LogEventResult; import fr.free.nrw.commons.mwapi.LogEventResult;

View file

@ -1,7 +1,5 @@
package fr.free.nrw.commons.di; package fr.free.nrw.commons.di;
import android.content.Context;
import javax.inject.Singleton; import javax.inject.Singleton;
import dagger.Component; import dagger.Component;
@ -11,10 +9,7 @@ import dagger.android.support.AndroidSupportInjectionModule;
import fr.free.nrw.commons.CommonsApplication; import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.MediaWikiImageView; import fr.free.nrw.commons.MediaWikiImageView;
import fr.free.nrw.commons.auth.LoginActivity; import fr.free.nrw.commons.auth.LoginActivity;
import fr.free.nrw.commons.category.CategoryContentProvider;
import fr.free.nrw.commons.contributions.ContributionsContentProvider;
import fr.free.nrw.commons.contributions.ContributionsSyncAdapter; import fr.free.nrw.commons.contributions.ContributionsSyncAdapter;
import fr.free.nrw.commons.modifications.ModificationsContentProvider;
import fr.free.nrw.commons.modifications.ModificationsSyncAdapter; import fr.free.nrw.commons.modifications.ModificationsSyncAdapter;
import fr.free.nrw.commons.settings.SettingsFragment; import fr.free.nrw.commons.settings.SettingsFragment;

View file

@ -38,7 +38,7 @@ public class LatLng {
/** /**
* gets the latitude and longitude of a given non-null location * gets the latitude and longitude of a given non-null location
* @param location the non-null location of the user * @param location the non-null location of the user
* @return * @return LatLng the Latitude and Longitude of a given location
*/ */
public static LatLng from(@NonNull Location location) { public static LatLng from(@NonNull Location location) {
return new LatLng(location.getLatitude(), location.getLongitude(), location.getAccuracy()); return new LatLng(location.getLatitude(), location.getLongitude(), location.getAccuracy());
@ -48,13 +48,12 @@ public class LatLng {
* creates a hash code for the longitude and longitude * creates a hash code for the longitude and longitude
*/ */
public int hashCode() { public int hashCode() {
boolean var1 = true; byte var1 = 1;
byte var2 = 1; long var2 = Double.doubleToLongBits(this.latitude);
long var3 = Double.doubleToLongBits(this.latitude); int var3 = 31 * var1 + (int)(var2 ^ var2 >>> 32);
int var5 = 31 * var2 + (int)(var3 ^ var3 >>> 32); var2 = Double.doubleToLongBits(this.longitude);
var3 = Double.doubleToLongBits(this.longitude); var3 = 31 * var3 + (int)(var2 ^ var2 >>> 32);
var5 = 31 * var5 + (int)(var3 ^ var3 >>> 32); return var3;
return var5;
} }
/** /**

View file

@ -14,9 +14,6 @@ import android.support.v4.content.ContextCompat;
import java.util.List; import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
import javax.inject.Inject;
import javax.inject.Singleton;
import timber.log.Timber; import timber.log.Timber;
public class LocationServiceManager implements LocationListener { public class LocationServiceManager implements LocationListener {
@ -33,6 +30,7 @@ public class LocationServiceManager implements LocationListener {
/** /**
* Constructs a new instance of LocationServiceManager. * Constructs a new instance of LocationServiceManager.
*
* @param context the context * @param context the context
*/ */
public LocationServiceManager(Context context) { public LocationServiceManager(Context context) {
@ -42,6 +40,7 @@ public class LocationServiceManager implements LocationListener {
/** /**
* Returns the current status of the GPS provider. * Returns the current status of the GPS provider.
*
* @return true if the GPS provider is enabled * @return true if the GPS provider is enabled
*/ */
public boolean isProviderEnabled() { public boolean isProviderEnabled() {
@ -50,6 +49,7 @@ public class LocationServiceManager implements LocationListener {
/** /**
* Returns whether the location permission is granted. * Returns whether the location permission is granted.
*
* @return true if the location permission is granted * @return true if the location permission is granted
*/ */
public boolean isLocationPermissionGranted() { public boolean isLocationPermissionGranted() {
@ -59,6 +59,7 @@ public class LocationServiceManager implements LocationListener {
/** /**
* Requests the location permission to be granted. * Requests the location permission to be granted.
*
* @param activity the activity * @param activity the activity
*/ */
public void requestPermissions(Activity activity) { public void requestPermissions(Activity activity) {
@ -71,11 +72,9 @@ public class LocationServiceManager implements LocationListener {
} }
public boolean isPermissionExplanationRequired(Activity activity) { public boolean isPermissionExplanationRequired(Activity activity) {
if (activity.isFinishing()) { return !activity.isFinishing() &&
return false; ActivityCompat.shouldShowRequestPermissionRationale(activity,
} Manifest.permission.ACCESS_FINE_LOCATION);
return ActivityCompat.shouldShowRequestPermissionRationale(activity,
Manifest.permission.ACCESS_FINE_LOCATION);
} }
public LatLng getLastLocation() { public LatLng getLastLocation() {
@ -85,7 +84,8 @@ public class LocationServiceManager implements LocationListener {
return LatLng.from(lastLocation); return LatLng.from(lastLocation);
} }
/** Registers a LocationManager to listen for current location. /**
* Registers a LocationManager to listen for current location.
*/ */
public void registerLocationManager() { public void registerLocationManager() {
if (!isLocationManagerRegistered) if (!isLocationManagerRegistered)
@ -95,6 +95,7 @@ public class LocationServiceManager implements LocationListener {
/** /**
* Requests location updates from the specified provider. * Requests location updates from the specified provider.
*
* @param locationProvider the location provider * @param locationProvider the location provider
* @return true if successful * @return true if successful
*/ */
@ -116,7 +117,8 @@ public class LocationServiceManager implements LocationListener {
/** /**
* Returns whether a given location is better than the current best location. * Returns whether a given location is better than the current best location.
* @param location the location to be tested *
* @param location the location to be tested
* @param currentBestLocation the current best location * @param currentBestLocation the current best location
* @return true if the given location is better * @return true if the given location is better
*/ */
@ -172,7 +174,8 @@ public class LocationServiceManager implements LocationListener {
return provider1.equals(provider2); return provider1.equals(provider2);
} }
/** Unregisters location manager. /**
* Unregisters location manager.
*/ */
public void unregisterLocationManager() { public void unregisterLocationManager() {
isLocationManagerRegistered = false; isLocationManagerRegistered = false;
@ -185,6 +188,7 @@ public class LocationServiceManager implements LocationListener {
/** /**
* Adds a new listener to the list of location listeners. * Adds a new listener to the list of location listeners.
*
* @param listener the new listener * @param listener the new listener
*/ */
public void addLocationListener(LocationUpdateListener listener) { public void addLocationListener(LocationUpdateListener listener) {
@ -195,6 +199,7 @@ public class LocationServiceManager implements LocationListener {
/** /**
* Removes a listener from the list of location listeners. * Removes a listener from the list of location listeners.
*
* @param listener the listener to be removed * @param listener the listener to be removed
*/ */
public void removeLocationListener(LocationUpdateListener listener) { public void removeLocationListener(LocationUpdateListener listener) {

View file

@ -77,7 +77,7 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
private ViewTreeObserver.OnGlobalLayoutListener layoutListener; // for layout stuff, only used once! private ViewTreeObserver.OnGlobalLayoutListener layoutListener; // for layout stuff, only used once!
private ViewTreeObserver.OnScrollChangedListener scrollListener; private ViewTreeObserver.OnScrollChangedListener scrollListener;
private DataSetObserver dataObserver; private DataSetObserver dataObserver;
private AsyncTask<Void,Void,Boolean> detailFetchTask; private AsyncTask<Void, Void, Boolean> detailFetchTask;
private LicenseList licenseList; private LicenseList licenseList;
@Override @Override
@ -96,7 +96,7 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
detailProvider = (MediaDetailPagerFragment.MediaDetailProvider)getActivity(); detailProvider = (MediaDetailPagerFragment.MediaDetailProvider) getActivity();
if (savedInstanceState != null) { if (savedInstanceState != null) {
editable = savedInstanceState.getBoolean("editable"); editable = savedInstanceState.getBoolean("editable");
@ -157,7 +157,8 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
return view; return view;
} }
@Override public void onResume() { @Override
public void onResume() {
super.onResume(); super.onResume();
Media media = detailProvider.getMediaAtPosition(index); Media media = detailProvider.getMediaAtPosition(index);
if (media == null) { if (media == null) {
@ -239,13 +240,13 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
detailFetchTask.cancel(true); detailFetchTask.cancel(true);
detailFetchTask = null; detailFetchTask = null;
} }
if (layoutListener != null) { if (layoutListener != null && getView() != null) {
getView().getViewTreeObserver().removeGlobalOnLayoutListener(layoutListener); // old Android was on crack. CRACK IS WHACK getView().getViewTreeObserver().removeGlobalOnLayoutListener(layoutListener); // old Android was on crack. CRACK IS WHACK
layoutListener = null; layoutListener = null;
} }
if (scrollListener != null) { if (scrollListener != null && getView() != null) {
getView().getViewTreeObserver().removeOnScrollChangedListener(scrollListener); getView().getViewTreeObserver().removeOnScrollChangedListener(scrollListener);
scrollListener = null; scrollListener = null;
} }
if (dataObserver != null) { if (dataObserver != null) {
detailProvider.unregisterDataSetObserver(dataObserver); detailProvider.unregisterDataSetObserver(dataObserver);
@ -290,7 +291,7 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
private View buildCatLabel(final String catName, ViewGroup categoryContainer) { private View buildCatLabel(final String catName, ViewGroup categoryContainer) {
final View item = LayoutInflater.from(getContext()).inflate(R.layout.detail_category_item, categoryContainer, false); final View item = LayoutInflater.from(getContext()).inflate(R.layout.detail_category_item, categoryContainer, false);
final CompatTextView textView = (CompatTextView)item.findViewById(R.id.mediaDetailCategoryItemText); final CompatTextView textView = (CompatTextView) item.findViewById(R.id.mediaDetailCategoryItemText);
textView.setText(catName); textView.setText(catName);
if (categoriesLoaded && categoriesPresent) { if (categoriesLoaded && categoriesPresent) {
@ -317,7 +318,7 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
// You must face the darkness alone // You must face the darkness alone
int scrollY = scrollView.getScrollY(); int scrollY = scrollView.getScrollY();
int scrollMax = getView().getHeight(); int scrollMax = getView().getHeight();
float scrollPercentage = (float)scrollY / (float)scrollMax; float scrollPercentage = (float) scrollY / (float) scrollMax;
final float transparencyMax = 0.75f; final float transparencyMax = 0.75f;
if (scrollPercentage > transparencyMax) { if (scrollPercentage > transparencyMax) {
scrollPercentage = transparencyMax; scrollPercentage = transparencyMax;
@ -371,7 +372,8 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
} }
private @Nullable String licenseLink(Media media) { private @Nullable
String licenseLink(Media media) {
String licenseKey = media.getLicense(); String licenseKey = media.getLicense();
if (licenseKey == null || licenseKey.equals("")) { if (licenseKey == null || licenseKey.equals("")) {
return null; return null;

View file

@ -44,9 +44,13 @@ import static android.content.pm.PackageManager.PERMISSION_GRANTED;
public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment implements ViewPager.OnPageChangeListener { public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment implements ViewPager.OnPageChangeListener {
@Inject MediaWikiApi mwApi; @Inject
@Inject SessionManager sessionManager; MediaWikiApi mwApi;
@Inject @Named("default_preferences") SharedPreferences prefs; @Inject
SessionManager sessionManager;
@Inject
@Named("default_preferences")
SharedPreferences prefs;
private ViewPager pager; private ViewPager pager;
private Boolean editable; private Boolean editable;
@ -175,13 +179,19 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple
req.allowScanningByMediaScanner(); req.allowScanningByMediaScanner();
req.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); req.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !(ContextCompat.checkSelfPermission(getContext(), READ_EXTERNAL_STORAGE) == PERMISSION_GRANTED)) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
ContextCompat.checkSelfPermission(getContext(), READ_EXTERNAL_STORAGE)
!= PERMISSION_GRANTED
&& getView() != null) {
Snackbar.make(getView(), R.string.read_storage_permission_rationale, Snackbar.make(getView(), R.string.read_storage_permission_rationale,
Snackbar.LENGTH_INDEFINITE).setAction(R.string.ok, Snackbar.LENGTH_INDEFINITE).setAction(R.string.ok,
view -> ActivityCompat.requestPermissions(getActivity(), view -> ActivityCompat.requestPermissions(getActivity(),
new String[]{READ_EXTERNAL_STORAGE}, 1)).show(); new String[]{READ_EXTERNAL_STORAGE}, 1)).show();
} else { } else {
((DownloadManager) getActivity().getSystemService(DOWNLOAD_SERVICE)).enqueue(req); DownloadManager systemService = (DownloadManager) getActivity().getSystemService(DOWNLOAD_SERVICE);
if (systemService != null) {
systemService.enqueue(req);
}
} }
} }

View file

@ -70,7 +70,7 @@ public class ModificationsContentProvider extends CommonsDaggerContentProvider {
public Uri insert(@NonNull Uri uri, ContentValues contentValues) { public Uri insert(@NonNull Uri uri, ContentValues contentValues) {
int uriType = uriMatcher.match(uri); int uriType = uriMatcher.match(uri);
SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase(); SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
long id = 0; long id;
switch (uriType) { switch (uriType) {
case MODIFICATIONS: case MODIFICATIONS:
id = sqlDB.insert(TABLE_NAME, null, contentValues); id = sqlDB.insert(TABLE_NAME, null, contentValues);
@ -132,7 +132,7 @@ public class ModificationsContentProvider extends CommonsDaggerContentProvider {
*/ */
int uriType = uriMatcher.match(uri); int uriType = uriMatcher.match(uri);
SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase(); SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
int rowsUpdated = 0; int rowsUpdated;
switch (uriType) { switch (uriType) {
case MODIFICATIONS: case MODIFICATIONS:
rowsUpdated = sqlDB.update(TABLE_NAME, rowsUpdated = sqlDB.update(TABLE_NAME,

View file

@ -52,7 +52,7 @@ public class ModifierSequenceDao {
ModifierSequence fromCursor(Cursor cursor) { ModifierSequence fromCursor(Cursor cursor) {
// Hardcoding column positions! // Hardcoding column positions!
ModifierSequence ms = null; ModifierSequence ms;
try { try {
ms = new ModifierSequence(Uri.parse(cursor.getString(1)), ms = new ModifierSequence(Uri.parse(cursor.getString(1)),
new JSONObject(cursor.getString(2))); new JSONObject(cursor.getString(2)));

View file

@ -1,6 +1,5 @@
package fr.free.nrw.commons.nearby; package fr.free.nrw.commons.nearby;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
@ -41,8 +40,6 @@ import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers; import io.reactivex.schedulers.Schedulers;
import timber.log.Timber; import timber.log.Timber;
import static fr.free.nrw.commons.location.LocationServiceManager.LOCATION_REQUEST;
public class NearbyActivity extends NavigationBaseActivity implements LocationUpdateListener { public class NearbyActivity extends NavigationBaseActivity implements LocationUpdateListener {
@ -259,7 +256,7 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
/** /**
* This method should be the single point to load/refresh nearby places * This method should be the single point to load/refresh nearby places
* *
* @param isHardRefresh * @param isHardRefresh Should display a toast if the location hasn't changed
*/ */
private void refreshView(boolean isHardRefresh) { private void refreshView(boolean isHardRefresh) {
if (lockNearbyView) { if (lockNearbyView) {

View file

@ -39,8 +39,9 @@ public class NearbyController {
/** /**
* Prepares Place list to make their distance information update later. * Prepares Place list to make their distance information update later.
*
* @param curLatLng current location for user * @param curLatLng current location for user
* @param context context * @param context context
* @return Place list without distance information * @return Place list without distance information
*/ */
public List<Place> loadAttractionsFromLocation(LatLng curLatLng, Context context) { public List<Place> loadAttractionsFromLocation(LatLng curLatLng, Context context) {
@ -51,25 +52,24 @@ public class NearbyController {
List<Place> places = prefs.getBoolean("useWikidata", true) List<Place> places = prefs.getBoolean("useWikidata", true)
? nearbyPlaces.getFromWikidataQuery(curLatLng, Locale.getDefault().getLanguage()) ? nearbyPlaces.getFromWikidataQuery(curLatLng, Locale.getDefault().getLanguage())
: nearbyPlaces.getFromWikiNeedsPictures(); : nearbyPlaces.getFromWikiNeedsPictures();
if (curLatLng != null) { Timber.d("Sorting places by distance...");
Timber.d("Sorting places by distance..."); final Map<Place, Double> distances = new HashMap<>();
final Map<Place, Double> distances = new HashMap<>(); for (Place place : places) {
for (Place place: places) { distances.put(place, computeDistanceBetween(place.location, curLatLng));
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);
}
);
} }
Collections.sort(places,
(lhs, rhs) -> {
double lhsDistance = distances.get(lhs);
double rhsDistance = distances.get(rhs);
return (int) (lhsDistance - rhsDistance);
}
);
return places; return places;
} }
/** /**
* Loads attractions from location for list view, we need to return Place data type. * Loads attractions from location for list view, we need to return Place data type.
*
* @param curLatLng users current location * @param curLatLng users current location
* @param placeList list of nearby places in Place data type * @param placeList list of nearby places in Place data type
* @return Place list that holds nearby places * @return Place list that holds nearby places
@ -78,7 +78,7 @@ public class NearbyController {
LatLng curLatLng, LatLng curLatLng,
List<Place> placeList) { List<Place> placeList) {
placeList = placeList.subList(0, Math.min(placeList.size(), MAX_RESULTS)); placeList = placeList.subList(0, Math.min(placeList.size(), MAX_RESULTS));
for (Place place: placeList) { for (Place place : placeList) {
String distance = formatDistanceBetween(curLatLng, place.location); String distance = formatDistanceBetween(curLatLng, place.location);
place.setDistance(distance); place.setDistance(distance);
} }
@ -86,7 +86,8 @@ public class NearbyController {
} }
/** /**
*Loads attractions from location for map view, we need to return BaseMarkerOption data type. * Loads attractions from location for map view, we need to return BaseMarkerOption data type.
*
* @param curLatLng users current location * @param curLatLng users current location
* @param placeList list of nearby places in Place data type * @param placeList list of nearby places in Place data type
* @return BaseMarkerOptions list that holds nearby places * @return BaseMarkerOptions list that holds nearby places
@ -103,26 +104,28 @@ public class NearbyController {
placeList = placeList.subList(0, Math.min(placeList.size(), MAX_RESULTS)); placeList = placeList.subList(0, Math.min(placeList.size(), MAX_RESULTS));
Bitmap icon = UiUtils.getBitmap( VectorDrawableCompat vectorDrawable = VectorDrawableCompat.create(
VectorDrawableCompat.create( context.getResources(), R.drawable.ic_custom_map_marker, context.getTheme()
context.getResources(), R.drawable.ic_custom_map_marker, context.getTheme() );
)); if (vectorDrawable != null) {
Bitmap icon = UiUtils.getBitmap(vectorDrawable);
for (Place place: placeList) { for (Place place : placeList) {
String distance = formatDistanceBetween(curLatLng, place.location); String distance = formatDistanceBetween(curLatLng, place.location);
place.setDistance(distance); place.setDistance(distance);
NearbyBaseMarker nearbyBaseMarker = new NearbyBaseMarker(); NearbyBaseMarker nearbyBaseMarker = new NearbyBaseMarker();
nearbyBaseMarker.title(place.name); nearbyBaseMarker.title(place.name);
nearbyBaseMarker.position( nearbyBaseMarker.position(
new com.mapbox.mapboxsdk.geometry.LatLng( new com.mapbox.mapboxsdk.geometry.LatLng(
place.location.getLatitude(), place.location.getLatitude(),
place.location.getLongitude())); place.location.getLongitude()));
nearbyBaseMarker.place(place); nearbyBaseMarker.place(place);
nearbyBaseMarker.icon(IconFactory.getInstance(context) nearbyBaseMarker.icon(IconFactory.getInstance(context)
.fromBitmap(icon)); .fromBitmap(icon));
baseMarkerOptions.add(nearbyBaseMarker); baseMarkerOptions.add(nearbyBaseMarker);
}
} }
return baseMarkerOptions; return baseMarkerOptions;
} }

View file

@ -19,7 +19,6 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import dagger.android.support.AndroidSupportInjection; import dagger.android.support.AndroidSupportInjection;
import dagger.android.support.DaggerFragment;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import fr.free.nrw.commons.location.LatLng; import fr.free.nrw.commons.location.LatLng;
import fr.free.nrw.commons.utils.UriDeserializer; import fr.free.nrw.commons.utils.UriDeserializer;

View file

@ -9,7 +9,6 @@ import android.view.ViewGroup;
import butterknife.ButterKnife; import butterknife.ButterKnife;
import dagger.android.support.AndroidSupportInjection; import dagger.android.support.AndroidSupportInjection;
import dagger.android.support.DaggerFragment;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import timber.log.Timber; import timber.log.Timber;

View file

@ -61,9 +61,7 @@ public class NotificationActivity extends NavigationBaseActivity {
.subscribe(notificationList -> { .subscribe(notificationList -> {
Timber.d("Number of notifications is %d", notificationList.size()); Timber.d("Number of notifications is %d", notificationList.size());
setAdapter(notificationList); setAdapter(notificationList);
}, throwable -> { }, throwable -> Timber.e(throwable, "Error occurred while loading notifications"));
Timber.e(throwable, "Error occurred while loading notifications");
});
} }
private void handleUrl(String url) { private void handleUrl(String url) {

View file

@ -8,13 +8,9 @@ import android.widget.TextView;
import com.pedrogomez.renderers.Renderer; import com.pedrogomez.renderers.Renderer;
import java.util.Calendar;
import java.util.Date;
import butterknife.BindView; import butterknife.BindView;
import butterknife.ButterKnife; import butterknife.ButterKnife;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import fr.free.nrw.commons.utils.DateUtils;
/** /**
* Created by root on 19.12.2017. * Created by root on 19.12.2017.

View file

@ -25,7 +25,6 @@ import java.io.File;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named; import javax.inject.Named;
import dagger.android.AndroidInjection;
import fr.free.nrw.commons.BuildConfig; import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.CommonsApplication; import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
@ -71,7 +70,12 @@ public class SettingsFragment extends PreferenceFragment {
uploadLimit.setText(uploads + ""); uploadLimit.setText(uploads + "");
uploadLimit.setSummary(uploads + ""); uploadLimit.setSummary(uploads + "");
uploadLimit.setOnPreferenceChangeListener((preference, newValue) -> { uploadLimit.setOnPreferenceChangeListener((preference, newValue) -> {
int value = Integer.parseInt(newValue.toString()); int value;
try {
value = Integer.parseInt(newValue.toString());
} catch(Exception e) {
value = 100; //Default number
}
final SharedPreferences.Editor editor = prefs.edit(); final SharedPreferences.Editor editor = prefs.edit();
if (value > 500) { if (value > 500) {
new AlertDialog.Builder(getActivity()) new AlertDialog.Builder(getActivity())
@ -85,9 +89,9 @@ public class SettingsFragment extends PreferenceFragment {
uploadLimit.setSummary(500 + ""); uploadLimit.setSummary(500 + "");
uploadLimit.setText(500 + ""); uploadLimit.setText(500 + "");
} else { } else {
editor.putInt(Prefs.UPLOADS_SHOWING, Integer.parseInt(newValue.toString())); editor.putInt(Prefs.UPLOADS_SHOWING, value);
editor.putBoolean(Prefs.IS_CONTRIBUTION_COUNT_CHANGED,true); editor.putBoolean(Prefs.IS_CONTRIBUTION_COUNT_CHANGED,true);
uploadLimit.setSummary(newValue.toString()); uploadLimit.setSummary(String.valueOf(value));
} }
editor.apply(); editor.apply();
return true; return true;

View file

@ -1,7 +1,7 @@
package fr.free.nrw.commons.ui.widget; package fr.free.nrw.commons.ui.widget;
/** /*
* Created by mikel on 07/08/2017. *Created by mikel on 07/08/2017.
*/ */
import android.content.Context; import android.content.Context;
@ -20,20 +20,22 @@ import fr.free.nrw.commons.utils.UiUtils;
* a text view compatible with older versions of the platform * a text view compatible with older versions of the platform
*/ */
public class CompatTextView extends AppCompatTextView { public class CompatTextView extends AppCompatTextView {
/** /**
* Constructs a new instance of CompatTextView * Constructs a new instance of CompatTextView
*
* @param context the view context * @param context the view context
*/ */
public CompatTextView(Context context) { public CompatTextView(Context context) {
super(context); super(context);
init(null); init(null);
} }
/** /**
* Constructs a new instance of CompatTextView * Constructs a new instance of CompatTextView
*
* @param context the view context * @param context the view context
* @param attrs the set of attributes for the view * @param attrs the set of attributes for the view
*/ */
public CompatTextView(Context context, AttributeSet attrs) { public CompatTextView(Context context, AttributeSet attrs) {
super(context, attrs); super(context, attrs);
@ -42,6 +44,7 @@ public class CompatTextView extends AppCompatTextView {
/** /**
* Constructs a new instance of CompatTextView * Constructs a new instance of CompatTextView
*
* @param context * @param context
* @param attrs * @param attrs
* @param defStyleAttr * @param defStyleAttr
@ -53,6 +56,7 @@ public class CompatTextView extends AppCompatTextView {
/** /**
* initializes the view * initializes the view
*
* @param attrs the attribute set of the view, which can be null * @param attrs the attribute set of the view, which can be null
*/ */
private void init(@Nullable AttributeSet attrs) { private void init(@Nullable AttributeSet attrs) {

View file

@ -19,7 +19,7 @@ public abstract class OverlayDialog extends DialogFragment {
/** /**
* creates a DialogFragment with the correct style and theme * creates a DialogFragment with the correct style and theme
* @param savedInstanceState * @param savedInstanceState bundle re-constructed from a previous saved state
*/ */
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {

View file

@ -28,7 +28,7 @@ public class FileUtils {
* other file-based ContentProviders. * other file-based ContentProviders.
* *
* @param context The context. * @param context The context.
* @param uri The Uri to query. * @param uri The Uri to query.
* @author paulburke * @author paulburke
*/ */
// Can be safely suppressed, checks for isKitKat before running isDocumentUri // Can be safely suppressed, checks for isKitKat before running isDocumentUri
@ -49,29 +49,31 @@ public class FileUtils {
if ("primary".equalsIgnoreCase(type)) { if ("primary".equalsIgnoreCase(type)) {
return Environment.getExternalStorageDirectory() + "/" + split[1]; return Environment.getExternalStorageDirectory() + "/" + split[1];
} }
} } else if (isDownloadsDocument(uri)) { // DownloadsProvider
// DownloadsProvider
else if (isDownloadsDocument(uri)) {
final String id = DocumentsContract.getDocumentId(uri); final String id = DocumentsContract.getDocumentId(uri);
final Uri contentUri = ContentUris.withAppendedId( final Uri contentUri = ContentUris.withAppendedId(
Uri.parse("content://downloads/public_downloads"), Long.valueOf(id)); Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
return getDataColumn(context, contentUri, null, null); return getDataColumn(context, contentUri, null, null);
} } else if (isMediaDocument(uri)) { // MediaProvider
// MediaProvider
else if (isMediaDocument(uri)) {
final String docId = DocumentsContract.getDocumentId(uri); final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":"); final String[] split = docId.split(":");
final String type = split[0]; final String type = split[0];
Uri contentUri = null; Uri contentUri = null;
if ("image".equals(type)) { switch (type) {
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; case "image":
} else if ("video".equals(type)) { contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; break;
} else if ("audio".equals(type)) { case "video":
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
break;
case "audio":
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
break;
default:
break;
} }
final String selection = "_id=?"; final String selection = "_id=?";
@ -163,7 +165,8 @@ public class FileUtils {
/** /**
* Copy content from source file to destination file. * Copy content from source file to destination file.
* @param source stream copied from *
* @param source stream copied from
* @param destination stream copied to * @param destination stream copied to
* @throws IOException thrown when failing to read source or opening destination file * @throws IOException thrown when failing to read source or opening destination file
*/ */
@ -176,7 +179,8 @@ public class FileUtils {
/** /**
* Copy content from source file to destination file. * Copy content from source file to destination file.
* @param source file descriptor copied from *
* @param source file descriptor copied from
* @param destination file path copied to * @param destination file path copied to
* @throws IOException thrown when failing to read source or opening destination file * @throws IOException thrown when failing to read source or opening destination file
*/ */

View file

@ -113,11 +113,11 @@ public class GPSExtractor {
*/ */
@Nullable @Nullable
public String getCoords(boolean useGPS) { public String getCoords(boolean useGPS) {
String latitude = ""; String latitude;
String longitude = ""; String longitude;
String latitude_ref = ""; String latitudeRef;
String longitude_ref = ""; String longitudeRef;
String decimalCoords = ""; String decimalCoords;
//If image has no EXIF data and user has enabled GPS setting, get user's location //If image has no EXIF data and user has enabled GPS setting, get user's location
if (exif == null || exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE) == null) { if (exif == null || exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE) == null) {
@ -150,15 +150,15 @@ public class GPSExtractor {
Timber.d("EXIF data has location info"); Timber.d("EXIF data has location info");
latitude = exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE); latitude = exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE);
latitude_ref = exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE_REF); latitudeRef = exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE_REF);
longitude = exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE); longitude = exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE);
longitude_ref = exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF); longitudeRef = exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF);
if (latitude!=null && latitude_ref!=null && longitude!=null && longitude_ref!=null) { if (latitude!=null && latitudeRef!=null && longitude!=null && longitudeRef!=null) {
Timber.d("Latitude: %s %s", latitude, latitude_ref); Timber.d("Latitude: %s %s", latitude, latitudeRef);
Timber.d("Longitude: %s %s", longitude, longitude_ref); Timber.d("Longitude: %s %s", longitude, longitudeRef);
decimalCoords = getDecimalCoords(latitude, latitude_ref, longitude, longitude_ref); decimalCoords = getDecimalCoords(latitude, latitudeRef, longitude, longitudeRef);
return decimalCoords; return decimalCoords;
} else { } else {
return null; return null;

View file

@ -51,11 +51,17 @@ public class MultipleShareActivity extends AuthenticatedActivity
MultipleUploadListFragment.OnMultipleUploadInitiatedHandler, MultipleUploadListFragment.OnMultipleUploadInitiatedHandler,
OnCategoriesSaveHandler { OnCategoriesSaveHandler {
@Inject MediaWikiApi mwApi; @Inject
@Inject SessionManager sessionManager; MediaWikiApi mwApi;
@Inject UploadController uploadController; @Inject
@Inject ModifierSequenceDao modifierSequenceDao; SessionManager sessionManager;
@Inject @Named("default_preferences") SharedPreferences prefs; @Inject
UploadController uploadController;
@Inject
ModifierSequenceDao modifierSequenceDao;
@Inject
@Named("default_preferences")
SharedPreferences prefs;
private ArrayList<Contribution> photosList = null; private ArrayList<Contribution> photosList = null;
@ -240,7 +246,7 @@ public class MultipleShareActivity extends AuthenticatedActivity
mwApi.setAuthCookie(authCookie); mwApi.setAuthCookie(authCookie);
Intent intent = getIntent(); Intent intent = getIntent();
if (intent.getAction().equals(Intent.ACTION_SEND_MULTIPLE)) { if (Intent.ACTION_SEND_MULTIPLE.equals(intent.getAction())) {
if (photosList == null) { if (photosList == null) {
photosList = new ArrayList<>(); photosList = new ArrayList<>();
ArrayList<Uri> urisList = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM); ArrayList<Uri> urisList = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
@ -278,7 +284,7 @@ public class MultipleShareActivity extends AuthenticatedActivity
@Override @Override
public void onBackStackChanged() { public void onBackStackChanged() {
getSupportActionBar().setDisplayHomeAsUpEnabled(mediaDetails != null && mediaDetails.isVisible()) ; getSupportActionBar().setDisplayHomeAsUpEnabled(mediaDetails != null && mediaDetails.isVisible());
} }
} }

View file

@ -16,6 +16,7 @@ import android.support.annotation.RequiresApi;
import android.support.design.widget.Snackbar; import android.support.design.widget.Snackbar;
import android.support.graphics.drawable.VectorDrawableCompat; import android.support.graphics.drawable.VectorDrawableCompat;
import android.support.v4.app.ActivityCompat; import android.support.v4.app.ActivityCompat;
import android.support.v4.app.FragmentManager;
import android.support.v4.content.ContextCompat; import android.support.v4.content.ContextCompat;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
@ -61,10 +62,10 @@ import static fr.free.nrw.commons.upload.ExistingFileAsync.Result.NO_DUPLICATE;
* Activity for the title/desc screen after image is selected. Also starts processing image * Activity for the title/desc screen after image is selected. Also starts processing image
* GPS coordinates or user location (if enabled in Settings) for category suggestions. * GPS coordinates or user location (if enabled in Settings) for category suggestions.
*/ */
public class ShareActivity public class ShareActivity
extends AuthenticatedActivity extends AuthenticatedActivity
implements SingleUploadFragment.OnUploadActionInitiated, implements SingleUploadFragment.OnUploadActionInitiated,
OnCategoriesSaveHandler { OnCategoriesSaveHandler,SimilarImageDialogFragment.onResponse {
private static final int REQUEST_PERM_ON_CREATE_STORAGE = 1; private static final int REQUEST_PERM_ON_CREATE_STORAGE = 1;
private static final int REQUEST_PERM_ON_CREATE_LOCATION = 2; private static final int REQUEST_PERM_ON_CREATE_LOCATION = 2;
@ -72,12 +73,19 @@ public class ShareActivity
private static final int REQUEST_PERM_ON_SUBMIT_STORAGE = 4; private static final int REQUEST_PERM_ON_SUBMIT_STORAGE = 4;
private CategorizationFragment categorizationFragment; private CategorizationFragment categorizationFragment;
@Inject MediaWikiApi mwApi; @Inject
@Inject CacheController cacheController; MediaWikiApi mwApi;
@Inject SessionManager sessionManager; @Inject
@Inject UploadController uploadController; CacheController cacheController;
@Inject ModifierSequenceDao modifierSequenceDao; @Inject
@Inject @Named("default_preferences") SharedPreferences prefs; SessionManager sessionManager;
@Inject
UploadController uploadController;
@Inject
ModifierSequenceDao modifierSequenceDao;
@Inject
@Named("default_preferences")
SharedPreferences prefs;
private String source; private String source;
private String mimeType; private String mimeType;
@ -89,6 +97,7 @@ public class ShareActivity
private boolean cacheFound; private boolean cacheFound;
private GPSExtractor imageObj; private GPSExtractor imageObj;
private GPSExtractor tempImageObj;
private String decimalCoords; private String decimalCoords;
private boolean useNewPermissions = false; private boolean useNewPermissions = false;
@ -99,7 +108,7 @@ public class ShareActivity
private String description; private String description;
private Snackbar snackbar; private Snackbar snackbar;
private boolean duplicateCheckPassed = false; private boolean duplicateCheckPassed = false;
private boolean haveCheckedForOtherImages = false;
/** /**
* Called when user taps the submit button. * Called when user taps the submit button.
*/ */
@ -216,7 +225,7 @@ public class ShareActivity
//Receive intent from ContributionController.java when user selects picture to upload //Receive intent from ContributionController.java when user selects picture to upload
Intent intent = getIntent(); Intent intent = getIntent();
if (intent.getAction().equals(Intent.ACTION_SEND)) { if (Intent.ACTION_SEND.equals(intent.getAction())) {
mediaUri = intent.getParcelableExtra(Intent.EXTRA_STREAM); mediaUri = intent.getParcelableExtra(Intent.EXTRA_STREAM);
if (intent.hasExtra(UploadService.EXTRA_SOURCE)) { if (intent.hasExtra(UploadService.EXTRA_SOURCE)) {
source = intent.getStringExtra(UploadService.EXTRA_SOURCE); source = intent.getStringExtra(UploadService.EXTRA_SOURCE);
@ -452,13 +461,93 @@ public class ShareActivity
if (imageObj != null) { if (imageObj != null) {
// Gets image coords from exif data or user location // Gets image coords from exif data or user location
decimalCoords = imageObj.getCoords(gpsEnabled); decimalCoords = imageObj.getCoords(gpsEnabled);
useImageCoords(); if(decimalCoords==null || !imageObj.imageCoordsExists){
// Check if the location is from GPS or EXIF
// Find other photos taken around the same time which has gps coordinates
Timber.d("EXIF:false");
Timber.d("EXIF call"+(imageObj==tempImageObj));
if(!haveCheckedForOtherImages)
findOtherImages(gpsEnabled);// Do not do repeat the process
}
else {
// As the selected image has GPS data in EXIF go ahead with the same.
useImageCoords();
}
} }
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
Timber.w("File not found: " + mediaUri, e); Timber.w("File not found: " + mediaUri, e);
} }
} }
private void findOtherImages(boolean gpsEnabled) {
Timber.d("filePath"+getPathOfMediaOrCopy());
String filePath = getPathOfMediaOrCopy();
long timeOfCreation = new File(filePath).lastModified();//Time when the original image was created
File folder = new File(filePath.substring(0,filePath.lastIndexOf('/')));
File[] files = folder.listFiles();
Timber.d("folderTime Number:"+files.length);
for(File file : files){
if(file.lastModified()-timeOfCreation<=(120*1000) && file.lastModified()-timeOfCreation>=-(120*1000)){
//Make sure the photos were taken within 20seconds
Timber.d("fild date:"+file.lastModified()+ " time of creation"+timeOfCreation);
tempImageObj = null;//Temporary GPSExtractor to extract coords from these photos
ParcelFileDescriptor descriptor
= null;
try {
descriptor = getContentResolver().openFileDescriptor(Uri.parse(file.getAbsolutePath()), "r");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
if (descriptor != null) {
tempImageObj = new GPSExtractor(descriptor.getFileDescriptor(),this, prefs);
}
} else {
if (filePath != null) {
tempImageObj = new GPSExtractor(file.getAbsolutePath(), this, prefs);
}
}
if(tempImageObj!=null){
Timber.d("not null fild EXIF"+tempImageObj.imageCoordsExists +" coords"+tempImageObj.getCoords(gpsEnabled));
if(tempImageObj.getCoords(gpsEnabled)!=null && tempImageObj.imageCoordsExists){
// Current image has gps coordinates and it's not current gps locaiton
Timber.d("This fild has image coords:"+ file.getAbsolutePath());
// Create a dialog fragment for the suggestion
FragmentManager fragmentManager = getSupportFragmentManager();
SimilarImageDialogFragment newFragment = new SimilarImageDialogFragment();
Bundle args = new Bundle();
args.putString("originalImagePath",filePath);
args.putString("possibleImagePath",file.getAbsolutePath());
newFragment.setArguments(args);
newFragment.show(fragmentManager, "dialog");
break;
}
}
}
}
haveCheckedForOtherImages = true; //Finished checking for other images
return;
}
@Override
public void onPostiveResponse() {
imageObj = tempImageObj;
decimalCoords = imageObj.getCoords(false);// Not necessary to use gps as image already ha EXIF data
Timber.d("EXIF from tempImageObj");
useImageCoords();
}
@Override
public void onNegativeResponse() {
Timber.d("EXIF from imageObj");
useImageCoords();
}
/** /**
* Initiates retrieval of image coordinates or user coordinates, and caching of coordinates. * Initiates retrieval of image coordinates or user coordinates, and caching of coordinates.
* Then initiates the calls to MediaWiki API through an instance of MwVolleyApi. * Then initiates the calls to MediaWiki API through an instance of MwVolleyApi.
@ -466,6 +555,7 @@ public class ShareActivity
public void useImageCoords() { public void useImageCoords() {
if (decimalCoords != null) { if (decimalCoords != null) {
Timber.d("Decimal coords of image: %s", decimalCoords); Timber.d("Decimal coords of image: %s", decimalCoords);
Timber.d("is EXIF data present:"+imageObj.imageCoordsExists+" from findOther image:"+(imageObj==tempImageObj));
// Only set cache for this point if image has coords // Only set cache for this point if image has coords
if (imageObj.imageCoordsExists) { if (imageObj.imageCoordsExists) {
@ -489,7 +579,10 @@ public class ShareActivity
Timber.d("Cache found, setting categoryList in MwVolleyApi to %s", displayCatList); Timber.d("Cache found, setting categoryList in MwVolleyApi to %s", displayCatList);
MwVolleyApi.setGpsCat(displayCatList); MwVolleyApi.setGpsCat(displayCatList);
} }
}else{
Timber.d("EXIF: no coords");
} }
} }
@Override @Override

View file

@ -0,0 +1,112 @@
package fr.free.nrw.commons.upload;
import android.app.Dialog;
import android.content.DialogInterface;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.graphics.drawable.VectorDrawableCompat;
import android.support.v4.app.DialogFragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.Button;
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
import com.facebook.drawee.view.SimpleDraweeView;
import com.facebook.imagepipeline.listener.RequestListener;
import com.facebook.imagepipeline.listener.RequestLoggingListener;
import java.io.File;
import java.util.HashSet;
import java.util.Set;
import fr.free.nrw.commons.R;
/**
* Created by harisanker on 14/2/18.
*/
public class SimilarImageDialogFragment extends DialogFragment {
SimpleDraweeView originalImage;
SimpleDraweeView possibleImage;
Button positiveButton;
Button negativeButton;
onResponse mOnResponse;//Implemented interface from shareActivity
Boolean gotResponse = false;
public SimilarImageDialogFragment() {
}
public interface onResponse{
public void onPostiveResponse();
public void onNegativeResponse();
}
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_similar_image_dialog, container, false);
Set<RequestListener> requestListeners = new HashSet<>();
requestListeners.add(new RequestLoggingListener());
originalImage =(SimpleDraweeView) view.findViewById(R.id.orginalImage);
possibleImage =(SimpleDraweeView) view.findViewById(R.id.possibleImage);
positiveButton = (Button) view.findViewById(R.id.postive_button);
negativeButton = (Button) view.findViewById(R.id.negative_button);
originalImage.setHierarchy(GenericDraweeHierarchyBuilder
.newInstance(getResources())
.setPlaceholderImage(VectorDrawableCompat.create(getResources(),
R.drawable.ic_image_black_24dp,getContext().getTheme()))
.setFailureImage(VectorDrawableCompat.create(getResources(),
R.drawable.ic_error_outline_black_24dp, getContext().getTheme()))
.build());
possibleImage.setHierarchy(GenericDraweeHierarchyBuilder
.newInstance(getResources())
.setPlaceholderImage(VectorDrawableCompat.create(getResources(),
R.drawable.ic_image_black_24dp,getContext().getTheme()))
.setFailureImage(VectorDrawableCompat.create(getResources(),
R.drawable.ic_error_outline_black_24dp, getContext().getTheme()))
.build());
originalImage.setImageURI(Uri.fromFile(new File(getArguments().getString("originalImagePath"))));
possibleImage.setImageURI(Uri.fromFile(new File(getArguments().getString("possibleImagePath"))));
negativeButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mOnResponse.onNegativeResponse();
gotResponse = true;
dismiss();
}
});
positiveButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mOnResponse.onPostiveResponse();
gotResponse = true;
dismiss();
}
});
return view;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mOnResponse = (onResponse) getActivity();//Interface Implementation
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
Dialog dialog = super.onCreateDialog(savedInstanceState);
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
return dialog;
}
@Override
public void onDismiss(DialogInterface dialog) {
// I user dismisses dialog by pressing outside the dialog.
if(!gotResponse)
mOnResponse.onNegativeResponse();
super.onDismiss(dialog);
}
}

View file

@ -6,6 +6,7 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.ServiceConnection; import android.content.ServiceConnection;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.res.AssetFileDescriptor;
import android.database.Cursor; import android.database.Cursor;
import android.net.Uri; import android.net.Uri;
import android.os.AsyncTask; import android.os.AsyncTask;
@ -49,7 +50,7 @@ public class UploadController {
private ServiceConnection uploadServiceConnection = new ServiceConnection() { private ServiceConnection uploadServiceConnection = new ServiceConnection() {
@Override @Override
public void onServiceConnected(ComponentName componentName, IBinder binder) { public void onServiceConnected(ComponentName componentName, IBinder binder) {
uploadService = (UploadService) ((HandlerService.HandlerServiceLocalBinder)binder).getService(); uploadService = (UploadService) ((HandlerService.HandlerServiceLocalBinder) binder).getService();
isUploadServiceConnected = true; isUploadServiceConnected = true;
} }
@ -81,13 +82,14 @@ public class UploadController {
/** /**
* Starts a new upload task. * Starts a new upload task.
* @param title the title of the contribution *
* @param mediaUri the media URI of the contribution * @param title the title of the contribution
* @param description the description of the contribution * @param mediaUri the media URI of the contribution
* @param mimeType the MIME type of the contribution * @param description the description of the contribution
* @param source the source of the contribution * @param mimeType the MIME type of the contribution
* @param source the source of the contribution
* @param decimalCoords the coordinates in decimal. (e.g. "37.51136|-77.602615") * @param decimalCoords the coordinates in decimal. (e.g. "37.51136|-77.602615")
* @param onComplete the progress tracker * @param onComplete the progress tracker
*/ */
public void startUpload(String title, Uri mediaUri, String description, String mimeType, String source, String decimalCoords, ContributionUploadProgress onComplete) { public void startUpload(String title, Uri mediaUri, String description, String mimeType, String source, String decimalCoords, ContributionUploadProgress onComplete) {
Contribution contribution; Contribution contribution;
@ -106,8 +108,9 @@ public class UploadController {
/** /**
* Starts a new upload task. * Starts a new upload task.
*
* @param contribution the contribution object * @param contribution the contribution object
* @param onComplete the progress tracker * @param onComplete the progress tracker
*/ */
public void startUpload(final Contribution contribution, final ContributionUploadProgress onComplete) { public void startUpload(final Contribution contribution, final ContributionUploadProgress onComplete) {
//Set creator, desc, and license //Set creator, desc, and license
@ -134,15 +137,17 @@ public class UploadController {
ContentResolver contentResolver = context.getContentResolver(); ContentResolver contentResolver = context.getContentResolver();
try { try {
if (contribution.getDataLength() <= 0) { if (contribution.getDataLength() <= 0) {
length = contentResolver AssetFileDescriptor assetFileDescriptor = contentResolver
.openAssetFileDescriptor(contribution.getLocalUri(), "r") .openAssetFileDescriptor(contribution.getLocalUri(), "r");
.getLength(); if (assetFileDescriptor != null) {
if (length == -1) { length = assetFileDescriptor.getLength();
// Let us find out the long way! if (length == -1) {
length = countBytes(contentResolver // Let us find out the long way!
.openInputStream(contribution.getLocalUri())); length = countBytes(contentResolver
.openInputStream(contribution.getLocalUri()));
}
contribution.setDataLength(length);
} }
contribution.setDataLength(length);
} }
} catch (IOException e) { } catch (IOException e) {
Timber.e(e, "IO Exception: "); Timber.e(e, "IO Exception: ");
@ -152,7 +157,7 @@ public class UploadController {
Timber.e(e, "Security Exception: "); Timber.e(e, "Security Exception: ");
} }
String mimeType = (String)contribution.getTag("mimeType"); String mimeType = (String) contribution.getTag("mimeType");
Boolean imagePrefix = false; Boolean imagePrefix = false;
if (mimeType == null || TextUtils.isEmpty(mimeType) || mimeType.endsWith("*")) { if (mimeType == null || TextUtils.isEmpty(mimeType) || mimeType.endsWith("*")) {
@ -199,6 +204,7 @@ public class UploadController {
/** /**
* Counts the number of bytes in {@code stream}. * Counts the number of bytes in {@code stream}.
*
* @param stream the stream * @param stream the stream
* @return the number of bytes in {@code stream} * @return the number of bytes in {@code stream}
* @throws IOException if an I/O error occurs * @throws IOException if an I/O error occurs

View file

@ -161,7 +161,7 @@ public class UploadService extends HandlerService<Contribution> {
@Override @Override
public int onStartCommand(Intent intent, int flags, int startId) { public int onStartCommand(Intent intent, int flags, int startId) {
if (intent.getAction().equals(ACTION_START_SERVICE) && freshStart) { if (ACTION_START_SERVICE.equals(intent.getAction()) && freshStart) {
ContentValues failedValues = new ContentValues(); ContentValues failedValues = new ContentValues();
failedValues.put(ContributionDao.Table.COLUMN_STATE, Contribution.STATE_FAILED); failedValues.put(ContributionDao.Table.COLUMN_STATE, Contribution.STATE_FAILED);

View file

@ -15,6 +15,8 @@ public class ExecutorUtils {
} }
}; };
public static Executor uiExecutor() { return uiExecutor; } public static Executor uiExecutor() {
return uiExecutor;
}
} }

View file

@ -11,7 +11,6 @@ import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.OutputStreamWriter; import java.io.OutputStreamWriter;
import fr.free.nrw.commons.CommonsApplication;
import timber.log.Timber; import timber.log.Timber;
public class FileUtils { public class FileUtils {
@ -32,7 +31,7 @@ public class FileUtils {
reader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8")); reader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
String line; String line;
while ((line = reader.readLine()) != null) { while ((line = reader.readLine()) != null) {
buffer.append(line + "\n"); buffer.append(line).append("\n");
} }
} finally { } finally {
if (reader != null) { if (reader != null) {

View file

@ -7,11 +7,6 @@ import android.widget.Toast;
public class ViewUtil { public class ViewUtil {
public static void showLongToast(final Context context, @StringRes final int stringResId) { public static void showLongToast(final Context context, @StringRes final int stringResId) {
ExecutorUtils.uiExecutor().execute(new Runnable() { ExecutorUtils.uiExecutor().execute(() -> Toast.makeText(context, context.getString(stringResId), Toast.LENGTH_LONG).show());
@Override
public void run() {
Toast.makeText(context, context.getString(stringResId), Toast.LENGTH_LONG).show();
}
});
} }
} }

View file

@ -15,9 +15,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" /> android:layout_height="wrap_content" />
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout android:layout_width="match_parent"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:id="@+id/contributionsFragmentContainer" android:id="@+id/contributionsFragmentContainer"
android:orientation="horizontal" android:orientation="horizontal"

View file

@ -15,8 +15,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" /> android:layout_height="wrap_content" />
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout android:id="@+id/uploadsFragmentContainer"
android:id="@+id/uploadsFragmentContainer"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_below="@id/toolbar" android:layout_below="@id/toolbar"

View file

@ -36,7 +36,7 @@
android:indeterminateOnly="true" android:indeterminateOnly="true"
android:layout_marginRight="@dimen/tiny_gap" android:layout_marginRight="@dimen/tiny_gap"
android:layout_marginEnd="@dimen/tiny_gap" android:layout_marginEnd="@dimen/tiny_gap"
android:layout_gravity="center_vertical|right|end" android:layout_gravity="center_vertical|end"
style="?android:progressBarStyleSmall" style="?android:progressBarStyleSmall"
android:visibility="gone" android:visibility="gone"
/> />

View file

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
xmlns:app="http://schemas.android.com/apk/res-auto">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/orginalImage"
android:layout_width="160dp"
android:layout_height="240dp"
android:layout_margin="3dp"
app:actualImageScaleType="centerCrop" />
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/possibleImage"
android:layout_width="160dp"
android:layout_height="240dp"
android:layout_margin="3dp"
app:actualImageScaleType="centerCrop" />
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="6dp"
android:layout_gravity="center"
android:textAlignment="center"
android:text="Did you shoot these two pictures at the same place? Do you want to use the latitude/longitude of the picture on the right?"/>
<LinearLayout
android:layout_width="match_parent"
android:orientation="horizontal"
android:layout_height="match_parent">
<View
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1"/>
<Button
style="@style/Widget.AppCompat.Button.Borderless"
android:layout_width="wrap_content"
android:layout_gravity="right"
android:id="@+id/negative_button"
android:layout_marginRight="3dp"
android:layout_height="wrap_content"
android:text="No" />
<Button
style="@style/Widget.AppCompat.Button.Borderless"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/postive_button"
android:layout_gravity="right"
android:layout_marginLeft="3dp"
android:text="Yes" />
</LinearLayout>
</LinearLayout>

View file

@ -121,7 +121,7 @@
<string name="tutorial_3_text">দয়া করে আপলোড করবেন না:</string> <string name="tutorial_3_text">দয়া করে আপলোড করবেন না:</string>
<string name="tutorial_3_subtext">u2022 আপনার বন্ধুর সেলফি বা ছবি \nu2022 ইন্টারনেট থেকে ডাউনলোড করা ছবি \nu2022 মালিকানাযুক্ত অ্যাপসমূহের স্ক্রীনশট</string> <string name="tutorial_3_subtext">u2022 আপনার বন্ধুর সেলফি বা ছবি \nu2022 ইন্টারনেট থেকে ডাউনলোড করা ছবি \nu2022 মালিকানাযুক্ত অ্যাপসমূহের স্ক্রীনশট</string>
<string name="tutorial_4_text">আপলোডের উদাহরণ:</string> <string name="tutorial_4_text">আপলোডের উদাহরণ:</string>
<string name="tutorial_4_subtext" fuzzy="true">u2022 শিরোনাম: সিডনি অপেরা হাউস \nu2022 বিবরণ: উপসাগর থেকে দেখা সিডনি অপেরা হাউস\nu2022 বিষয়শ্রেণী: সিডনি অপেরা হাউস, পশ্চিম দিক থেকে সিডনি অপেরা হাউস, সিডনি অপেরা হাউসের দূরবর্তী দৃশ্য</string> <string name="tutorial_4_subtext">- শিরোনাম: সিডনি অপেরা হাউস \n- বিবরণ: উপসাগর থেকে দেখা সিডনি অপেরা হাউস\n- বিষয়শ্রেণী: সিডনি অপেরা হাউস, পশ্চিম দিক থেকে সিডনি অপেরা হাউস, সিডনি অপেরা হাউসের দূরবর্তী দৃশ্য</string>
<string name="welcome_wikipedia_text">আপনার ছবি দিয়ে অবদান রাখুন। উইকিপিডিয়ার নিবন্ধগুলিকে নতুন রূপ দিতে সাহায্য করুন!</string> <string name="welcome_wikipedia_text">আপনার ছবি দিয়ে অবদান রাখুন। উইকিপিডিয়ার নিবন্ধগুলিকে নতুন রূপ দিতে সাহায্য করুন!</string>
<string name="welcome_wikipedia_subtext">উইকিপিডিয়াতে চিত্র উইকিমিডিয়া কমন্স থেকে এসেছে।</string> <string name="welcome_wikipedia_subtext">উইকিপিডিয়াতে চিত্র উইকিমিডিয়া কমন্স থেকে এসেছে।</string>
<string name="welcome_copyright_text">আপনার ছবি সারা বিশ্বের শিক্ষিত মানুষকে সাহায্য করবে।</string> <string name="welcome_copyright_text">আপনার ছবি সারা বিশ্বের শিক্ষিত মানুষকে সাহায্য করবে।</string>
@ -191,6 +191,7 @@
<string name="navigation_item_feedback">প্রতিক্রিয়া</string> <string name="navigation_item_feedback">প্রতিক্রিয়া</string>
<string name="navigation_item_logout">প্রস্থান</string> <string name="navigation_item_logout">প্রস্থান</string>
<string name="navigation_item_info">ভূমিকা</string> <string name="navigation_item_info">ভূমিকা</string>
<string name="navigation_item_notification">বিজ্ঞপ্তি</string>
<string name="nearby_needs_permissions">অবস্থানের অনুমতি ছাড়া কাছাকাছি জায়গাগুলি প্রদর্শন করা যাবে না</string> <string name="nearby_needs_permissions">অবস্থানের অনুমতি ছাড়া কাছাকাছি জায়গাগুলি প্রদর্শন করা যাবে না</string>
<string name="no_description_found">কোন বিবরণ পাওয়া যায়নি</string> <string name="no_description_found">কোন বিবরণ পাওয়া যায়নি</string>
<string name="nearby_info_menu_commons_article">কমন্সে ফাইলের পাতা</string> <string name="nearby_info_menu_commons_article">কমন্সে ফাইলের পাতা</string>

View file

@ -55,7 +55,7 @@
<string name="enable_gps">Spustit GPS</string> <string name="enable_gps">Spustit GPS</string>
<string name="contributions_subtitle_zero">Žádné nahrané soubory</string> <string name="contributions_subtitle_zero">Žádné nahrané soubory</string>
<plurals name="contributions_subtitle"> <plurals name="contributions_subtitle">
<item quantity="zero">Žádné soubory</item> <item quantity="zero">\@string/contributions_subtitle_zero</item>
<item quantity="one">1 soubor</item> <item quantity="one">1 soubor</item>
<item quantity="other">%1$d souborů</item> <item quantity="other">%1$d souborů</item>
</plurals> </plurals>

View file

@ -198,7 +198,7 @@
<string name="nearby_info_menu_wikidata_article">Elemento de Wikidata</string> <string name="nearby_info_menu_wikidata_article">Elemento de Wikidata</string>
<string name="error_while_cache">Error al almacenar imágenes en la antememoria</string> <string name="error_while_cache">Error al almacenar imágenes en la antememoria</string>
<string name="title_info">Un título único descriptivo para el archivo, que servirá como un nombre de archivo. Puede usar un lenguaje claro con espacios. No incluya la extensión del archivo.</string> <string name="title_info">Un título único descriptivo para el archivo, que servirá como un nombre de archivo. Puede usar un lenguaje claro con espacios. No incluya la extensión del archivo.</string>
<string name="description_info">Por favor, describa el elemento multimedia tanto como sea posible: ¿dónde fue tomado?, ¿qué muestra?, ¿cuál es el contexto? Por favor, describa los objetos o personas. Ofrezca la información que no puede ser inferida tan facilmente, por ejemplo el momento del día si es un paisaje. Si el medio muestra algo inusual, explique qué lo hace insual.</string> <string name="description_info">Por favor, describa el elemento multimedia tanto como sea posible: ¿dónde fue tomado?, ¿qué muestra?, ¿cuál es el contexto? Por favor, describa los objetos o personas. Ofrezca la información que no puede ser inferida tan fácilmente, por ejemplo el momento del día si es un paisaje. Si el medio muestra algo inusual, explique qué lo hace insual.</string>
<string name="give_permission">Otorgar permiso</string> <string name="give_permission">Otorgar permiso</string>
<string name="use_external_storage">Utilizar almacenamiento externo</string> <string name="use_external_storage">Utilizar almacenamiento externo</string>
<string name="use_external_storage_summary">Guardar en el dispositivo imágenes capturadas con la cámara de la aplicación</string> <string name="use_external_storage_summary">Guardar en el dispositivo imágenes capturadas con la cámara de la aplicación</string>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="crash_dialog_text">Вагь. Бир зат чы терс гетди!</string>
<string name="crash_dialog_comment_prompt">Этгенигизни язып бизге электрон почгъа йибери. О масъаланы чечме кёмек этежек!</string>
<string name="crash_dialog_ok_toast">Савбол!</string>
</resources>

View file

@ -90,4 +90,5 @@
<string name="detail_description_empty">वर्णन छैन</string> <string name="detail_description_empty">वर्णन छैन</string>
<string name="detail_license_empty">अज्ञान अनुमतिपत्र</string> <string name="detail_license_empty">अज्ञान अनुमतिपत्र</string>
<string name="menu_refresh">ताजागर्ने</string> <string name="menu_refresh">ताजागर्ने</string>
<string name="toggle_view_button">टगल दृश्य</string>
</resources> </resources>

View file

@ -52,7 +52,10 @@
<string name="enable_gps">เปิดใช้งาน GPS</string> <string name="enable_gps">เปิดใช้งาน GPS</string>
<string name="contributions_subtitle_zero">ยังไม่มีการอัปโหลด</string> <string name="contributions_subtitle_zero">ยังไม่มีการอัปโหลด</string>
<string name="contributions_subtitle">การอัปโหลด %1$d รายการ</string> <string name="contributions_subtitle">การอัปโหลด %1$d รายการ</string>
<string name="starting_multiple_uploads" fuzzy="true">กำลังเริ่มการอัปโหลด %1$d รายการ</string> <plurals name="starting_multiple_uploads">
<item quantity="one">กำลังเริ่มอัปโหลด %1$d รายการ</item>
<item quantity="other">กำลังเริ่มอัปโหลด %1$d รายการ</item>
</plurals>
<plurals name="multiple_uploads_title"> <plurals name="multiple_uploads_title">
<item quantity="one">การอัปโหลด %1$d รายการ</item> <item quantity="one">การอัปโหลด %1$d รายการ</item>
<item quantity="other">การอัปโหลด %1$d รายการ</item> <item quantity="other">การอัปโหลด %1$d รายการ</item>

View file

@ -7,7 +7,7 @@ buildscript {
google() google()
} }
dependencies { dependencies {
classpath "com.android.tools.build:gradle:${project.gradleVersion}" classpath 'com.android.tools.build:gradle:3.0.1'
classpath 'com.dicedmelon.gradle:jacoco-android:0.1.1' classpath 'com.dicedmelon.gradle:jacoco-android:0.1.1'
classpath 'com.getkeepsafe.dexcount:dexcount-gradle-plugin:0.7.1' classpath 'com.getkeepsafe.dexcount:dexcount-gradle-plugin:0.7.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"

Binary file not shown.

After

Width:  |  Height:  |  Size: 873 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 721 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 539 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 768 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 300 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 239 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 988 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 706 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 420 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 717 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 344 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 319 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 KiB