diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1688b3b027..60562d7519 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,18 @@
# Wikimedia Commons for Android
+## v2.8.1
+- Fixed bug with category edits not being sent to server
+
+## v2.8.0
+- Fixed failed uploads by modifying auth token
+- Fixed crashes during upload by storing file temporarily
+- Added automatic Wikidata p18 edits upon Nearby upload
+- Added Explore feature to browse other Commons images, including featured images
+- Added Achievements feature to see current level and upload stats
+- Added quiz for users with high deletion rates
+- Added first run tutorial for Nearby
+- Various small improvements to ShareActivity UI
+
## v2.7.2
- Modified subtext for "automatically get current location" setting to emphasize that it will reveal user's location
diff --git a/CREDITS b/CREDITS
index 6847ac9b66..a620e5a88e 100644
--- a/CREDITS
+++ b/CREDITS
@@ -40,6 +40,12 @@ their contribution to the product.
* Suchit Kar
* Tanvi Dadu
* Ujjwal Agrawal
+* Mansi Agarwal
+* Siddharth Vaish
+* Ashish Kumar
+* Ilgaz Er
+* Alicia Bendz
+* Kaartic Sivaraam
3rd party open source libraries used:
* Butterknife
diff --git a/README.md b/README.md
index 57ba0db031..70a5128aad 100644
--- a/README.md
+++ b/README.md
@@ -39,11 +39,9 @@ We try to have an extensive documentation at [our wiki here at Github][5]:
* [Fresco][27]
* [Stetho][28]
* [Dagger][29]
-* [AndroidSVG][30]
-* [Java-HTTP-Fluent][31]
-* [CircleProgressBar][32]
-* [Glide][33]
-* [Leak Canary][34]
+* [Java-HTTP-Fluent][30]
+* [CircleProgressBar][31]
+* [Leak Canary][32]
## License ##
@@ -80,8 +78,6 @@ This software is open source, licensed under the [Apache License 2.0][4].
[27]: https://github.com/facebook/fresco
[28]: https://github.com/facebook/stetho
[29]: https://github.com/google/dagger
-[30]: https://github.com/BigBadaboom/androidsvg
-[31]: https://github.com/yuvipanda/java-http-fluent/blob/master/src/main/java/in/yuvi/http/fluent/Http.java
-[32]: https://github.com/dinuscxj/CircleProgressBar
-[33]: https://github.com/bumptech/glide
-[34]: https://github.com/square/leakcanary
+[30]: https://github.com/yuvipanda/java-http-fluent/blob/master/src/main/java/in/yuvi/http/fluent/Http.java
+[31]: https://github.com/dinuscxj/CircleProgressBar
+[32]: https://github.com/square/leakcanary
diff --git a/app/build.gradle b/app/build.gradle
index 05050e4fb1..c6e6eaaf42 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -7,7 +7,6 @@ apply from: 'quality.gradle'
apply plugin: 'com.getkeepsafe.dexcount'
dependencies {
- implementation 'com.squareup.picasso:picasso:2.71828'
implementation 'com.prof.rssparser:rssparser:1.1'
implementation 'com.github.nicolas-raoul:Quadtree:ac16ea8035bf07'
implementation 'fr.avianey.com.viewpagerindicator:library:2.4.1.1@aar'
@@ -21,12 +20,12 @@ dependencies {
implementation 'com.jakewharton.timber:timber:4.5.1'
implementation 'info.debatty:java-string-similarity:0.24'
implementation 'com.borjabravo:readmoretextview:2.1.0'
-
- implementation 'com.android.support.constraint:constraint-layout:1.1.0'
+ implementation 'com.android.support.constraint:constraint-layout:1.1.2'
implementation('com.mapbox.mapboxsdk:mapbox-android-sdk:5.5.0@aar') {
transitive = true
}
implementation 'com.github.deano2390:MaterialShowcaseView:1.2.0'
+
//noinspection GradleCompatible
implementation "com.android.support:support-v4:$SUPPORT_LIB_VERSION"
implementation "com.android.support:appcompat-v7:$SUPPORT_LIB_VERSION"
@@ -38,9 +37,28 @@ dependencies {
implementation 'com.squareup.okhttp3:okhttp:3.9.1'
implementation 'com.squareup.okio:okio:1.13.0'
implementation 'io.reactivex.rxjava2:rxandroid:2.0.1'
+
// Because RxAndroid releases are few and far between, it is recommended you also
+
// explicitly depend on RxJava's latest version for bug fixes and new features.
+ implementation 'io.reactivex.rxjava2:rxjava:2.1.2'
+ implementation 'com.jakewharton.rxbinding2:rxbinding:2.0.0'
+ implementation 'com.jakewharton.rxbinding2:rxbinding-support-v4:2.0.0'
+ implementation 'com.jakewharton.rxbinding2:rxbinding-appcompat-v7:2.0.0'
+ implementation 'com.jakewharton.rxbinding2:rxbinding-design:2.0.0'
+ implementation 'com.facebook.fresco:fresco:1.3.0'
+ implementation 'com.facebook.stetho:stetho:1.5.0'
implementation 'com.android.support:multidex:1.0.3'
+ testImplementation 'junit:junit:4.12'
+ testImplementation 'org.robolectric:robolectric:3.7.1'
+ testImplementation 'org.robolectric:multidex:3.4.2'
+ testImplementation 'com.squareup.okhttp3:mockwebserver:3.8.1'
+ androidTestImplementation 'com.squareup.okhttp3:mockwebserver:3.8.1'
+ androidTestImplementation "com.android.support:support-annotations:${project.SUPPORT_LIB_VERSION}"
+ androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
+ debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.5.1'
+ releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1'
+ testImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1'
implementation 'io.reactivex.rxjava2:rxjava:2.1.2'
implementation 'com.jakewharton.rxbinding2:rxbinding:2.0.0'
implementation 'com.jakewharton.rxbinding2:rxbinding-support-v4:2.0.0'
@@ -53,7 +71,6 @@ dependencies {
implementation "com.google.dagger:dagger-android-support:$DAGGER_VERSION"
kapt "com.google.dagger:dagger-android-processor:$DAGGER_VERSION"
kapt "com.google.dagger:dagger-compiler:$DAGGER_VERSION"
- testImplementation 'org.robolectric:multidex:3.4.2'
testImplementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
testImplementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
testImplementation 'junit:junit:4.12'
@@ -61,21 +78,17 @@ dependencies {
testImplementation 'com.nhaarman:mockito-kotlin:1.5.0'
testImplementation 'com.squareup.okhttp3:mockwebserver:3.8.1'
implementation 'com.dinuscxj:circleprogressbar:1.1.1'
-
- implementation 'com.caverock:androidsvg:1.2.1'
- implementation 'com.github.bumptech.glide:glide:4.7.1'
- kapt 'com.github.bumptech.glide:compiler:4.7.1'
-
androidTestImplementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
androidTestImplementation 'com.squareup.okhttp3:mockwebserver:3.8.1'
androidTestImplementation "com.android.support:support-annotations:$SUPPORT_LIB_VERSION"
androidTestImplementation 'com.android.support.test:rules:1.0.2'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
-
debugImplementation "com.squareup.leakcanary:leakcanary-android:$LEAK_CANARY"
releaseImplementation "com.squareup.leakcanary:leakcanary-android-no-op:$LEAK_CANARY"
testImplementation "com.squareup.leakcanary:leakcanary-android-no-op:$LEAK_CANARY"
+ //For handling runtime permissions
+ implementation 'com.karumi:dexter:5.0.0'
}
@@ -87,16 +100,14 @@ android {
defaultConfig {
applicationId 'fr.free.nrw.commons'
- versionCode 85
- versionName '2.7.2'
+ versionCode 88
+ versionName '2.8.1'
setProperty("archivesBaseName", "app-commons-v$versionName-" + getBranchName())
minSdkVersion project.minSdkVersion
targetSdkVersion project.targetSdkVersion
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
-
- multiDexEnabled true
}
testOptions {
@@ -117,7 +128,7 @@ android {
buildTypes {
release {
minifyEnabled false // See https://stackoverflow.com/questions/40232404/google-play-apk-and-android-studio-apk-usb-debug-behaving-differently - proguard.cfg modification alone insufficient.
- proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt', 'proguard-glide.txt'
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
}
debug {
testCoverageEnabled true
@@ -149,6 +160,7 @@ android {
buildConfigField "String", "CONTRIBUTION_AUTHORITY", "\"fr.free.nrw.commons.contributions.contentprovider\""
buildConfigField "String", "MODIFICATION_AUTHORITY", "\"fr.free.nrw.commons.modifications.contentprovider\""
buildConfigField "String", "CATEGORY_AUTHORITY", "\"fr.free.nrw.commons.categories.contentprovider\""
+ buildConfigField "String", "RECENT_SEARCH_AUTHORITY", "\"fr.free.nrw.commons.explore.recentsearches.contentprovider\""
dimension 'tier'
}
@@ -175,6 +187,7 @@ android {
buildConfigField "String", "CONTRIBUTION_AUTHORITY", "\"fr.free.nrw.commons.beta.contributions.contentprovider\""
buildConfigField "String", "MODIFICATION_AUTHORITY", "\"fr.free.nrw.commons.beta.modifications.contentprovider\""
buildConfigField "String", "CATEGORY_AUTHORITY", "\"fr.free.nrw.commons.beta.categories.contentprovider\""
+ buildConfigField "String", "RECENT_SEARCH_AUTHORITY", "\"fr.free.nrw.commons.beta.explore.recentsearches.contentprovider\""
dimension 'tier'
}
diff --git a/app/proguard-glide.txt b/app/proguard-glide.txt
deleted file mode 100644
index ef3437660a..0000000000
--- a/app/proguard-glide.txt
+++ /dev/null
@@ -1,9 +0,0 @@
--keep public class * implements com.bumptech.glide.module.GlideModule
--keep public class * extends com.bumptech.glide.module.AppGlideModule
--keep public enum com.bumptech.glide.load.ImageHeaderParser$** {
- **[] $VALUES;
- public *;
-}
-
-# for DexGuard only
--keepresourcexmlelements manifest/application/meta-data@value=GlideModule
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index e80e2a84f3..506a8c5e0e 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,4 +1,3 @@
-
@@ -18,7 +17,6 @@
-
@@ -44,8 +42,7 @@
-
@@ -54,12 +51,6 @@
-
-
-
@@ -123,6 +114,7 @@
android:label="@string/Achievements" />
+
+
@@ -141,6 +134,7 @@
+
@@ -152,6 +146,7 @@
+
@@ -190,7 +185,7 @@
@@ -207,4 +202,4 @@
-
\ No newline at end of file
+
diff --git a/app/src/main/java/fr/free/nrw/commons/CommonsApplication.java b/app/src/main/java/fr/free/nrw/commons/CommonsApplication.java
index cb47f75e9a..2ee5f305fb 100644
--- a/app/src/main/java/fr/free/nrw/commons/CommonsApplication.java
+++ b/app/src/main/java/fr/free/nrw/commons/CommonsApplication.java
@@ -1,9 +1,13 @@
package fr.free.nrw.commons;
+import android.app.Application;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.sqlite.SQLiteDatabase;
-import android.support.multidex.MultiDexApplication;
+import android.os.Build;
+import android.support.annotation.RequiresApi;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.imagepipeline.core.ImagePipelineConfig;
@@ -41,7 +45,7 @@
resDialogCommentPrompt = R.string.crash_dialog_comment_prompt,
resDialogOkToast = R.string.crash_dialog_ok_toast
)
-public class CommonsApplication extends MultiDexApplication {
+public class CommonsApplication extends Application {
@Inject SessionManager sessionManager;
@Inject DBOpenHelper dbOpenHelper;
@@ -50,6 +54,11 @@ public class CommonsApplication extends MultiDexApplication {
@Inject @Named("application_preferences") SharedPreferences applicationPrefs;
@Inject @Named("prefs") SharedPreferences otherPrefs;
+ /**
+ * Constants begin
+ */
+ public static final int OPEN_APPLICATION_DETAIL_SETTINGS = 1001;
+
public static final String DEFAULT_EDIT_SUMMARY = "Uploaded using [[COM:MOA|Commons Mobile App]]";
public static final String FEEDBACK_EMAIL = "commons-app-android@googlegroups.com";
@@ -60,6 +69,12 @@ public class CommonsApplication extends MultiDexApplication {
public static final String LOGS_PRIVATE_EMAIL_SUBJECT = "Commons Android App (%s) Logs";
+ public static final String NOTIFICATION_CHANNEL_ID_ALL = "CommonsNotificationAll";
+
+ /**
+ * Constants End
+ */
+
private RefWatcher refWatcher;
@@ -92,10 +107,23 @@ public void onCreate() {
Stetho.initializeWithDefaults(this);
}
+
+ if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O) {
+ createNotificationChannel();
+ }
+
// Fire progress callbacks for every 3% of uploaded content
System.setProperty("in.yuvi.http.fluent.PROGRESS_TRIGGER_THRESHOLD", "3.0");
}
+ @RequiresApi(26)
+ private void createNotificationChannel() {
+ NotificationChannel channel = new NotificationChannel(
+ NOTIFICATION_CHANNEL_ID_ALL,
+ getString(R.string.notifications_channel_name_all), NotificationManager.IMPORTANCE_NONE);
+ NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ manager.createNotificationChannel(channel);
+ }
/**
* Helps in setting up LeakCanary library
diff --git a/app/src/main/java/fr/free/nrw/commons/MediaDataExtractor.java b/app/src/main/java/fr/free/nrw/commons/MediaDataExtractor.java
index affb575282..16941b35af 100644
--- a/app/src/main/java/fr/free/nrw/commons/MediaDataExtractor.java
+++ b/app/src/main/java/fr/free/nrw/commons/MediaDataExtractor.java
@@ -2,6 +2,7 @@
import android.support.annotation.Nullable;
+import android.text.TextUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
@@ -161,7 +162,11 @@ private Node findTemplate(Element parentNode, String title_) throws IOException
Node node = nodes.item(i);
if (node.getNodeName().equals("template")) {
String foundTitle = getTemplateTitle(node);
- if (title.equals(new PageTitle(foundTitle).getDisplayText())) {
+ String displayText = new PageTitle(foundTitle).getDisplayText();
+ //replaced equals with contains because multiple sources had multiple formats
+ //say from two sources I had {{Location|12.958117388888889|77.6440805}} & {{Location dec|47.99081|7.845416|heading:255.9}},
+ //So exact string match would show null results for uploads via web
+ if (!(TextUtils.isEmpty(displayText)) && displayText.contains(title)) {
return node;
}
}
diff --git a/app/src/main/java/fr/free/nrw/commons/Utils.java b/app/src/main/java/fr/free/nrw/commons/Utils.java
index 4e4b46b01f..1fdff8eab6 100644
--- a/app/src/main/java/fr/free/nrw/commons/Utils.java
+++ b/app/src/main/java/fr/free/nrw/commons/Utils.java
@@ -19,7 +19,9 @@
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
+import java.util.LinkedHashMap;
import java.util.Locale;
+import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -110,6 +112,31 @@ public static int licenseNameFor(String license) {
throw new RuntimeException("Unrecognized license value: " + license);
}
+ /**
+ * Generates license url with given ID
+ * @param license License ID
+ * @return Url of license
+ */
+
+
+ @NonNull
+ public static String licenseUrlFor(String license) {
+ switch (license) {
+ case Prefs.Licenses.CC_BY_3:
+ return "https://creativecommons.org/licenses/by/3.0/";
+ case Prefs.Licenses.CC_BY_4:
+ return "https://creativecommons.org/licenses/by/4.0/";
+ case Prefs.Licenses.CC_BY_SA_3:
+ return "https://creativecommons.org/licenses/by-sa/3.0/";
+ case Prefs.Licenses.CC_BY_SA_4:
+ return "https://creativecommons.org/licenses/by-sa/4.0/";
+ case Prefs.Licenses.CC0:
+ return "https://creativecommons.org/publicdomain/zero/1.0/";
+ default:
+ throw new RuntimeException("Unrecognized license value: " + license);
+ }
+ }
+
/**
* Fixing incorrect extension
* @param title File name
@@ -215,4 +242,14 @@ public static Bitmap getScreenShot(View view) {
return bitmap;
}
+ public static Map arraysToMap(K[] kArray, V[] vArray){
+ if(kArray.length!=vArray.length)
+ throw new RuntimeException("arraysToMap array sizes don't match");
+ Map map=new LinkedHashMap<>();
+ for (int i=0;i 0) {
+ return accounts[0];
+ }
+ } catch (SecurityException e) {
+ Timber.e(e);
+ }
+ return null;
+ }
+
+ @Nullable
+ public static String getUserName(Context context) {
+ Account account = account(context);
+ return account == null ? null : account.name;
+ }
+
+ @Nullable
+ public static String getPassword(Context context) {
+ Account account = account(context);
+ return account == null ? null : accountManager(context).getPassword(account);
+ }
+
+ private static AccountManager accountManager(Context context) {
+ return AccountManager.get(context);
+ }
+
}
diff --git a/app/src/main/java/fr/free/nrw/commons/auth/AuthenticatedActivity.java b/app/src/main/java/fr/free/nrw/commons/auth/AuthenticatedActivity.java
index 611cb7975d..dd3c1f607e 100644
--- a/app/src/main/java/fr/free/nrw/commons/auth/AuthenticatedActivity.java
+++ b/app/src/main/java/fr/free/nrw/commons/auth/AuthenticatedActivity.java
@@ -16,7 +16,8 @@
public abstract class AuthenticatedActivity extends NavigationBaseActivity {
- @Inject SessionManager sessionManager;
+ @Inject
+ protected SessionManager sessionManager;
@Inject
MediaWikiApi mediaWikiApi;
private String authCookie;
diff --git a/app/src/main/java/fr/free/nrw/commons/auth/LoginActivity.java b/app/src/main/java/fr/free/nrw/commons/auth/LoginActivity.java
index 35e5f4c943..1f2ebf0479 100644
--- a/app/src/main/java/fr/free/nrw/commons/auth/LoginActivity.java
+++ b/app/src/main/java/fr/free/nrw/commons/auth/LoginActivity.java
@@ -16,6 +16,7 @@
import android.support.design.widget.TextInputLayout;
import android.support.v4.app.NavUtils;
import android.support.v4.content.ContextCompat;
+import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatDelegate;
import android.text.Editable;
import android.text.TextWatcher;
@@ -26,6 +27,7 @@
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
+import android.widget.Toast;
import java.io.IOException;
import java.util.Locale;
@@ -41,6 +43,7 @@
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.WelcomeActivity;
+import fr.free.nrw.commons.category.CategoryImagesActivity;
import fr.free.nrw.commons.contributions.ContributionsActivity;
import fr.free.nrw.commons.di.ApplicationlessInjection;
import fr.free.nrw.commons.mwapi.MediaWikiApi;
@@ -55,12 +58,12 @@
import static android.view.KeyEvent.KEYCODE_ENTER;
import static android.view.View.VISIBLE;
import static android.view.inputmethod.EditorInfo.IME_ACTION_DONE;
-import static fr.free.nrw.commons.auth.AccountUtil.ACCOUNT_TYPE;
import static fr.free.nrw.commons.auth.AccountUtil.AUTH_TOKEN_TYPE;
public class LoginActivity extends AccountAuthenticatorActivity {
public static final String PARAM_USERNAME = "fr.free.nrw.commons.login.username";
+ private static final String FEATURED_IMAGES_CATEGORY = "Category:Featured_pictures_on_Wikimedia_Commons";
@Inject MediaWikiApi mwApi;
@Inject SessionManager sessionManager;
@@ -77,6 +80,7 @@ public class LoginActivity extends AccountAuthenticatorActivity {
@BindView(R.id.login_credentials) TextView loginCredentials;
@BindView(R.id.two_factor_container) TextInputLayout twoFactorContainer;
@BindView(R.id.forgotPassword) HtmlTextView forgotPasswordText;
+ @BindView(R.id.skipLogin) HtmlTextView skipLoginText;
ProgressDialog progressDialog;
private AppCompatDelegate delegate;
@@ -126,6 +130,15 @@ public void onCreate(Bundle savedInstanceState) {
signupButton.setOnClickListener(view -> signUp());
forgotPasswordText.setOnClickListener(view -> forgotPassword());
+ skipLoginText.setOnClickListener(view -> new AlertDialog.Builder(this).setTitle(R.string.skip_login_title)
+ .setMessage(R.string.skip_login_message)
+ .setCancelable(false)
+ .setPositiveButton(R.string.yes, (dialog, which) -> {
+ dialog.cancel();
+ skipLogin();
+ })
+ .setNegativeButton(R.string.no, (dialog, which) -> dialog.cancel())
+ .show());
if(BuildConfig.FLAVOR.equals("beta")){
loginCredentials.setText(getString(R.string.login_credential));
@@ -134,6 +147,17 @@ public void onCreate(Bundle savedInstanceState) {
}
}
+ /**
+ * This function is called when user skips the login.
+ * It redirects the user to Explore Activity.
+ */
+ private void skipLogin() {
+ prefs.edit().putBoolean("login_skipped", true).apply();
+ CategoryImagesActivity.startYourself(this, getString(R.string.title_activity_explore), FEATURED_IMAGES_CATEGORY);
+ finish();
+
+ }
+
private void forgotPassword() {
Utils.handleWebUrl(this, Uri.parse(BuildConfig.FORGOT_PASSWORD_URL));
}
@@ -160,9 +184,15 @@ protected void onResume() {
if (sessionManager.getCurrentAccount() != null
&& sessionManager.isUserLoggedIn()
&& sessionManager.getCachedAuthCookie() != null) {
+ prefs.edit().putBoolean("login_skipped", false).apply();
sessionManager.revalidateAuthToken();
startMainActivity();
}
+
+ if (prefs.getBoolean("login_skipped", false)){
+ skipLogin();
+ }
+
}
@Override
@@ -242,7 +272,7 @@ private void handlePassResult(String username, String password) {
if (response != null) {
Bundle authResult = new Bundle();
authResult.putString(AccountManager.KEY_ACCOUNT_NAME, username);
- authResult.putString(AccountManager.KEY_ACCOUNT_TYPE, ACCOUNT_TYPE);
+ authResult.putString(AccountManager.KEY_ACCOUNT_TYPE, BuildConfig.ACCOUNT_TYPE);
response.onResult(authResult);
}
}
diff --git a/app/src/main/java/fr/free/nrw/commons/auth/SessionManager.java b/app/src/main/java/fr/free/nrw/commons/auth/SessionManager.java
index 4a22b88c90..cd23d12829 100644
--- a/app/src/main/java/fr/free/nrw/commons/auth/SessionManager.java
+++ b/app/src/main/java/fr/free/nrw/commons/auth/SessionManager.java
@@ -76,10 +76,6 @@ public void createAccount(@Nullable AccountAuthenticatorResponse response,
ContentResolver.setSyncAutomatically(account, BuildConfig.MODIFICATION_AUTHORITY, true); // Enable sync by default!
}
- private AccountManager accountManager() {
- return AccountManager.get(context);
- }
-
/**
* @return Account|null
*/
@@ -95,6 +91,22 @@ public Account getCurrentAccount() {
return currentAccount;
}
+ @Nullable
+ public String getUserName() {
+ Account account = getCurrentAccount();
+ return account == null ? null : account.name;
+ }
+
+ @Nullable
+ public String getPassword() {
+ Account account = getCurrentAccount();
+ return account == null ? null : accountManager().getPassword(account);
+ }
+
+ private AccountManager accountManager() {
+ return AccountManager.get(context);
+ }
+
public Boolean revalidateAuthToken() {
AccountManager accountManager = AccountManager.get(context);
Account curAccount = getCurrentAccount();
@@ -103,12 +115,13 @@ public Boolean revalidateAuthToken() {
return false; // This should never happen
}
- accountManager.invalidateAuthToken(BuildConfig.ACCOUNT_TYPE, mediaWikiApi.getAuthCookie());
+ accountManager.invalidateAuthToken(BuildConfig.ACCOUNT_TYPE, null);
String authCookie = getAuthCookie();
if (authCookie == null) {
return false;
}
+
mediaWikiApi.setAuthCookie(authCookie);
return true;
}
diff --git a/app/src/main/java/fr/free/nrw/commons/auth/WikiAccountAuthenticator.java b/app/src/main/java/fr/free/nrw/commons/auth/WikiAccountAuthenticator.java
index 7a0980d80f..2f71a69b42 100644
--- a/app/src/main/java/fr/free/nrw/commons/auth/WikiAccountAuthenticator.java
+++ b/app/src/main/java/fr/free/nrw/commons/auth/WikiAccountAuthenticator.java
@@ -13,10 +13,7 @@
import android.support.annotation.Nullable;
import fr.free.nrw.commons.BuildConfig;
-import fr.free.nrw.commons.contributions.ContributionsContentProvider;
-import fr.free.nrw.commons.modifications.ModificationsContentProvider;
-import static fr.free.nrw.commons.auth.AccountUtil.ACCOUNT_TYPE;
import static fr.free.nrw.commons.auth.AccountUtil.AUTH_TOKEN_TYPE;
public class WikiAccountAuthenticator extends AbstractAccountAuthenticator {
@@ -99,7 +96,7 @@ public Bundle hasFeatures(@NonNull AccountAuthenticatorResponse response,
}
private boolean supportedAccountType(@Nullable String type) {
- return ACCOUNT_TYPE.equals(type);
+ return BuildConfig.ACCOUNT_TYPE.equals(type);
}
private Bundle addAccount(AccountAuthenticatorResponse response) {
diff --git a/app/src/main/java/fr/free/nrw/commons/category/CategoriesAdapterFactory.java b/app/src/main/java/fr/free/nrw/commons/category/CategoriesAdapterFactory.java
index 417121c443..77b6615da0 100644
--- a/app/src/main/java/fr/free/nrw/commons/category/CategoriesAdapterFactory.java
+++ b/app/src/main/java/fr/free/nrw/commons/category/CategoriesAdapterFactory.java
@@ -1,24 +1,23 @@
package fr.free.nrw.commons.category;
import com.pedrogomez.renderers.ListAdapteeCollection;
-import com.pedrogomez.renderers.RVRendererAdapter;
import com.pedrogomez.renderers.RendererBuilder;
import java.util.Collections;
import java.util.List;
-class CategoriesAdapterFactory {
- private final CategoriesRenderer.CategoryClickedListener listener;
+public class CategoriesAdapterFactory {
+ private final CategoryClickedListener listener;
- CategoriesAdapterFactory(CategoriesRenderer.CategoryClickedListener listener) {
+ public CategoriesAdapterFactory(CategoryClickedListener listener) {
this.listener = listener;
}
- public RVRendererAdapter create(List placeList) {
+ public CategoryRendererAdapter create(List placeList) {
RendererBuilder builder = new RendererBuilder()
.bind(CategoryItem.class, new CategoriesRenderer(listener));
ListAdapteeCollection collection = new ListAdapteeCollection<>(
placeList != null ? placeList : Collections.emptyList());
- return new RVRendererAdapter<>(builder, collection);
+ return new CategoryRendererAdapter(builder, collection);
}
}
diff --git a/app/src/main/java/fr/free/nrw/commons/category/CategoriesModel.java b/app/src/main/java/fr/free/nrw/commons/category/CategoriesModel.java
new file mode 100644
index 0000000000..d23c93c2a2
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/category/CategoriesModel.java
@@ -0,0 +1,225 @@
+package fr.free.nrw.commons.category;
+
+import android.content.SharedPreferences;
+import android.text.TextUtils;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+import fr.free.nrw.commons.mwapi.MediaWikiApi;
+import fr.free.nrw.commons.upload.GpsCategoryModel;
+import fr.free.nrw.commons.utils.StringSortingUtils;
+import io.reactivex.Observable;
+import timber.log.Timber;
+
+public class CategoriesModel implements CategoryClickedListener {
+ private static final int SEARCH_CATS_LIMIT = 25;
+
+ private final MediaWikiApi mwApi;
+ private final CategoryDao categoryDao;
+ private final SharedPreferences prefs;
+ private final SharedPreferences directPrefs;
+
+ private HashMap> categoriesCache;
+ private List selectedCategories;
+
+ @Inject GpsCategoryModel gpsCategoryModel;
+ @Inject
+ public CategoriesModel(MediaWikiApi mwApi, CategoryDao categoryDao,
+ @Named("default_preferences") SharedPreferences prefs,
+ @Named("direct_nearby_upload_prefs") SharedPreferences directPrefs) {
+ this.mwApi = mwApi;
+ this.categoryDao = categoryDao;
+ this.prefs = prefs;
+ this.directPrefs = directPrefs;
+ this.categoriesCache = new HashMap<>();
+ this.selectedCategories = new ArrayList<>();
+ }
+
+ //region Misc. utility methods
+ public Comparator sortBySimilarity(final String filter) {
+ Comparator stringSimilarityComparator = StringSortingUtils.sortBySimilarity(filter);
+ return (firstItem, secondItem) -> stringSimilarityComparator
+ .compare(firstItem.getName(), secondItem.getName());
+ }
+
+ public boolean containsYear(String item) {
+ //Check for current and previous year to exclude these categories from removal
+ Calendar now = Calendar.getInstance();
+ int year = now.get(Calendar.YEAR);
+ String yearInString = String.valueOf(year);
+
+ int prevYear = year - 1;
+ String prevYearInString = String.valueOf(prevYear);
+ Timber.d("Previous year: %s", prevYearInString);
+
+ //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 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))
+ || item.matches("(.*)needing(.*)") || item.matches("(.*)taken on(.*)")
+ || (item.matches(".*0s.*") && !item.matches(".*(200|201)0s.*")));
+ }
+
+ public void updateCategoryCount(CategoryItem item) {
+ Category category = categoryDao.find(item.getName());
+
+ // Newly used category...
+ if (category == null) {
+ category = new Category(null, item.getName(), new Date(), 0);
+ }
+
+ category.incTimesUsed();
+ categoryDao.save(category);
+ }
+ //endregion
+
+ //region Category Caching
+ public void cacheAll(HashMap> categories) {
+ categoriesCache.putAll(categories);
+ }
+
+ public HashMap> getCategoriesCache() {
+ return categoriesCache;
+ }
+
+ boolean cacheContainsKey(String term) {
+ return categoriesCache.containsKey(term);
+ }
+ //endregion
+
+ //region Category searching
+ public Observable searchAll(String term) {
+ //If user hasn't typed anything in yet, get GPS and recent items
+ if (TextUtils.isEmpty(term)) {
+ return gpsCategories()
+ .concatWith(titleCategories())
+ .concatWith(recentCategories());
+ }
+
+ //if user types in something that is in cache, return cached category
+ if (cacheContainsKey(term)) {
+ return Observable.fromIterable(getCachedCategories(term))
+ .map(name -> new CategoryItem(name, false));
+ }
+
+ //otherwise, search API for matching categories
+ return mwApi
+ .allCategories(term, SEARCH_CATS_LIMIT)
+ .map(name -> new CategoryItem(name, false));
+ }
+
+ public Observable searchCategories(String term) {
+ //If user hasn't typed anything in yet, get GPS and recent items
+ if (TextUtils.isEmpty(term)) {
+ return gpsCategories()
+ .concatWith(titleCategories())
+ .concatWith(recentCategories());
+ }
+
+ return mwApi
+ .searchCategories(term, SEARCH_CATS_LIMIT)
+ .map(s -> new CategoryItem(s, false));
+ }
+
+ private ArrayList getCachedCategories(String term) {
+ return categoriesCache.get(term);
+ }
+
+ public Observable defaultCategories() {
+ Observable directCat = directCategories();
+ if (hasDirectCategories()) {
+ Timber.d("Image has direct Cat");
+ return directCat
+ .concatWith(gpsCategories())
+ .concatWith(titleCategories())
+ .concatWith(recentCategories());
+ } else {
+ Timber.d("Image has no direct Cat");
+ return gpsCategories()
+ .concatWith(titleCategories())
+ .concatWith(recentCategories());
+ }
+ }
+
+ private boolean hasDirectCategories() {
+ return !directPrefs.getString("Category", "").equals("");
+ }
+
+ private Observable directCategories() {
+ String directCategory = directPrefs.getString("Category", "");
+ List categoryList = new ArrayList<>();
+ Timber.d("Direct category found: " + directCategory);
+
+ if (!directCategory.equals("")) {
+ categoryList.add(directCategory);
+ Timber.d("DirectCat does not equal emptyString. Direct Cat list has " + categoryList);
+ }
+ return Observable.fromIterable(categoryList).map(name -> new CategoryItem(name, false));
+ }
+
+ Observable gpsCategories() {
+ return Observable.fromIterable(gpsCategoryModel.getCategoryList())
+ .map(name -> new CategoryItem(name, false));
+ }
+
+ private Observable titleCategories() {
+ //Retrieve the title that was saved when user tapped submit icon
+ String title = prefs.getString("Title", "");
+
+ return mwApi
+ .searchTitles(title, SEARCH_CATS_LIMIT)
+ .map(name -> new CategoryItem(name, false));
+ }
+
+ private Observable recentCategories() {
+ return Observable.fromIterable(categoryDao.recentCategories(SEARCH_CATS_LIMIT))
+ .map(s -> new CategoryItem(s, false));
+ }
+ //endregion
+
+ //region Category Selection
+ @Override
+ public void categoryClicked(CategoryItem item) {
+ if (item.isSelected()) {
+ selectCategory(item);
+ updateCategoryCount(item);
+ } else {
+ unselectCategory(item);
+ }
+ }
+
+ public void selectCategory(CategoryItem item) {
+ selectedCategories.add(item);
+ }
+
+ public void unselectCategory(CategoryItem item) {
+ selectedCategories.remove(item);
+ }
+
+ public int selectedCategoriesCount() {
+ return selectedCategories.size();
+ }
+
+ public List getSelectedCategories() {
+ return selectedCategories;
+ }
+
+ public List getCategoryStringList() {
+ List output = new ArrayList<>();
+ for (CategoryItem item : selectedCategories) {
+ output.add(item.getName());
+ }
+ return output;
+ }
+ //endregion
+
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/category/CategoriesRenderer.java b/app/src/main/java/fr/free/nrw/commons/category/CategoriesRenderer.java
index 81cccdb72d..f9a349ccb0 100644
--- a/app/src/main/java/fr/free/nrw/commons/category/CategoriesRenderer.java
+++ b/app/src/main/java/fr/free/nrw/commons/category/CategoriesRenderer.java
@@ -1,5 +1,6 @@
package fr.free.nrw.commons.category;
+import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -11,7 +12,7 @@
import butterknife.ButterKnife;
import fr.free.nrw.commons.R;
-class CategoriesRenderer extends Renderer {
+public class CategoriesRenderer extends Renderer {
@BindView(R.id.tvName) CheckedTextView checkedView;
private final CategoryClickedListener listener;
@@ -44,11 +45,8 @@ protected void hookListeners(View view) {
@Override
public void render() {
CategoryItem item = getContent();
+ Log.e("Commons", "Rendering: "+item);
checkedView.setChecked(item.isSelected());
checkedView.setText(item.getName());
}
-
- interface CategoryClickedListener {
- void categoryClicked(CategoryItem item);
- }
}
diff --git a/app/src/main/java/fr/free/nrw/commons/category/CategorizationFragment.java b/app/src/main/java/fr/free/nrw/commons/category/CategorizationFragment.java
index 93ddb60d5d..0c1eec97bf 100644
--- a/app/src/main/java/fr/free/nrw/commons/category/CategorizationFragment.java
+++ b/app/src/main/java/fr/free/nrw/commons/category/CategorizationFragment.java
@@ -1,6 +1,8 @@
package fr.free.nrw.commons.category;
+import android.annotation.SuppressLint;
+import android.app.Activity;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.support.v7.app.AlertDialog;
@@ -73,24 +75,13 @@ public class CategorizationFragment extends CommonsDaggerSupportFragment {
@Inject @Named("prefs") SharedPreferences prefsPrefs;
@Inject @Named("direct_nearby_upload_prefs") SharedPreferences directPrefs;
@Inject CategoryDao categoryDao;
- @Inject GpsCategoryModel gpsCategoryModel;
+ @Inject CategoriesModel categoriesModel;
- private RVRendererAdapter categoriesAdapter;
+ private CategoryRendererAdapter categoriesAdapter;
private OnCategoriesSaveHandler onCategoriesSaveHandler;
- private HashMap> categoriesCache;
- private List selectedCategories = new ArrayList<>();
private TitleTextWatcher textWatcher = new TitleTextWatcher();
private boolean hasDirectCategories = false;
- private final CategoriesAdapterFactory adapterFactory = new CategoriesAdapterFactory(item -> {
- if (item.isSelected()) {
- selectedCategories.add(item);
- updateCategoryCount(item);
- } else {
- selectedCategories.remove(item);
- }
- });
-
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
@@ -100,18 +91,24 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container,
categoriesList.setLayoutManager(new LinearLayoutManager(getContext()));
ArrayList items = new ArrayList<>();
- categoriesCache = new HashMap<>();
if (savedInstanceState != null) {
items.addAll(savedInstanceState.getParcelableArrayList("currentCategories"));
//noinspection unchecked
- categoriesCache.putAll((HashMap>) savedInstanceState
+ categoriesModel.cacheAll((HashMap>) savedInstanceState
.getSerializable("categoriesCache"));
}
+ CategoriesAdapterFactory adapterFactory = new CategoriesAdapterFactory(item -> {
+ if (item.isSelected()) {
+ categoriesModel.selectCategory(item);
+ categoriesModel.updateCategoryCount(item);
+ } else {
+ categoriesModel.unselectCategory(item);
+ }
+ });
categoriesAdapter = adapterFactory.create(items);
categoriesList.setAdapter(categoriesAdapter);
-
categoriesFilter.addTextChangedListener(textWatcher);
categoriesFilter.setOnFocusChangeListener((v, hasFocus) -> {
@@ -162,22 +159,17 @@ public void onResume() {
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
- int itemCount = categoriesAdapter.getItemCount();
- ArrayList items = new ArrayList<>(itemCount);
- for (int i = 0; i < itemCount; i++) {
- items.add(categoriesAdapter.getItem(i));
- }
- outState.putParcelableArrayList("currentCategories", items);
- outState.putSerializable("categoriesCache", categoriesCache);
+ outState.putParcelableArrayList("currentCategories", categoriesAdapter.allItems());
+ outState.putSerializable("categoriesCache", categoriesModel.getCategoriesCache());
}
@Override
public boolean onOptionsItemSelected(MenuItem menuItem) {
switch (menuItem.getItemId()) {
case R.id.menu_save_categories:
- if (selectedCategories.size() > 0) {
+ if (categoriesModel.selectedCategoriesCount() > 0) {
//Some categories selected, proceed to submission
- onCategoriesSaveHandler.onCategoriesSave(getStringList(selectedCategories));
+ onCategoriesSaveHandler.onCategoriesSave(categoriesModel.getCategoryStringList());
} else {
//No categories selected, prompt the user to select some
showConfirmationDialog();
@@ -196,8 +188,9 @@ public void onActivityCreated(Bundle savedInstanceState) {
getActivity().setTitle(R.string.categories_activity_title);
}
+ @SuppressLint({"StringFormatInvalid", "CheckResult"})
private void updateCategoryList(String filter) {
- Observable.fromIterable(selectedCategories)
+ Observable.fromIterable(categoriesModel.getSelectedCategories())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe(disposable -> {
@@ -208,14 +201,14 @@ private void updateCategoryList(String filter) {
})
.observeOn(Schedulers.io())
.concatWith(
- searchAll(filter)
- .mergeWith(searchCategories(filter))
+ categoriesModel.searchAll(filter)
+ .mergeWith(categoriesModel.searchCategories(filter))
.concatWith(TextUtils.isEmpty(filter)
- ? defaultCategories() : Observable.empty())
+ ? categoriesModel.defaultCategories() : Observable.empty())
)
- .filter(categoryItem -> !containsYear(categoryItem.getName()))
+ .filter(categoryItem -> !categoriesModel.containsYear(categoryItem.getName()))
.distinct()
- .sorted(sortBySimilarity(filter))
+ .sorted(categoriesModel.sortBySimilarity(filter))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
s -> categoriesAdapter.add(s),
@@ -224,7 +217,7 @@ private void updateCategoryList(String filter) {
categoriesAdapter.notifyDataSetChanged();
categoriesSearchInProgress.setVisibility(View.GONE);
- if (categoriesAdapter.getItemCount() == selectedCategories.size()) {
+ if (categoriesAdapter.getItemCount() == categoriesModel.selectedCategoriesCount()) {
// There are no suggestions
if (TextUtils.isEmpty(filter)) {
// Allow to send image with no categories
@@ -258,13 +251,13 @@ private Observable defaultCategories() {
if (hasDirectCategories) {
Timber.d("Image has direct Cat");
return directCat
- .concatWith(gpsCategories())
+ .concatWith(categoriesModel.gpsCategories())
.concatWith(titleCategories())
.concatWith(recentCategories());
}
else {
Timber.d("Image has no direct Cat");
- return gpsCategories()
+ return categoriesModel.gpsCategories()
.concatWith(titleCategories())
.concatWith(recentCategories());
}
@@ -286,11 +279,6 @@ private Observable directCategories() {
return Observable.fromIterable(categoryList).map(name -> new CategoryItem(name, false));
}
- private Observable gpsCategories() {
- return Observable.fromIterable(gpsCategoryModel.getCategoryList())
- .map(name -> new CategoryItem(name, false));
- }
-
private Observable titleCategories() {
//Retrieve the title that was saved when user tapped submit icon
String title = prefs.getString("Title", "");
@@ -305,70 +293,6 @@ private Observable recentCategories() {
.map(s -> new CategoryItem(s, false));
}
- private Observable searchAll(String term) {
- //If user hasn't typed anything in yet, get GPS and recent items
- if (TextUtils.isEmpty(term)) {
- return Observable.empty();
- }
-
- //if user types in something that is in cache, return cached category
- if (categoriesCache.containsKey(term)) {
- return Observable.fromIterable(categoriesCache.get(term))
- .map(name -> new CategoryItem(name, false));
- }
-
- //otherwise, search API for matching categories
- return mwApi
- .allCategories(term, SEARCH_CATS_LIMIT)
- .map(name -> new CategoryItem(name, false));
- }
-
- private Observable searchCategories(String term) {
- //If user hasn't typed anything in yet, get GPS and recent items
- if (TextUtils.isEmpty(term)) {
- return Observable.empty();
- }
-
- return mwApi
- .searchCategories(term, SEARCH_CATS_LIMIT)
- .map(s -> new CategoryItem(s, false));
- }
-
- private boolean containsYear(String item) {
- //Check for current and previous year to exclude these categories from removal
- Calendar now = Calendar.getInstance();
- int year = now.get(Calendar.YEAR);
- String yearInString = String.valueOf(year);
-
- int prevYear = year - 1;
- String prevYearInString = String.valueOf(prevYear);
- Timber.d("Previous year: %s", prevYearInString);
-
- //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 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))
- || item.matches("(.*)needing(.*)") || item.matches("(.*)taken on(.*)")
- || (item.matches(".*0s.*") && !item.matches(".*(200|201)0s.*")));
- }
-
- private void updateCategoryCount(CategoryItem item) {
- Category category = categoryDao.find(item.getName());
-
- // Newly used category...
- if (category == null) {
- category = new Category(null, item.getName(), new Date(), 0);
- }
-
- category.incTimesUsed();
- categoryDao.save(category);
- }
-
- public int getCurrentSelectedCount() {
- return selectedCategories.size();
- }
-
/**
* Show dialog asking for confirmation to leave without saving categories.
*/
@@ -377,10 +301,10 @@ public void showBackButtonDialog() {
.setMessage("Are you sure you want to go back? The image will not "
+ "have any categories saved.")
.setTitle("Warning")
- .setPositiveButton("No", (dialog, id) -> {
+ .setPositiveButton(android.R.string.no, (dialog, id) -> {
//No need to do anything, user remains on categorization screen
})
- .setNegativeButton("Yes", (dialog, id) -> getActivity().finish())
+ .setNegativeButton(android.R.string.yes, (dialog, id) -> getActivity().finish())
.create()
.show();
}
@@ -391,12 +315,12 @@ private void showConfirmationDialog() {
+ "Are you sure you want to submit without selecting "
+ "categories?")
.setTitle("No Categories Selected")
- .setPositiveButton("No, go back", (dialog, id) -> {
+ .setPositiveButton(android.R.string.no, (dialog, id) -> {
//Exit menuItem so user can select their categories
})
- .setNegativeButton("Yes, submit", (dialog, id) -> {
+ .setNegativeButton(android.R.string.yes, (dialog, id) -> {
//Proceed to submission
- onCategoriesSaveHandler.onCategoriesSave(getStringList(selectedCategories));
+ onCategoriesSaveHandler.onCategoriesSave(categoriesModel.getCategoryStringList());
})
.create()
.show();
diff --git a/app/src/main/java/fr/free/nrw/commons/category/CategoryClickedListener.java b/app/src/main/java/fr/free/nrw/commons/category/CategoryClickedListener.java
new file mode 100644
index 0000000000..df99b40603
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/category/CategoryClickedListener.java
@@ -0,0 +1,5 @@
+package fr.free.nrw.commons.category;
+
+public interface CategoryClickedListener {
+ void categoryClicked(CategoryItem item);
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/category/CategoryDetailsActivity.java b/app/src/main/java/fr/free/nrw/commons/category/CategoryDetailsActivity.java
index 3ab3c2c072..fce2fac5ef 100644
--- a/app/src/main/java/fr/free/nrw/commons/category/CategoryDetailsActivity.java
+++ b/app/src/main/java/fr/free/nrw/commons/category/CategoryDetailsActivity.java
@@ -250,4 +250,24 @@ public void onBackPressed() {
}
super.onBackPressed();
}
+
+ /**
+ * This method is called on success of API call for Images inside a category.
+ * The viewpager will notified that number of items have changed.
+ */
+ public void viewPagerNotifyDataSetChanged() {
+ if (mediaDetails!=null){
+ mediaDetails.notifyDataSetChanged();
+ }
+ }
+
+ /**
+ * This method is called when viewPager has reached its end.
+ * Fetches more images using search query and adds it to the grid view and viewpager adapter
+ */
+ public void requestMoreImages() {
+ if (categoryImagesListFragment!=null){
+ categoryImagesListFragment.fetchMoreImagesViewPager();
+ }
+ }
}
diff --git a/app/src/main/java/fr/free/nrw/commons/category/CategoryImagesActivity.java b/app/src/main/java/fr/free/nrw/commons/category/CategoryImagesActivity.java
index 3821344724..eafc46e682 100644
--- a/app/src/main/java/fr/free/nrw/commons/category/CategoryImagesActivity.java
+++ b/app/src/main/java/fr/free/nrw/commons/category/CategoryImagesActivity.java
@@ -170,6 +170,16 @@ public Media getMediaAtPosition(int i) {
}
}
+ /**
+ * This method is called on success of API call for featured Images.
+ * The viewpager will notified that number of items have changed.
+ */
+ public void viewPagerNotifyDataSetChanged() {
+ if (mediaDetails!=null){
+ mediaDetails.notifyDataSetChanged();
+ }
+ }
+
/**
* This method is called on from getCount of MediaDetailPagerFragment
* The viewpager will contain same number of media items as that of media elements in adapter.
@@ -236,4 +246,14 @@ public boolean onOptionsItemSelected(MenuItem item) {
return super.onOptionsItemSelected(item);
}
}
+
+ /**
+ * This method is called when viewPager has reached its end.
+ * Fetches more images using search query and adds it to the gridView and viewpager adapter
+ */
+ public void requestMoreImages() {
+ if (categoryImagesListFragment!=null){
+ categoryImagesListFragment.fetchMoreImagesViewPager();
+ }
+ }
}
diff --git a/app/src/main/java/fr/free/nrw/commons/category/CategoryImagesListFragment.java b/app/src/main/java/fr/free/nrw/commons/category/CategoryImagesListFragment.java
index 385662a054..a78157ee25 100644
--- a/app/src/main/java/fr/free/nrw/commons/category/CategoryImagesListFragment.java
+++ b/app/src/main/java/fr/free/nrw/commons/category/CategoryImagesListFragment.java
@@ -190,6 +190,20 @@ public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCoun
});
}
+ /**
+ * This method is called when viewPager has reached its end.
+ * Fetches more images for the category and adds it to the grid view and viewpager adapter
+ */
+ public void fetchMoreImagesViewPager(){
+ if (hasMoreImages && !isLoading) {
+ isLoading = true;
+ fetchMoreImages();
+ }
+ if (!hasMoreImages){
+ progressBar.setVisibility(GONE);
+ }
+ }
+
/**
* Fetches more images for the category and adds it to the grid view adapter
*/
@@ -228,8 +242,17 @@ private void handleSuccess(List collection) {
return;
}
gridAdapter.addItems(collection);
+ try {
+ ((CategoryImagesActivity) getContext()).viewPagerNotifyDataSetChanged();
+ }catch (Exception e){
+ e.printStackTrace();
+ }
+ try {
+ ((CategoryDetailsActivity) getContext()).viewPagerNotifyDataSetChanged();
+ }catch (Exception e){
+ e.printStackTrace();
+ }
}
-
progressBar.setVisibility(GONE);
isLoading = false;
statusTextView.setVisibility(GONE);
diff --git a/app/src/main/java/fr/free/nrw/commons/category/CategoryItem.java b/app/src/main/java/fr/free/nrw/commons/category/CategoryItem.java
index f6bacfb51a..f3ade09d8b 100644
--- a/app/src/main/java/fr/free/nrw/commons/category/CategoryItem.java
+++ b/app/src/main/java/fr/free/nrw/commons/category/CategoryItem.java
@@ -3,7 +3,7 @@
import android.os.Parcel;
import android.os.Parcelable;
-class CategoryItem implements Parcelable {
+public class CategoryItem implements Parcelable {
private final String name;
private boolean selected;
@@ -71,4 +71,9 @@ public boolean equals(Object o) {
public int hashCode() {
return name.hashCode();
}
+
+ @Override
+ public String toString() {
+ return "CategoryItem: '" + name + '\'';
+ }
}
diff --git a/app/src/main/java/fr/free/nrw/commons/category/CategoryRendererAdapter.java b/app/src/main/java/fr/free/nrw/commons/category/CategoryRendererAdapter.java
new file mode 100644
index 0000000000..887ad595fc
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/category/CategoryRendererAdapter.java
@@ -0,0 +1,22 @@
+package fr.free.nrw.commons.category;
+
+import com.pedrogomez.renderers.AdapteeCollection;
+import com.pedrogomez.renderers.RVRendererAdapter;
+import com.pedrogomez.renderers.RendererBuilder;
+
+import java.util.ArrayList;
+
+public class CategoryRendererAdapter extends RVRendererAdapter {
+ CategoryRendererAdapter(RendererBuilder rendererBuilder, AdapteeCollection collection) {
+ super(rendererBuilder, collection);
+ }
+
+ protected ArrayList allItems() {
+ int itemCount = getItemCount();
+ ArrayList items = new ArrayList<>(itemCount);
+ for (int i = 0; i < itemCount; i++) {
+ items.add(getItem(i));
+ }
+ return items;
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/Contribution.java b/app/src/main/java/fr/free/nrw/commons/contributions/Contribution.java
index 9f5084e6a5..98e9665071 100644
--- a/app/src/main/java/fr/free/nrw/commons/contributions/Contribution.java
+++ b/app/src/main/java/fr/free/nrw/commons/contributions/Contribution.java
@@ -2,8 +2,11 @@
import android.net.Uri;
import android.os.Parcel;
+import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
+import android.support.annotation.StringDef;
+import java.lang.annotation.Retention;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
@@ -13,6 +16,8 @@
import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.settings.Prefs;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
public class Contribution extends Media {
public static Creator CREATOR = new Creator() {
@@ -33,6 +38,10 @@ public Contribution[] newArray(int i) {
public static final int STATE_QUEUED = 2;
public static final int STATE_IN_PROGRESS = 3;
+ @Retention(SOURCE)
+ @StringDef({SOURCE_CAMERA, SOURCE_GALLERY, SOURCE_EXTERNAL})
+ public @interface FileSource {}
+
public static final String SOURCE_CAMERA = "camera";
public static final String SOURCE_GALLERY = "gallery";
public static final String SOURCE_EXTERNAL = "external";
@@ -141,10 +150,6 @@ public void setDateUploaded(Date date) {
this.dateUploaded = date;
}
- public String getTrackingTemplates() {
- return "{{subst:unc}}"; // Remove when we have categorization
- }
-
public String getPageContents() {
StringBuilder buffer = new StringBuilder();
SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH);
@@ -169,8 +174,15 @@ public String getPageContents() {
buffer.append("== {{int:license-header}} ==\n")
.append(licenseTemplateFor(getLicense())).append("\n\n")
- .append("{{Uploaded from Mobile|platform=Android|version=").append(BuildConfig.VERSION_NAME).append("}}\n")
- .append(getTrackingTemplates());
+ .append("{{Uploaded from Mobile|platform=Android|version=").append(BuildConfig.VERSION_NAME).append("}}\n");
+ if(categories!=null&&categories.size()!=0) {
+ for (int i = 0; i < categories.size(); i++) {
+ String category = categories.get(i);
+ buffer.append("\n[[Category:").append(category).append("]]");
+ }
+ }
+ else
+ buffer.append("{{subst:unc}}");
return buffer.toString();
}
@@ -232,7 +244,7 @@ public String getWikiDataEntityId() {
/**
* When the corresponding wikidata entity is known as in case of nearby uploads, it can be set
* using the setter method
- * @param wikiDataEntityId
+ * @param wikiDataEntityId wikiDataEntityId
*/
public void setWikiDataEntityId(String wikiDataEntityId) {
this.wikiDataEntityId = wikiDataEntityId;
diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionController.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionController.java
index f57c8e239a..c282c7258f 100644
--- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionController.java
+++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionController.java
@@ -17,7 +17,7 @@
import java.util.Date;
import java.util.List;
-import fr.free.nrw.commons.upload.ShareActivity;
+import fr.free.nrw.commons.upload.UploadActivity;
import timber.log.Timber;
import static android.content.Intent.ACTION_GET_CONTENT;
@@ -95,8 +95,8 @@ public void startGalleryPick() {
public void handleImagePicked(int requestCode, @Nullable Uri uri, boolean isDirectUpload, String wikiDataEntityId) {
FragmentActivity activity = fragment.getActivity();
- Timber.d("handleImagePicked() called with onActivityResult()");
- Intent shareIntent = new Intent(activity, ShareActivity.class);
+ Timber.d("handleImagePicked() called with onActivityResult(). Boolean isDirectUpload: " + isDirectUpload + "String wikiDataEntityId: " + wikiDataEntityId);
+ Intent shareIntent = new Intent(activity, UploadActivity.class);
shareIntent.setAction(ACTION_SEND);
switch (requestCode) {
case SELECT_FROM_GALLERY:
@@ -113,21 +113,26 @@ public void handleImagePicked(int requestCode, @Nullable Uri uri, boolean isDire
shareIntent.setType("image/jpeg");
shareIntent.putExtra(EXTRA_STREAM, lastGeneratedCaptureUri);
shareIntent.putExtra(EXTRA_SOURCE, SOURCE_CAMERA);
-
break;
default:
break;
}
+
Timber.i("Image selected");
+ shareIntent.putExtra("isDirectUpload", isDirectUpload);
+ Timber.d("Put extras into image intent, isDirectUpload is " + isDirectUpload);
+
try {
- shareIntent.putExtra("isDirectUpload", isDirectUpload);
if (wikiDataEntityId != null && !wikiDataEntityId.equals("")) {
shareIntent.putExtra(WIKIDATA_ENTITY_ID_PREF, wikiDataEntityId);
}
- activity.startActivity(shareIntent);
} catch (SecurityException e) {
Timber.e(e, "Security Exception");
}
+
+ if (activity != null) {
+ activity.startActivity(shareIntent);
+ }
}
void saveState(Bundle outState) {
diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.java
index 6abb3ce43e..e366a14681 100644
--- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.java
+++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.java
@@ -3,13 +3,11 @@
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
-import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog;
-import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
@@ -33,7 +31,6 @@
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
import fr.free.nrw.commons.nearby.NearbyActivity;
-import fr.free.nrw.commons.utils.ContributionUtils;
import timber.log.Timber;
import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
@@ -154,11 +151,11 @@ public boolean onOptionsItemSelected(MenuItem item) {
new AlertDialog.Builder(getActivity())
.setMessage(getString(R.string.read_storage_permission_rationale))
- .setPositiveButton("OK", (dialog, which) -> {
+ .setPositiveButton(android.R.string.ok, (dialog, which) -> {
requestPermissions(new String[]{READ_EXTERNAL_STORAGE}, 1);
dialog.dismiss();
})
- .setNegativeButton("Cancel", null)
+ .setNegativeButton(android.R.string.cancel, null)
.create()
.show();
@@ -196,11 +193,11 @@ public boolean onOptionsItemSelected(MenuItem item) {
// sees the explanation, try again to request the permission.
new AlertDialog.Builder(getActivity())
.setMessage(getString(R.string.write_storage_permission_rationale))
- .setPositiveButton("OK", (dialog, which) -> {
+ .setPositiveButton(android.R.string.ok, (dialog, which) -> {
requestPermissions(new String[]{WRITE_EXTERNAL_STORAGE}, 3);
dialog.dismiss();
})
- .setNegativeButton("Cancel", null)
+ .setNegativeButton(android.R.string.cancel, null)
.create()
.show();
} else {
diff --git a/app/src/main/java/fr/free/nrw/commons/delete/DeleteTask.java b/app/src/main/java/fr/free/nrw/commons/delete/DeleteTask.java
index 37b9a7a828..f35a6c38e2 100644
--- a/app/src/main/java/fr/free/nrw/commons/delete/DeleteTask.java
+++ b/app/src/main/java/fr/free/nrw/commons/delete/DeleteTask.java
@@ -14,6 +14,7 @@
import javax.inject.Inject;
+import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.auth.SessionManager;
@@ -44,14 +45,17 @@ public DeleteTask (Context context, Media media, String reason){
}
@Override
- protected void onPreExecute(){
+ protected void onPreExecute() {
ApplicationlessInjection
.getInstance(context.getApplicationContext())
.getCommonsApplicationComponent()
.inject(this);
- notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
- notificationBuilder = new NotificationCompat.Builder(context);
+ notificationManager =
+ (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+ notificationBuilder = new NotificationCompat.Builder(
+ context,
+ CommonsApplication.NOTIFICATION_CHANNEL_ID_ALL);
Toast toast = new Toast(context);
toast.setGravity(Gravity.CENTER,0,0);
toast = Toast.makeText(context,"Trying to nominate "+media.getDisplayTitle()+ " for deletion",Toast.LENGTH_SHORT);
diff --git a/app/src/main/java/fr/free/nrw/commons/di/ActivityBuilderModule.java b/app/src/main/java/fr/free/nrw/commons/di/ActivityBuilderModule.java
index 70ffec55f1..6c9b38fe88 100644
--- a/app/src/main/java/fr/free/nrw/commons/di/ActivityBuilderModule.java
+++ b/app/src/main/java/fr/free/nrw/commons/di/ActivityBuilderModule.java
@@ -15,8 +15,7 @@
import fr.free.nrw.commons.nearby.NearbyActivity;
import fr.free.nrw.commons.notification.NotificationActivity;
import fr.free.nrw.commons.settings.SettingsActivity;
-import fr.free.nrw.commons.upload.MultipleShareActivity;
-import fr.free.nrw.commons.upload.ShareActivity;
+import fr.free.nrw.commons.upload.UploadActivity;
@Module
@SuppressWarnings({"WeakerAccess", "unused"})
@@ -28,11 +27,11 @@ public abstract class ActivityBuilderModule {
@ContributesAndroidInjector
abstract WelcomeActivity bindWelcomeActivity();
- @ContributesAndroidInjector
- abstract ShareActivity bindShareActivity();
+ //@ContributesAndroidInjector
+ //abstract ShareActivity bindShareActivity();
- @ContributesAndroidInjector
- abstract MultipleShareActivity bindMultipleShareActivity();
+ //@ContributesAndroidInjector
+ //abstract MultipleShareActivity bindMultipleShareActivity();
@ContributesAndroidInjector
abstract ContributionsActivity bindContributionsActivity();
@@ -64,4 +63,6 @@ public abstract class ActivityBuilderModule {
@ContributesAndroidInjector
abstract AchievementsActivity bindAchievementsActivity();
+ @ContributesAndroidInjector
+ abstract UploadActivity bindUploadActivity();
}
diff --git a/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationModule.java b/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationModule.java
index cf96193601..15cd91b62b 100644
--- a/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationModule.java
+++ b/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationModule.java
@@ -1,30 +1,41 @@
package fr.free.nrw.commons.di;
+import android.app.Activity;
import android.content.ContentProviderClient;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.support.v4.util.LruCache;
+import android.view.inputmethod.InputMethodManager;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.google.gson.Gson;
import javax.inject.Named;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
-
import fr.free.nrw.commons.BuildConfig;
+import fr.free.nrw.commons.R;
import fr.free.nrw.commons.auth.AccountUtil;
import fr.free.nrw.commons.auth.SessionManager;
+import fr.free.nrw.commons.caching.CacheController;
import fr.free.nrw.commons.data.DBOpenHelper;
import fr.free.nrw.commons.location.LocationServiceManager;
+import fr.free.nrw.commons.mwapi.ApacheHttpClientMediaWikiApi;
import fr.free.nrw.commons.mwapi.MediaWikiApi;
import fr.free.nrw.commons.nearby.NearbyPlaces;
+import fr.free.nrw.commons.settings.Prefs;
import fr.free.nrw.commons.upload.UploadController;
import fr.free.nrw.commons.wikidata.WikidataEditListener;
import fr.free.nrw.commons.wikidata.WikidataEditListenerImpl;
import static android.content.Context.MODE_PRIVATE;
-import static fr.free.nrw.commons.explore.recentsearches.RecentSearchesContentProvider.RECENT_SEARCH_AUTHORITY;
@Module
@SuppressWarnings({"WeakerAccess", "unused"})
@@ -40,6 +51,35 @@ public Context providesApplicationContext() {
return this.applicationContext;
}
+ @Provides
+ public InputMethodManager provideInputMethodManager() {
+ return (InputMethodManager) applicationContext.getSystemService(Activity.INPUT_METHOD_SERVICE);
+ }
+
+ @Provides
+ @Named("licenses")
+ public List provideLicenses(Context context) {
+ List licenseItems = new ArrayList<>();
+ licenseItems.add(context.getString(R.string.license_name_cc0));
+ licenseItems.add(context.getString(R.string.license_name_cc_by));
+ licenseItems.add(context.getString(R.string.license_name_cc_by_sa));
+ licenseItems.add(context.getString(R.string.license_name_cc_by_four));
+ licenseItems.add(context.getString(R.string.license_name_cc_by_sa_four));
+ return licenseItems;
+ }
+
+ @Provides
+ @Named("licenses_by_name")
+ public Map provideLicensesByName(Context context) {
+ Map byName = new HashMap<>();
+ byName.put(context.getString(R.string.license_name_cc0), Prefs.Licenses.CC0);
+ byName.put(context.getString(R.string.license_name_cc_by), Prefs.Licenses.CC_BY_3);
+ byName.put(context.getString(R.string.license_name_cc_by_sa), Prefs.Licenses.CC_BY_SA_3);
+ byName.put(context.getString(R.string.license_name_cc_by_four), Prefs.Licenses.CC_BY_4);
+ byName.put(context.getString(R.string.license_name_cc_by_sa_four), Prefs.Licenses.CC_BY_SA_4);
+ return byName;
+ }
+
@Provides
public AccountUtil providesAccountUtil(Context context) {
return new AccountUtil(context);
@@ -60,7 +100,7 @@ public ContentProviderClient provideCategoryContentProviderClient(Context contex
@Provides
@Named("recentsearch")
public ContentProviderClient provideRecentSearchContentProviderClient(Context context) {
- return context.getContentResolver().acquireContentProviderClient(RECENT_SEARCH_AUTHORITY);
+ return context.getContentResolver().acquireContentProviderClient(BuildConfig.RECENT_SEARCH_AUTHORITY);
}
@Provides
@@ -123,12 +163,37 @@ public SessionManager providesSessionManager(Context context,
return new SessionManager(context, mediaWikiApi, sharedPreferences);
}
+// @Provides
+// @Singleton
+// public MediaWikiApi provideMediaWikiApi(Context context,
+// @Named("default_preferences") SharedPreferences defaultPreferences,
+// @Named("category_prefs") SharedPreferences categoryPrefs,
+// Gson gson) {
+// return new ApacheHttpClientMediaWikiApi(context, BuildConfig.WIKIMEDIA_API_HOST, defaultPreferences, categoryPrefs, gson);
+// }
+
@Provides
@Singleton
public LocationServiceManager provideLocationServiceManager(Context context) {
return new LocationServiceManager(context);
}
+ /*
+ * Gson objects are very heavy. The app should ideally be using just one instance of it instead of creating new instances everywhere.
+ * @return returns a singleton Gson instance
+ */
+// @Provides
+// @Singleton
+// public Gson provideGson() {
+// return new Gson();
+// }
+//
+// @Provides
+// @Singleton
+// public CacheController provideCacheController() {
+// return new CacheController();
+// }
+
@Provides
@Singleton
public DBOpenHelper provideDBOpenHelper(Context context) {
diff --git a/app/src/main/java/fr/free/nrw/commons/di/FragmentBuilderModule.java b/app/src/main/java/fr/free/nrw/commons/di/FragmentBuilderModule.java
index b14d8feeff..18900a06aa 100644
--- a/app/src/main/java/fr/free/nrw/commons/di/FragmentBuilderModule.java
+++ b/app/src/main/java/fr/free/nrw/commons/di/FragmentBuilderModule.java
@@ -15,8 +15,6 @@
import fr.free.nrw.commons.nearby.NearbyMapFragment;
import fr.free.nrw.commons.nearby.NoPermissionsFragment;
import fr.free.nrw.commons.settings.SettingsFragment;
-import fr.free.nrw.commons.upload.MultipleUploadListFragment;
-import fr.free.nrw.commons.upload.SingleUploadFragment;
@Module
@SuppressWarnings({"WeakerAccess", "unused"})
@@ -46,12 +44,6 @@ public abstract class FragmentBuilderModule {
@ContributesAndroidInjector
abstract SettingsFragment bindSettingsFragment();
- @ContributesAndroidInjector
- abstract MultipleUploadListFragment bindMultipleUploadListFragment();
-
- @ContributesAndroidInjector
- abstract SingleUploadFragment bindSingleUploadFragment();
-
@ContributesAndroidInjector
abstract CategoryImagesListFragment bindFeaturedImagesListFragment();
diff --git a/app/src/main/java/fr/free/nrw/commons/explore/SearchActivity.java b/app/src/main/java/fr/free/nrw/commons/explore/SearchActivity.java
index 475b3900a7..38d78fb34e 100644
--- a/app/src/main/java/fr/free/nrw/commons/explore/SearchActivity.java
+++ b/app/src/main/java/fr/free/nrw/commons/explore/SearchActivity.java
@@ -53,6 +53,7 @@ public class SearchActivity extends NavigationBaseActivity implements MediaDetai
private FragmentManager supportFragmentManager;
private MediaDetailPagerFragment mediaDetails;
ViewPagerAdapter viewPagerAdapter;
+ private String query;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -104,6 +105,7 @@ public void setTabs() {
.debounce(500, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe( query -> {
+ this.query = query.toString();
//update image list
if (!TextUtils.isEmpty(query)) {
viewPager.setVisibility(View.VISIBLE);
@@ -145,7 +147,16 @@ public int getTotalMediaCount() {
*/
@Override
public void notifyDatasetChanged() {
+ }
+ /**
+ * This method is called on success of API call for image Search.
+ * The viewpager will notified that number of items have changed.
+ */
+ public void viewPagerNotifyDataSetChanged() {
+ if (mediaDetails!=null){
+ mediaDetails.notifyDataSetChanged();
+ }
}
/**
@@ -245,4 +256,14 @@ public void updateText(String query) {
// https://stackoverflow.com/questions/6117967/how-to-remove-focus-without-setting-focus-to-another-control/15481511
viewPager.requestFocus();
}
+
+ /**
+ * This method is called when viewPager has reached its end.
+ * Fetches more images using search query and adds it to the recycler view and viewpager adapter
+ */
+ public void requestMoreImages() {
+ if (searchImageFragment!=null){
+ searchImageFragment.addImagesToList(query);
+ }
+ }
}
diff --git a/app/src/main/java/fr/free/nrw/commons/explore/images/SearchImageFragment.java b/app/src/main/java/fr/free/nrw/commons/explore/images/SearchImageFragment.java
index a503207e4d..1770516201 100644
--- a/app/src/main/java/fr/free/nrw/commons/explore/images/SearchImageFragment.java
+++ b/app/src/main/java/fr/free/nrw/commons/explore/images/SearchImageFragment.java
@@ -1,6 +1,7 @@
package fr.free.nrw.commons.explore.images;
+import android.annotation.SuppressLint;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.os.Bundle;
@@ -13,6 +14,8 @@
import android.view.ViewGroup;
import android.widget.ProgressBar;
import android.widget.TextView;
+import android.widget.Toast;
+
import com.pedrogomez.renderers.RVRendererAdapter;
import java.util.ArrayList;
import java.util.Date;
@@ -119,6 +122,7 @@ public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
* Checks for internet connection and then initializes the recycler view with 25 images of the searched query
* Clearing imageAdapter every time new keyword is searched so that user can see only new results
*/
+ @SuppressLint("CheckResult")
public void updateImageList(String query) {
this.query = query;
imagesNotFoundView.setVisibility(GONE);
@@ -140,6 +144,7 @@ public void updateImageList(String query) {
/**
* Adds more results to existing search results
*/
+ @SuppressLint("CheckResult")
public void addImagesToList(String query) {
this.query = query;
progressBar.setVisibility(View.VISIBLE);
@@ -156,10 +161,13 @@ public void addImagesToList(String query) {
* @param mediaList List of media to be added
*/
private void handlePaginationSuccess(List mediaList) {
- queryList.addAll(mediaList);
progressBar.setVisibility(View.GONE);
- imagesAdapter.addAll(mediaList);
- imagesAdapter.notifyDataSetChanged();
+ if (mediaList.size() != 0 || !queryList.get(queryList.size() - 1).getFilename().equals(mediaList.get(mediaList.size() - 1).getFilename())) {
+ queryList.addAll(mediaList);
+ imagesAdapter.addAll(mediaList);
+ imagesAdapter.notifyDataSetChanged();
+ ((SearchActivity) getContext()).viewPagerNotifyDataSetChanged();
+ }
}
@@ -179,6 +187,7 @@ private void handleSuccess(List mediaList) {
progressBar.setVisibility(View.GONE);
imagesAdapter.addAll(mediaList);
imagesAdapter.notifyDataSetChanged();
+ ((SearchActivity)getContext()).viewPagerNotifyDataSetChanged();
// check if user is waiting for 5 seconds if yes then save search query to history.
Handler handler = new Handler();
@@ -193,7 +202,6 @@ private void handleSuccess(List mediaList) {
private void handleError(Throwable throwable) {
Timber.e(throwable, "Error occurred while loading queried images");
try {
- initErrorView();
ViewUtil.showSnackbar(imagesRecyclerView, R.string.error_loading_images);
}catch (Exception e){
e.printStackTrace();
@@ -239,7 +247,7 @@ public Media getImageAtPosition(int i) {
return null;
}
else {
- return new Media(imagesAdapter.getItem(i).getFilename());
+ return imagesAdapter.getItem(i);
}
}
}
diff --git a/app/src/main/java/fr/free/nrw/commons/explore/images/SearchImagesRenderer.java b/app/src/main/java/fr/free/nrw/commons/explore/images/SearchImagesRenderer.java
index 42c044d705..022e8307e2 100644
--- a/app/src/main/java/fr/free/nrw/commons/explore/images/SearchImagesRenderer.java
+++ b/app/src/main/java/fr/free/nrw/commons/explore/images/SearchImagesRenderer.java
@@ -65,11 +65,11 @@ interface ImageClickedListener {
*/
private void setAuthorView(Media item, TextView author) {
if (item.getCreator() != null && !item.getCreator().equals("")) {
- author.setVisibility(View.GONE);
+ author.setVisibility(View.VISIBLE);
String uploadedByTemplate = getContext().getString(R.string.image_uploaded_by);
author.setText(String.format(uploadedByTemplate, item.getCreator()));
} else {
- author.setVisibility(View.VISIBLE);
+ author.setVisibility(View.GONE);
}
}
}
diff --git a/app/src/main/java/fr/free/nrw/commons/explore/recentsearches/RecentSearchesContentProvider.java b/app/src/main/java/fr/free/nrw/commons/explore/recentsearches/RecentSearchesContentProvider.java
index bf3cf959ae..ed7821885e 100644
--- a/app/src/main/java/fr/free/nrw/commons/explore/recentsearches/RecentSearchesContentProvider.java
+++ b/app/src/main/java/fr/free/nrw/commons/explore/recentsearches/RecentSearchesContentProvider.java
@@ -11,6 +11,7 @@
import javax.inject.Inject;
+import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.contributions.ContributionDao;
import fr.free.nrw.commons.data.DBOpenHelper;
import fr.free.nrw.commons.di.CommonsDaggerContentProvider;
@@ -28,17 +29,16 @@
**/
public class RecentSearchesContentProvider extends CommonsDaggerContentProvider {
- public static final String RECENT_SEARCH_AUTHORITY = "fr.free.nrw.commons.explore.recentsearches.contentprovider";
// For URI matcher
private static final int RECENT_SEARCHES = 1;
private static final int RECENT_SEARCHES_ID = 2;
private static final String BASE_PATH = "recent_searches";
- public static final Uri BASE_URI = Uri.parse("content://" + RECENT_SEARCH_AUTHORITY + "/" + BASE_PATH);
+ public static final Uri BASE_URI = Uri.parse("content://" + BuildConfig.RECENT_SEARCH_AUTHORITY + "/" + BASE_PATH);
private static final UriMatcher uriMatcher = new UriMatcher(NO_MATCH);
static {
- uriMatcher.addURI(RECENT_SEARCH_AUTHORITY, BASE_PATH, RECENT_SEARCHES);
- uriMatcher.addURI(RECENT_SEARCH_AUTHORITY, BASE_PATH + "/#", RECENT_SEARCHES_ID);
+ uriMatcher.addURI(BuildConfig.RECENT_SEARCH_AUTHORITY, BASE_PATH, RECENT_SEARCHES);
+ uriMatcher.addURI(BuildConfig.RECENT_SEARCH_AUTHORITY, BASE_PATH + "/#", RECENT_SEARCHES_ID);
}
public static Uri uriForId(int id) {
diff --git a/app/src/main/java/fr/free/nrw/commons/explore/recentsearches/RecentSearchesFragment.java b/app/src/main/java/fr/free/nrw/commons/explore/recentsearches/RecentSearchesFragment.java
index 5c109fbb4e..7c0a6fcca0 100644
--- a/app/src/main/java/fr/free/nrw/commons/explore/recentsearches/RecentSearchesFragment.java
+++ b/app/src/main/java/fr/free/nrw/commons/explore/recentsearches/RecentSearchesFragment.java
@@ -41,7 +41,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container,
recent_searches_delete_button.setOnClickListener(v -> {
new AlertDialog.Builder(getContext())
.setMessage(getString(R.string.delete_recent_searches_dialog))
- .setPositiveButton("YES", (dialog, which) -> {
+ .setPositiveButton(android.R.string.yes, (dialog, which) -> {
recentSearchesDao.deleteAll(recentSearches);
Toast.makeText(getContext(),getString(R.string.search_history_deleted),Toast.LENGTH_SHORT).show();
recentSearches = recentSearchesDao.recentSearches(10);
@@ -50,7 +50,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container,
adapter.notifyDataSetChanged();
dialog.dismiss();
})
- .setNegativeButton("NO", null)
+ .setNegativeButton(android.R.string.no, null)
.create()
.show();
});
diff --git a/app/src/main/java/fr/free/nrw/commons/glide/SvgDecoder.java b/app/src/main/java/fr/free/nrw/commons/glide/SvgDecoder.java
deleted file mode 100644
index 9087f95014..0000000000
--- a/app/src/main/java/fr/free/nrw/commons/glide/SvgDecoder.java
+++ /dev/null
@@ -1,36 +0,0 @@
-package fr.free.nrw.commons.glide;
-
-import android.support.annotation.NonNull;
-
-import com.bumptech.glide.load.Options;
-import com.bumptech.glide.load.ResourceDecoder;
-import com.bumptech.glide.load.engine.Resource;
-import com.bumptech.glide.load.resource.SimpleResource;
-import com.caverock.androidsvg.SVG;
-import com.caverock.androidsvg.SVGParseException;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-/**
- * Decodes an SVG internal representation from an {@link InputStream}.
- */
-public class SvgDecoder implements ResourceDecoder {
-
- @Override
- public boolean handles(@NonNull InputStream source, @NonNull Options options) {
- // TODO: Can we tell?
- return true;
- }
-
- public Resource