mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-27 21:03:54 +01:00
Merge branch 'master' into dependency-injection
This commit is contained in:
commit
f134d23ecb
111 changed files with 1230 additions and 364 deletions
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")) {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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()) ;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue