Merge branch 'master' into dependency-injection

This commit is contained in:
Paul Hawke 2017-10-21 12:07:12 -05:00
commit f134d23ecb
111 changed files with 1230 additions and 364 deletions

View file

@ -87,7 +87,7 @@ public class CommonsApplication extends DaggerApplication {
}
@Override
protected AndroidInjector<? extends DaggerApplication> applicationInjector() {
protected AndroidInjector<? extends DaggerApplication> applicationInjector() {
return injector();
}
@ -120,8 +120,7 @@ public class CommonsApplication extends DaggerApplication {
//TODO: fix preference manager
defaultPrefs.edit().clear().commit();
applicationPrefs.edit().clear().commit();
applicationPrefs.edit().putBoolean("firstrun", false).apply();
otherPrefs.edit().clear().commit();
applicationPrefs.edit().putBoolean("firstrun", false).apply();otherPrefs.edit().clear().commit();
updateAllDatabases();
logoutListener.onLogoutComplete();

View file

@ -3,10 +3,10 @@ package fr.free.nrw.commons;
import android.support.annotation.Nullable;
public class License {
String key;
String template;
String url;
String name;
private String key;
private String template;
private String url;
private String name;
public License(String key, String template, String url, String name) {
if (key == null) {

View file

@ -1,5 +1,8 @@
package fr.free.nrw.commons;
import android.content.Context;
import android.preference.PreferenceManager;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.codec.digest.DigestUtils;
@ -9,9 +12,9 @@ import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import fr.free.nrw.commons.auth.LoginActivity;
import fr.free.nrw.commons.settings.Prefs;
public class Utils {
/**
@ -76,10 +79,14 @@ public class Utils {
extension = "jpg";
}
title = jpegPattern.matcher(title).replaceFirst(".jpg");
if (extension != null && !title.toLowerCase(Locale.getDefault()).endsWith("." + extension.toLowerCase(Locale.ENGLISH))) {
if (extension != null && !title.toLowerCase(Locale.getDefault())
.endsWith("." + extension.toLowerCase(Locale.ENGLISH))) {
title += "." + extension;
}
return title;
}
public static boolean isDarkTheme(Context context) {
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean("theme", false);
}
}

View file

@ -21,7 +21,7 @@ public abstract class AuthenticatedActivity extends NavigationBaseActivity {
@Inject SessionManager sessionManager;
private String authCookie;
private void getAuthCookie(Account account, AccountManager accountManager) {
Single.fromCallable(() -> accountManager.blockingGetAuthToken(account, "", false))

View file

@ -5,15 +5,21 @@ import android.app.ProgressDialog;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.support.annotation.ColorRes;
import android.support.annotation.NonNull;
import android.support.annotation.StringRes;
import android.support.v4.app.NavUtils;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatDelegate;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import javax.inject.Inject;
import javax.inject.Named;
@ -24,6 +30,7 @@ import dagger.android.AndroidInjection;
import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.PageTitle;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.WelcomeActivity;
import fr.free.nrw.commons.contributions.ContributionsActivity;
import fr.free.nrw.commons.mwapi.MediaWikiApi;
@ -32,7 +39,6 @@ import timber.log.Timber;
import static android.view.KeyEvent.KEYCODE_ENTER;
import static android.view.inputmethod.EditorInfo.IME_ACTION_DONE;
public class LoginActivity extends AccountAuthenticatorActivity {
public static final String PARAM_USERNAME = "fr.free.nrw.commons.login.username";
@ -40,7 +46,7 @@ public class LoginActivity extends AccountAuthenticatorActivity {
@Inject MediaWikiApi mwApi;
@Inject AccountUtil accountUtil;
@Inject SessionManager sessionManager;
@Inject @Named("application_preferences") SharedPreferences prefs = null;
@Inject @Named("application_preferences") SharedPreferences prefs;
@Inject @Named("default_preferences") SharedPreferences defaultPrefs;
@BindView(R.id.loginButton) Button loginButton;
@ -48,16 +54,22 @@ public class LoginActivity extends AccountAuthenticatorActivity {
@BindView(R.id.loginUsername) EditText usernameEdit;
@BindView(R.id.loginPassword) EditText passwordEdit;
@BindView(R.id.loginTwoFactor) EditText twoFactorEdit;
@BindView(R.id.error_message_container) ViewGroup errorMessageContainer;
@BindView(R.id.error_message) TextView errorMessage;
ProgressDialog progressDialog;
private AppCompatDelegate delegate;
private LoginTextWatcher textWatcher = new LoginTextWatcher();
@Override
public void onCreate(Bundle savedInstanceState) {
setTheme(Utils.isDarkTheme(this) ? R.style.DarkAppTheme : R.style.LightAppTheme);
getDelegate().installViewFactory();
getDelegate().onCreate(savedInstanceState);
AndroidInjection.inject(this);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
ButterKnife.bind(this);
usernameEdit.addTextChangedListener(textWatcher);
@ -65,45 +77,17 @@ public class LoginActivity extends AccountAuthenticatorActivity {
twoFactorEdit.addTextChangedListener(textWatcher);
passwordEdit.setOnEditorActionListener(newLoginInputActionListener());
loginButton.setOnClickListener(this::performLogin);
signupButton.setOnClickListener(this::signUp);
loginButton.setOnClickListener(view -> performLogin());
signupButton.setOnClickListener(view -> signUp());
}
private class LoginTextWatcher implements TextWatcher {
@Override
public void beforeTextChanged(CharSequence charSequence, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence charSequence, int start, int count, int after) {
}
@Override
public void afterTextChanged(Editable editable) {
if (usernameEdit.getText().length() != 0 && passwordEdit.getText().length() != 0 &&
(BuildConfig.DEBUG || twoFactorEdit.getText().length() != 0 || twoFactorEdit.getVisibility() != View.VISIBLE)) {
loginButton.setEnabled(true);
} else {
loginButton.setEnabled(false);
}
}
}
private TextView.OnEditorActionListener newLoginInputActionListener() {
return (textView, actionId, keyEvent) -> {
if (loginButton.isEnabled()) {
if (actionId == IME_ACTION_DONE) {
performLogin(textView);
return true;
} else if ((keyEvent != null) && keyEvent.getKeyCode() == KEYCODE_ENTER) {
performLogin(textView);
return true;
}
}
return false;
};
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
getDelegate().onPostCreate(savedInstanceState);
}
@Override
protected void onResume() {
super.onResume();
if (prefs.getBoolean("firstrun", true)) {
@ -128,15 +112,10 @@ public class LoginActivity extends AccountAuthenticatorActivity {
usernameEdit.removeTextChangedListener(textWatcher);
passwordEdit.removeTextChangedListener(textWatcher);
twoFactorEdit.removeTextChangedListener(textWatcher);
delegate.onDestroy();
super.onDestroy();
}
private void performLogin(View view) {
Timber.d("Login to start!");
LoginTask task = getLoginTask();
task.execute();
}
private LoginTask getLoginTask() {
return new LoginTask(
this,
@ -156,6 +135,29 @@ public class LoginActivity extends AccountAuthenticatorActivity {
return new PageTitle(username).getText();
}
@Override
protected void onStart() {
super.onStart();
delegate.onStart();
}
@Override
protected void onStop() {
super.onStop();
delegate.onStop();
}
@Override
protected void onPostResume() {
super.onPostResume();
getDelegate().onPostResume();
}
@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
getDelegate().setContentView(view, params);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
@ -166,36 +168,28 @@ public class LoginActivity extends AccountAuthenticatorActivity {
return super.onOptionsItemSelected(item);
}
/**
* Called when Sign Up button is clicked.
* @param view View
*/
public void signUp(View view) {
Intent intent = new Intent(this, SignupActivity.class);
startActivity(intent);
@Override
@NonNull
public MenuInflater getMenuInflater() {
return getDelegate().getMenuInflater();
}
public void askUserForTwoFactorAuth() {
if (BuildConfig.DEBUG) {
twoFactorEdit.setVisibility(View.VISIBLE);
showUserToastAndCancelDialog(R.string.login_failed_2fa_needed);
showMessageAndCancelDialog(R.string.login_failed_2fa_needed);
} else {
showUserToastAndCancelDialog(R.string.login_failed_2fa_not_supported);
showMessageAndCancelDialog(R.string.login_failed_2fa_not_supported);
}
}
public void showUserToastAndCancelDialog(int resId) {
showUserToast(resId);
public void showMessageAndCancelDialog(@StringRes int resId) {
showMessage(resId, R.color.secondaryDarkColor);
progressDialog.cancel();
}
private void showUserToast(int resId) {
Toast.makeText(this, resId, Toast.LENGTH_LONG).show();
}
public void showSuccessToastAndDismissDialog() {
Toast successToast = Toast.makeText(this, R.string.login_success, Toast.LENGTH_SHORT);
successToast.show();
public void showSuccessAndDismissDialog() {
showMessage(R.string.login_success, R.color.primaryDarkColor);
progressDialog.dismiss();
}
@ -209,4 +203,59 @@ public class LoginActivity extends AccountAuthenticatorActivity {
finish();
}
private void performLogin() {
Timber.d("Login to start!");
LoginTask task = getLoginTask();
task.execute();
}
private void signUp() {
Intent intent = new Intent(this, SignupActivity.class);
startActivity(intent);
}
private TextView.OnEditorActionListener newLoginInputActionListener() {
return (textView, actionId, keyEvent) -> {
if (loginButton.isEnabled()) {
if (actionId == IME_ACTION_DONE) {
performLogin();
return true;
} else if ((keyEvent != null) && keyEvent.getKeyCode() == KEYCODE_ENTER) {
performLogin();
return true;
}
}
return false;
};
}
private void showMessage(@StringRes int resId, @ColorRes int colorResId) {
errorMessage.setText(getString(resId));
errorMessage.setTextColor(ContextCompat.getColor(this, colorResId));
errorMessageContainer.setVisibility(View.VISIBLE);
}
private AppCompatDelegate getDelegate() {
if (delegate == null) {
delegate = AppCompatDelegate.create(this, null);
}
return delegate;
}
private class LoginTextWatcher implements TextWatcher {
@Override
public void beforeTextChanged(CharSequence charSequence, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence charSequence, int start, int count, int after) {
}
@Override
public void afterTextChanged(Editable editable) {
boolean enabled = usernameEdit.getText().length() != 0 && passwordEdit.getText().length() != 0 &&
(BuildConfig.DEBUG || twoFactorEdit.getText().length() != 0 || twoFactorEdit.getVisibility() != View.VISIBLE);
loginButton.setEnabled(enabled);
}
}
}

View file

@ -84,7 +84,7 @@ class LoginTask extends AsyncTask<String, String, String> {
}
private void handlePassResult() {
loginActivity.showSuccessToastAndDismissDialog();
loginActivity.showSuccessAndDismissDialog();
AccountAuthenticatorResponse response = null;
@ -111,27 +111,27 @@ class LoginTask extends AsyncTask<String, String, String> {
private void handleOtherResults(String result) {
if (result.equals("NetworkFailure")) {
// Matches NetworkFailure which is created by the doInBackground method
loginActivity.showUserToastAndCancelDialog(R.string.login_failed_network);
loginActivity.showMessageAndCancelDialog(R.string.login_failed_network);
} else if (result.toLowerCase().contains("nosuchuser".toLowerCase()) || result.toLowerCase().contains("noname".toLowerCase())) {
// Matches nosuchuser, nosuchusershort, noname
loginActivity.showUserToastAndCancelDialog(R.string.login_failed_username);
loginActivity.showMessageAndCancelDialog(R.string.login_failed_username);
loginActivity.emptySensitiveEditFields();
} else if (result.toLowerCase().contains("wrongpassword".toLowerCase())) {
// Matches wrongpassword, wrongpasswordempty
loginActivity.showUserToastAndCancelDialog(R.string.login_failed_password);
loginActivity.showMessageAndCancelDialog(R.string.login_failed_password);
loginActivity.emptySensitiveEditFields();
} else if (result.toLowerCase().contains("throttle".toLowerCase())) {
// Matches unknown throttle error codes
loginActivity.showUserToastAndCancelDialog(R.string.login_failed_throttled);
loginActivity.showMessageAndCancelDialog(R.string.login_failed_throttled);
} else if (result.toLowerCase().contains("userblocked".toLowerCase())) {
// Matches login-userblocked
loginActivity.showUserToastAndCancelDialog(R.string.login_failed_blocked);
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.showUserToastAndCancelDialog(R.string.login_failed_generic);
loginActivity.showMessageAndCancelDialog(R.string.login_failed_generic);
}
}
}

View file

@ -24,7 +24,8 @@ public class SignupActivity extends BaseActivity {
webView.setWebViewClient(new MyWebViewClient());
WebSettings webSettings = webView.getSettings();
//Needed to refresh Captcha. Might introduce XSS vulnerabilities, but we can trust Wikimedia's site... right?
/*Needed to refresh Captcha. Might introduce XSS vulnerabilities, but we can
trust Wikimedia's site... right?*/
webSettings.setJavaScriptEnabled(true);
webView.loadUrl(BuildConfig.SIGNUP_LANDING_URL);

View file

@ -32,7 +32,7 @@ public class CacheController {
public void cacheCategory() {
List<String> pointCatList = new ArrayList<>();
if (MwVolleyApi.GpsCatExists.getGpsCatExists()) {
pointCatList.addAll(MwVolleyApi.getGpsCat());
pointCatList.addAll(MwVolleyApi.getGpsCat());
Timber.d("Categories being cached: %s", pointCatList);
} else {
Timber.d("No categories found, so no categories cached");

View file

@ -23,6 +23,7 @@ import com.pedrogomez.renderers.RVRendererAdapter;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
@ -38,6 +39,7 @@ import fr.free.nrw.commons.R;
import fr.free.nrw.commons.data.Category;
import fr.free.nrw.commons.mwapi.MediaWikiApi;
import fr.free.nrw.commons.upload.MwVolleyApi;
import fr.free.nrw.commons.utils.StringSortingUtils;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
@ -112,7 +114,7 @@ public class CategorizationFragment extends DaggerFragment {
RxTextView.textChanges(categoriesFilter)
.takeUntil(RxView.detaches(categoriesFilter))
.debounce(300, TimeUnit.MILLISECONDS)
.debounce(500, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(filter -> updateCategoryList(filter.toString()));
return rootView;
@ -200,11 +202,12 @@ public class CategorizationFragment extends DaggerFragment {
.concatWith(
searchAll(filter)
.mergeWith(searchCategories(filter))
.concatWith( TextUtils.isEmpty(filter)
.concatWith(TextUtils.isEmpty(filter)
? defaultCategories() : Observable.empty())
)
.filter(categoryItem -> !containsYear(categoryItem.getName()))
.distinct()
.sorted(sortBySimilarity(filter))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
s -> categoriesAdapter.add(s),
@ -228,6 +231,12 @@ public class CategorizationFragment extends DaggerFragment {
);
}
private Comparator<CategoryItem> sortBySimilarity(final String filter) {
Comparator<String> stringSimilarityComparator = StringSortingUtils.sortBySimilarity(filter);
return (firstItem, secondItem) -> stringSimilarityComparator
.compare(firstItem.getName(), secondItem.getName());
}
private List<String> getStringList(List<CategoryItem> input) {
List<String> output = new ArrayList<>();
for (CategoryItem item : input) {

View file

@ -13,7 +13,6 @@ import android.text.TextUtils;
import javax.inject.Inject;
import dagger.android.AndroidInjection;
import fr.free.nrw.commons.data.Category;
import fr.free.nrw.commons.data.DBOpenHelper;
import timber.log.Timber;
@ -142,11 +141,13 @@ public class CategoryContentProvider extends ContentProvider {
public int update(@NonNull Uri uri, ContentValues contentValues, String selection,
String[] selectionArgs) {
/*
SQL Injection warnings: First, note that we're not exposing this to the outside world (exported="false")
Even then, we should make sure to sanitize all user input appropriately. Input that passes through ContentValues
SQL Injection warnings: First, note that we're not exposing this to the
outside world (exported="false"). Even then, we should make sure to sanitize
all user input appropriately. Input that passes through ContentValues
should be fine. So only issues are those that pass in via concating.
In here, the only concat created argument is for id. It is cast to an int, and will error out otherwise.
In here, the only concat created argument is for id. It is cast to an int,
and will error out otherwise.
*/
int uriType = uriMatcher.match(uri);
SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();

View file

@ -156,10 +156,12 @@ public class ContributionsContentProvider extends ContentProvider {
public int update(@NonNull Uri uri, ContentValues contentValues, String selection, String[] selectionArgs) {
/*
SQL Injection warnings: First, note that we're not exposing this to the outside world (exported="false")
Even then, we should make sure to sanitize all user input appropriately. Input that passes through ContentValues
should be fine. So only issues are those that pass in via concating.
Even then, we should make sure to sanitize all user input appropriately.
Input that passes through ContentValuesshould be fine. So only issues are those that pass
in via concating.
In here, the only concat created argument is for id. It is cast to an int, and will error out otherwise.
In here, the only concat created argument is for id. It is cast to an int, and will
error out otherwise.
*/
int uriType = uriMatcher.match(uri);
SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();

View file

@ -27,7 +27,7 @@ public class ModifierSequence {
public ModifierSequence(Uri mediaUri, JSONObject data) {
this(mediaUri);
JSONArray modifiersJSON = data.optJSONArray("modifiers");
for(int i=0; i< modifiersJSON.length(); i++) {
for (int i=0; i< modifiersJSON.length(); i++) {
modifiers.add(PageModifier.fromJSON(modifiersJSON.optJSONObject(i)));
}
}
@ -41,7 +41,7 @@ public class ModifierSequence {
}
public String executeModifications(String pageName, String pageContents) {
for(PageModifier modifier: modifiers) {
for (PageModifier modifier: modifiers) {
pageContents = modifier.doModification(pageName, pageContents);
}
return pageContents;
@ -60,7 +60,7 @@ public class ModifierSequence {
JSONObject data = new JSONObject();
try {
JSONArray modifiersJSON = new JSONArray();
for(PageModifier modifier: modifiers) {
for (PageModifier modifier: modifiers) {
modifiersJSON.put(modifier.toJSON());
}
data.put("modifiers", modifiersJSON);
@ -81,7 +81,8 @@ public class ModifierSequence {
// Hardcoding column positions!
ModifierSequence ms = null;
try {
ms = new ModifierSequence(Uri.parse(cursor.getString(1)), new JSONObject(cursor.getString(2)));
ms = new ModifierSequence(Uri.parse(cursor.getString(1)),
new JSONObject(cursor.getString(2)));
} catch (JSONException e) {
throw new RuntimeException(e);
}

View file

@ -391,7 +391,7 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
public UploadResult uploadFile(String filename, InputStream file, long dataLength, String pageContents, String editSummary, final ProgressListener progressListener) throws IOException {
ApiResult result = api.upload(filename, file, dataLength, pageContents, editSummary, progressListener::onProgress);
Log.e("WTF", "Result: "+result.toString());
Log.e("WTF", "Result: " +result.toString());
String resultStatus = result.getString("/api/upload/@result");
if (!resultStatus.equals("Success")) {

View file

@ -10,6 +10,7 @@ import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.Fragment;
@ -49,26 +50,45 @@ public class NearbyActivity extends NavigationBaseActivity {
private boolean isMapViewActive = false;
private static final int LOCATION_REQUEST = 1;
private static final String MAP_LAST_USED_PREFERENCE = "mapLastUsed";
private LocationServiceManager locationManager;
private LatLng curLatLang;
private Bundle bundle;
private NearbyAsyncTask nearbyAsyncTask;
private SharedPreferences sharedPreferences;
private NearbyActivityMode viewMode;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
setContentView(R.layout.activity_nearby);
ButterKnife.bind(this);
checkLocationPermission();
bundle = new Bundle();
initDrawer();
initViewState();
}
private void initViewState() {
if (sharedPreferences.getBoolean(MAP_LAST_USED_PREFERENCE, false)) {
viewMode = NearbyActivityMode.MAP;
} else {
viewMode = NearbyActivityMode.LIST;
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
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);
}
@ -79,13 +99,10 @@ public class NearbyActivity extends NavigationBaseActivity {
case R.id.action_refresh:
refreshView();
return true;
case R.id.action_map:
showMapView();
if (isMapViewActive) {
item.setIcon(R.drawable.ic_list_white_24dp);
} else {
item.setIcon(R.drawable.ic_map_white_24dp);
}
case R.id.action_toggle_view:
viewMode = viewMode.toggle();
item.setIcon(viewMode.getIcon());
toggleView();
return true;
default:
return super.onOptionsItemSelected(item);
@ -163,15 +180,30 @@ public class NearbyActivity extends NavigationBaseActivity {
if (progressBar != null) {
progressBar.setVisibility(View.GONE);
}
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
Fragment noPermissionsFragment = new NoPermissionsFragment();
fragmentTransaction.replace(R.id.container, noPermissionsFragment);
fragmentTransaction.commit();
showLocationPermissionDeniedErrorDialog();
}
}
}
}
private void showLocationPermissionDeniedErrorDialog() {
new AlertDialog.Builder(this)
.setMessage(R.string.nearby_needs_permissions)
.setCancelable(false)
.setPositiveButton(R.string.give_permission, (dialog, which) -> {
//will ask for the location permission again
checkLocationPermission();
})
.setNegativeButton(R.string.cancel, (dialog, which) -> {
//dismiss dialog and finish activity
dialog.cancel();
finish();
})
.create()
.show();
}
private void checkGps() {
LocationManager manager = (LocationManager) getSystemService(LOCATION_SERVICE);
if (!manager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
@ -203,20 +235,16 @@ public class NearbyActivity extends NavigationBaseActivity {
}
}
private void showMapView() {
private void toggleView() {
if (nearbyAsyncTask != null) {
if (!isMapViewActive) {
isMapViewActive = true;
if (nearbyAsyncTask.getStatus() == AsyncTask.Status.FINISHED) {
if (nearbyAsyncTask.getStatus() == AsyncTask.Status.FINISHED) {
if (viewMode.isMap()) {
setMapFragment();
}
} else {
isMapViewActive = false;
if (nearbyAsyncTask.getStatus() == AsyncTask.Status.FINISHED) {
} else {
setListFragment();
}
}
sharedPreferences.edit().putBoolean(MAP_LAST_USED_PREFERENCE, viewMode.isMap()).apply();
}
}
@ -292,7 +320,7 @@ public class NearbyActivity extends NavigationBaseActivity {
bundle.putString("CurLatLng", gsonCurLatLng);
// Begin the transaction
if (isMapViewActive) {
if (viewMode.isMap()) {
setMapFragment();
} else {
setListFragment();

View file

@ -0,0 +1,30 @@
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

@ -309,7 +309,7 @@ public class MultipleShareActivity extends AuthenticatedActivity
@Override
public void onBackStackChanged() {
getSupportActionBar().setDisplayHomeAsUpEnabled(mediaDetails != null && mediaDetails.isVisible());
getSupportActionBar().setDisplayHomeAsUpEnabled(mediaDetails != null && mediaDetails.isVisible()) ;
}
}

View file

@ -90,7 +90,8 @@ public class SingleUploadFragment extends DaggerFragment {
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_single_upload, container, false);
ButterKnife.bind(this, rootView);

View file

@ -0,0 +1,46 @@
package fr.free.nrw.commons.utils;
import java.util.Comparator;
import info.debatty.java.stringsimilarity.Levenshtein;
public class StringSortingUtils {
private StringSortingUtils() {
//no-op
}
/**
* Returns Comparator for sorting strings by its similarity with Levenshtein
* algorithm. By using this Comparator we get results from the highest to
* the lowest match.
*
* @param filter pattern to compare similarity
* @return Comparator with string similarity
*/
public static Comparator<String> sortBySimilarity(final String filter) {
return (firstItem, secondItem) -> {
double firstItemSimilarity = calculateSimilarity(firstItem, filter);
double secondItemSimilarity = calculateSimilarity(secondItem, filter);
return (int) Math.signum(secondItemSimilarity - firstItemSimilarity);
};
}
private static double calculateSimilarity(String firstString, String secondString) {
String longer = firstString.toLowerCase();
String shorter = secondString.toLowerCase();
if (firstString.length() < secondString.length()) {
longer = secondString;
shorter = firstString;
}
int longerLength = longer.length();
if (longerLength == 0) {
return 1.0;
}
double distanceBetweenStrings = new Levenshtein().distance(longer, shorter);
return (longerLength - distanceBetweenStrings) / (double) longerLength;
}
}